From 93b3391167aaa3f3ad2c5409d945c5696167e6ed Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 1 Dec 2017 21:26:34 -0500 Subject: [PATCH 001/356] Fixes server log hardcoded to debug level Fixes #3786 To configure the log level of the server, this introduces a new task key named `serverLog`. The idea is to set this using `Global / serverLog / logLevel`. It will also check the global log level, and if all else fails, fallback to Warn. ``` lazy val level: Level.Value = (s get serverLogLevel) orElse (s get logLevel) match { case Some(x) => x case None => Level.Warn } ``` --- main-command/src/main/scala/sbt/BasicKeys.scala | 9 +++++++++ main/src/main/scala/sbt/Keys.scala | 3 ++- main/src/main/scala/sbt/Project.scala | 7 ++++++- .../scala/sbt/internal/CommandExchange.scala | 16 +++++++++++++--- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/main-command/src/main/scala/sbt/BasicKeys.scala b/main-command/src/main/scala/sbt/BasicKeys.scala index 42b68daa4..86dd9343f 100644 --- a/main-command/src/main/scala/sbt/BasicKeys.scala +++ b/main-command/src/main/scala/sbt/BasicKeys.scala @@ -11,6 +11,7 @@ import java.io.File import sbt.internal.util.AttributeKey import sbt.internal.inc.classpath.ClassLoaderCache import sbt.librarymanagement.ModuleID +import sbt.util.Level object BasicKeys { val historyPath = AttributeKey[Option[File]]( @@ -38,6 +39,14 @@ object BasicKeys { "The wire protocol for the server command.", 10000) + // Unlike other BasicKeys, this is not used directly as a setting key, + // and severLog / logLevel is used instead. + private[sbt] val serverLogLevel = + AttributeKey[Level.Value]("serverLogLevel", "The log level for the server.", 10000) + + private[sbt] val logLevel = + AttributeKey[Level.Value]("logLevel", "The amount of logging sent to the screen.", 10) + private[sbt] val interactive = AttributeKey[Boolean]( "interactive", "True if commands are currently being entered from an interactive environment.", diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 3df25679d..bfcb3139e 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -94,7 +94,7 @@ object Keys { val TraceValues = "-1 to disable, 0 for up to the first sbt frame, or a positive number to set the maximum number of frames shown." // logging - val logLevel = settingKey[Level.Value]("The amount of logging sent to the screen.").withRank(ASetting) + val logLevel = SettingKey(BasicKeys.logLevel).withRank(ASetting) val persistLogLevel = settingKey[Level.Value]("The amount of logging sent to a file for persistence.").withRank(CSetting) val traceLevel = settingKey[Int]("The amount of a stack trace displayed. " + TraceValues).withRank(ASetting) val persistTraceLevel = settingKey[Int]("The amount of stack trace persisted.").withRank(CSetting) @@ -105,6 +105,7 @@ object Keys { val logManager = settingKey[LogManager]("The log manager, which creates Loggers for different contexts.").withRank(DSetting) val logBuffered = settingKey[Boolean]("True if logging should be buffered until work completes.").withRank(CSetting) val sLog = settingKey[Logger]("Logger usable by settings during project loading.").withRank(CSetting) + val serverLog = taskKey[Unit]("A dummy task to set server log level using Global / serverLog / logLevel.").withRank(CTask) // Project keys val autoGeneratedProject = settingKey[Boolean]("If it exists, represents that the project (and name) were automatically created, rather than user specified.").withRank(DSetting) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 680bba811..fd175f6c6 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -11,6 +11,7 @@ import java.io.File import java.net.URI import java.util.Locale import Project._ +import BasicKeys.serverLogLevel import Keys.{ stateBuildStructure, commands, @@ -21,9 +22,11 @@ import Keys.{ shellPrompt, templateResolverInfos, serverHost, + serverLog, serverPort, serverAuthentication, serverConnectionType, + logLevel, watch } import Scope.{ Global, ThisScope } @@ -41,7 +44,7 @@ import sbt.internal.util.{ AttributeKey, AttributeMap, Dag, Relation, Settings, import sbt.internal.util.Types.{ const, idFun } import sbt.internal.util.complete.DefaultParsers import sbt.librarymanagement.Configuration -import sbt.util.Show +import sbt.util.{ Show, Level } import sjsonnew.JsonFormat import language.experimental.macros @@ -463,6 +466,7 @@ object Project extends ProjectExtra { val port: Option[Int] = get(serverPort) val authentication: Option[Set[ServerAuthentication]] = get(serverAuthentication) val connectionType: Option[ConnectionType] = get(serverConnectionType) + val srvLogLevel: Option[Level.Value] = (logLevel in (ref, serverLog)).get(structure.data) val commandDefs = allCommands.distinct.flatten[Command].map(_ tag (projectCommand, true)) val newDefinedCommands = commandDefs ++ BasicCommands.removeTagged(s.definedCommands, projectCommand) @@ -477,6 +481,7 @@ object Project extends ProjectExtra { .put(historyPath.key, history) .put(templateResolverInfos.key, trs) .setCond(shellPrompt.key, prompt) + .setCond(serverLogLevel, srvLogLevel) s.copy( attributes = newAttrs, definedCommands = newDefinedCommands diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 7bbb2c38b..476ea5f76 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -13,7 +13,14 @@ import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable.ListBuffer import scala.annotation.tailrec -import BasicKeys.{ serverHost, serverPort, serverAuthentication, serverConnectionType } +import BasicKeys.{ + serverHost, + serverPort, + serverAuthentication, + serverConnectionType, + serverLogLevel, + logLevel +} import java.net.Socket import sjsonnew.JsonFormat import sjsonnew.shaded.scalajson.ast.unsafe._ @@ -106,7 +113,10 @@ private[sbt] final class CommandExchange { case Some(x) => x case None => ConnectionType.Tcp } - val serverLogLevel: Level.Value = Level.Debug + lazy val level: Level.Value = (s get serverLogLevel) + .orElse(s get logLevel) + .getOrElse(Level.Warn) + def onIncomingSocket(socket: Socket, instance: ServerInstance): Unit = { val name = newNetworkName s.log.info(s"new client connected: $name") @@ -114,7 +124,7 @@ private[sbt] final class CommandExchange { val log = LogExchange.logger(name, None, None) LogExchange.unbindLoggerAppenders(name) val appender = MainAppender.defaultScreen(s.globalLogging.console) - LogExchange.bindLoggerAppenders(name, List(appender -> serverLogLevel)) + LogExchange.bindLoggerAppenders(name, List(appender -> level)) log } val channel = From ca7171ed17dc3b862fe79cc8a6e0fe535b861555 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Fri, 1 Dec 2017 12:39:59 -0800 Subject: [PATCH 002/356] Cache watch service I noticed that my custom WatchService was never cleaned up by sbt and realized that after every build we were making a new WatchService. At the same time, we were reusing the WatchState from the previous run, which was using the original WatchService. This was particularly problematic because it prevented us from registering any paths with the new watch service. This may have prevented some of the file updates from being seen by the watch service. Moreover, because we lost the reference to the original WatchService, there was no way to clean it up, which was a resource leak. May be related to #3775, #3695 --- main-command/src/main/scala/sbt/Watched.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index fa1dee03f..8b8385b61 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -94,7 +94,7 @@ object Watched { @tailrec def shouldTerminate: Boolean = (System.in.available > 0) && (watched.terminateWatch(System.in.read()) || shouldTerminate) val sources = watched.watchSources(s) - val service = watched.watchService() + val service = s get ContinuousWatchService getOrElse watched.watchService() val watchState = s get ContinuousState getOrElse WatchState.empty(service, sources) if (watchState.count > 0) @@ -115,15 +115,21 @@ object Watched { if (triggered) { printIfDefined(watched triggeredMessage newWatchState) - (ClearOnFailure :: next :: FailureWall :: repeat :: s).put(ContinuousState, newWatchState) + (ClearOnFailure :: next :: FailureWall :: repeat :: s) + .put(ContinuousState, newWatchState) + .put(ContinuousWatchService, service) } else { while (System.in.available() > 0) System.in.read() service.close() - s.remove(ContinuousState) + s.remove(ContinuousState).remove(ContinuousWatchService) } } val ContinuousState = AttributeKey[WatchState]("watch state", "Internal: tracks state for continuous execution.") + + val ContinuousWatchService = + AttributeKey[WatchService]("watch service", + "Internal: tracks watch service for continuous execution.") val Configuration = AttributeKey[Watched]("watched-configuration", "Configures continuous execution.") From 2b2c1f05684d05f589bafd5b0f0107e93e308c8a Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 5 Dec 2017 06:32:50 -0500 Subject: [PATCH 003/356] prevent "shutdown" when server didn't come up --- main/src/main/scala/sbt/internal/CommandExchange.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 476ea5f76..32175d256 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -152,11 +152,13 @@ private[sbt] final class CommandExchange { Await.ready(x.ready, Duration("10s")) x.ready.value match { case Some(Success(_)) => + // rememeber to shutdown only when the server comes up + server = Some(x) case Some(Failure(e)) => s.log.error(e.toString) + server = None case None => // this won't happen because we awaited } - server = Some(x) } s } From 322f9b31cdea4daab1d8029b2622b50069fe7555 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 5 Dec 2017 08:07:20 -0500 Subject: [PATCH 004/356] Only the first session starts the server Currently the server will try to start even if there are existing sbt sessions. This causes the second session to take over the server for Unix domain socket. This adds a check before the server comes up and make sure that the socket is not taken. --- .../java/sbt/internal/NGUnixDomainSocket.java | 21 ++++++++++ .../internal/NGUnixDomainSocketLibrary.java | 2 + .../scala/sbt/internal/server/Server.scala | 40 ++++++++++++++----- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/main-command/src/main/java/sbt/internal/NGUnixDomainSocket.java b/main-command/src/main/java/sbt/internal/NGUnixDomainSocket.java index 1a9942ad9..b70bef611 100644 --- a/main-command/src/main/java/sbt/internal/NGUnixDomainSocket.java +++ b/main-command/src/main/java/sbt/internal/NGUnixDomainSocket.java @@ -29,6 +29,8 @@ import java.nio.ByteBuffer; import java.net.Socket; +import java.util.concurrent.atomic.AtomicInteger; + /** * Implements a {@link Socket} backed by a native Unix domain socket. * @@ -41,6 +43,25 @@ public class NGUnixDomainSocket extends Socket { private final InputStream is; private final OutputStream os; + public NGUnixDomainSocket(String path) throws IOException { + try { + AtomicInteger fd = new AtomicInteger( + NGUnixDomainSocketLibrary.socket( + NGUnixDomainSocketLibrary.PF_LOCAL, + NGUnixDomainSocketLibrary.SOCK_STREAM, + 0)); + NGUnixDomainSocketLibrary.SockaddrUn address = + new NGUnixDomainSocketLibrary.SockaddrUn(path); + int socketFd = fd.get(); + NGUnixDomainSocketLibrary.connect(socketFd, address, address.size()); + this.fd = new ReferenceCountedFileDescriptor(socketFd); + this.is = new NGUnixDomainSocketInputStream(); + this.os = new NGUnixDomainSocketOutputStream(); + } catch (LastErrorException e) { + throw new IOException(e); + } + } + /** * Creates a Unix domain socket backed by a native file descriptor. */ diff --git a/main-command/src/main/java/sbt/internal/NGUnixDomainSocketLibrary.java b/main-command/src/main/java/sbt/internal/NGUnixDomainSocketLibrary.java index 7e760d37a..4d781b6b6 100644 --- a/main-command/src/main/java/sbt/internal/NGUnixDomainSocketLibrary.java +++ b/main-command/src/main/java/sbt/internal/NGUnixDomainSocketLibrary.java @@ -131,6 +131,8 @@ public class NGUnixDomainSocketLibrary { public static native int listen(int fd, int backlog) throws LastErrorException; public static native int accept(int fd, SockaddrUn address, IntByReference addressLen) throws LastErrorException; + public static native int connect(int fd, SockaddrUn address, int addressLen) + throws LastErrorException; public static native int read(int fd, ByteBuffer buffer, int count) throws LastErrorException; public static native int write(int fd, ByteBuffer buffer, int count) diff --git a/main-command/src/main/scala/sbt/internal/server/Server.scala b/main-command/src/main/scala/sbt/internal/server/Server.scala index c4d3b542c..0b8ee4b32 100644 --- a/main-command/src/main/scala/sbt/internal/server/Server.scala +++ b/main-command/src/main/scala/sbt/internal/server/Server.scala @@ -9,7 +9,7 @@ package sbt package internal package server -import java.io.File +import java.io.{ File, IOException } import java.net.{ SocketTimeoutException, InetAddress, ServerSocket, Socket } import java.util.concurrent.atomic.AtomicBoolean import java.nio.file.attribute.{ UserPrincipal, AclEntry, AclEntryPermission, AclEntryType } @@ -54,15 +54,17 @@ private[sbt] object Server { val serverThread = new Thread("sbt-socket-server") { override def run(): Unit = { Try { - ErrorHandling.translate(s"server failed to start on ${connection.shortName}. ") { - connection.connectionType match { - case ConnectionType.Local if isWindows => - new NGWin32NamedPipeServerSocket(pipeName) - case ConnectionType.Local => - prepareSocketfile() - new NGUnixDomainServerSocket(socketfile.getAbsolutePath) - case ConnectionType.Tcp => new ServerSocket(port, 50, InetAddress.getByName(host)) - } + connection.connectionType match { + case ConnectionType.Local if isWindows => + // Named pipe already has an exclusive lock. + addServerError(new NGWin32NamedPipeServerSocket(pipeName)) + case ConnectionType.Local => + tryClient(new NGUnixDomainSocket(socketfile.getAbsolutePath)) + prepareSocketfile() + addServerError(new NGUnixDomainServerSocket(socketfile.getAbsolutePath)) + case ConnectionType.Tcp => + tryClient(new Socket(InetAddress.getByName(host), port)) + addServerError(new ServerSocket(port, 50, InetAddress.getByName(host))) } } match { case Failure(e) => p.failure(e) @@ -87,6 +89,24 @@ private[sbt] object Server { } serverThread.start() + // Try the socket as a client to make sure that the server is not already up. + // f tries to connect to the server, and flip the result. + def tryClient(f: => Socket): Unit = { + if (portfile.exists) { + Try { f } match { + case Failure(e) => () + case Success(socket) => + socket.close() + throw new IOException("sbt server is already running.") + } + } else () + } + + def addServerError(f: => ServerSocket): ServerSocket = + ErrorHandling.translate(s"server failed to start on ${connection.shortName}. ") { + f + } + override def authenticate(challenge: String): Boolean = synchronized { if (token == challenge) { token = nextToken From 5c72c3b4879cf00a0e2ab8447cfa8dd4d2aa1d85 Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Fri, 8 Dec 2017 19:21:14 +0100 Subject: [PATCH 005/356] Revert "Alt scala parser sync workaround" Although in theory the fix in #3776 should be preferable to synchronize templateStats() manually, it turns out that we still get errors in some tests. So, reverting to a synchronized section while we investigate. This reverts commit ee90917cc4bb0de8294fc4d93633c798a4866ff4. --- main/src/main/scala/sbt/internal/parser/SbtParser.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main/src/main/scala/sbt/internal/parser/SbtParser.scala b/main/src/main/scala/sbt/internal/parser/SbtParser.scala index 6e89cdf28..18dfa2b5e 100644 --- a/main/src/main/scala/sbt/internal/parser/SbtParser.scala +++ b/main/src/main/scala/sbt/internal/parser/SbtParser.scala @@ -116,9 +116,7 @@ private[sbt] object SbtParser { scalacGlobalInitReporter = Some(new ConsoleReporter(settings)) // Mix Positions, otherwise global ignores -Yrangepos - val global = new Global(settings, globalReporter) with Positions { - override protected def synchronizeNames = true // https://github.com/scala/bug/issues/10605 - } + val global = new Global(settings, globalReporter) with Positions val run = new global.Run // Add required dummy unit for initialization... val initFile = new BatchSourceFile("", "") @@ -151,7 +149,9 @@ private[sbt] object SbtParser { val wrapperFile = new BatchSourceFile(reporterId, code) val unit = new CompilationUnit(wrapperFile) val parser = new syntaxAnalyzer.UnitParser(unit) - val parsedTrees = parser.templateStats() + val parsedTrees = SbtParser.synchronized { // see https://github.com/scala/bug/issues/10605 + parser.templateStats() + } parser.accept(scala.tools.nsc.ast.parser.Tokens.EOF) globalReporter.throwParserErrorsIfAny(reporter, filePath) parsedTrees -> reporterId From bbddb26224f1c9695c570a9142b08408d8e2e232 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 6 Dec 2017 17:35:42 +0000 Subject: [PATCH 006/356] 2.12.4 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 4cfe0e774..a8a1195dc 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -scalaVersion := "2.12.3" +scalaVersion := "2.12.4" scalacOptions ++= Seq("-feature", "-language:postfixOps") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.17") From 1f1806a4f19a0ec3c926d68cb6c33f85e4a364b1 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 6 Dec 2017 16:45:17 +0000 Subject: [PATCH 007/356] Re-enable unused warnings --- project/Util.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project/Util.scala b/project/Util.scala index 89556cd2e..bcbedcabf 100644 --- a/project/Util.scala +++ b/project/Util.scala @@ -48,7 +48,8 @@ object Util { "-Yno-adapted-args", "-Ywarn-dead-code", "-Ywarn-numeric-widen", - "-Ywarn-unused:-patvars,-params,-implicits,_", + //"-Ywarn-value-discard", + "-Ywarn-unused", "-Ywarn-unused-import" ) }), From b80a6b217b672be669032acf9be0bf78e76904a8 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 Dec 2017 15:50:45 +0000 Subject: [PATCH 008/356] Remove all warnings from collectionProj --- build.sbt | 5 +++++ .../src/main/scala/sbt/internal/util/IDSet.scala | 4 +++- .../src/main/scala/sbt/internal/util/KList.scala | 9 +++++---- .../src/main/scala/sbt/internal/util/Signal.scala | 2 +- .../scala/sbt/internal/util/TypeFunctions.scala | 1 + .../src/test/scala/SettingsTest.scala | 13 ++++++------- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/build.sbt b/build.sbt index 9a1e8b923..8625be777 100644 --- a/build.sbt +++ b/build.sbt @@ -157,6 +157,11 @@ val collectionProj = (project in file("internal") / "util-collection") exclude[MissingClassProblem]("sbt.internal.util.Fn1"), exclude[DirectMissingMethodProblem]("sbt.internal.util.TypeFunctions.toFn1"), exclude[DirectMissingMethodProblem]("sbt.internal.util.Types.toFn1"), + + // Instead of defining foldr in KList & overriding in KCons, + // it's now abstract in KList and defined in both KCons & KNil. + exclude[FinalMethodProblem]("sbt.internal.util.KNil.foldr"), + exclude[DirectAbstractMethodProblem]("sbt.internal.util.KList.foldr"), ), ) .configure(addSbtUtilPosition) diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/IDSet.scala b/internal/util-collection/src/main/scala/sbt/internal/util/IDSet.scala index 356b6906b..6bd5cff95 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/IDSet.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/IDSet.scala @@ -7,6 +7,8 @@ package sbt.internal.util +import scala.collection.JavaConverters._ + /** A mutable set interface that uses object identity to test for set membership.*/ trait IDSet[T] { def apply(t: T): Boolean @@ -41,7 +43,7 @@ object IDSet { def +=(t: T) = { backing.put(t, Dummy); () } def ++=(t: Iterable[T]) = t foreach += def -=(t: T) = if (backing.remove(t) eq null) false else true - def all = collection.JavaConverters.collectionAsScalaIterable(backing.keySet) + def all = backing.keySet.asScala def toList = all.toList def isEmpty = backing.isEmpty diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/KList.scala b/internal/util-collection/src/main/scala/sbt/internal/util/KList.scala index a68b61788..51babcae3 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/KList.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/KList.scala @@ -18,7 +18,7 @@ sealed trait KList[+M[_]] { def transform[N[_]](f: M ~> N): Transform[N] /** Folds this list using a function that operates on the homogeneous type of the elements of this list. */ - def foldr[B](f: (M[_], B) => B, init: B): B = init // had trouble defining it in KNil + def foldr[B](f: (M[_], B) => B, init: B): B /** Applies `f` to the elements of this list in the applicative functor defined by `ap`. */ def apply[N[x] >: M[x], Z](f: Transform[Id] => Z)(implicit ap: Applicative[N]): N[Z] @@ -54,13 +54,14 @@ final case class KCons[H, +T <: KList[M], +M[_]](head: M[H], tail: T) extends KL override def foldr[B](f: (M[_], B) => B, init: B): B = f(head, tail.foldr(f, init)) } -sealed abstract class KNil extends KList[Nothing] { +sealed abstract class KNil extends KList[NothingK] { final type Transform[N[_]] = KNil - final def transform[N[_]](f: Nothing ~> N): Transform[N] = KNil + final def transform[N[_]](f: NothingK ~> N): Transform[N] = KNil + final def foldr[B](f: (NothingK[_], B) => B, init: B): B = init final def toList = Nil final def apply[N[x], Z](f: KNil => Z)(implicit ap: Applicative[N]): N[Z] = ap.pure(f(KNil)) - final def traverse[N[_], P[_]](f: Nothing ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[KNil] = + final def traverse[N[_], P[_]](f: NothingK ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[KNil] = np.pure(KNil) } diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/Signal.scala b/internal/util-collection/src/main/scala/sbt/internal/util/Signal.scala index f1989ae19..661890b88 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/Signal.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/Signal.scala @@ -65,7 +65,7 @@ object Signals { } // Must only be referenced using a -// try { } catch { case e: LinkageError => ... } +// try { } catch { case _: LinkageError => ... } // block to private final class Signals0 { def supported(signal: String): Boolean = { diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/TypeFunctions.scala b/internal/util-collection/src/main/scala/sbt/internal/util/TypeFunctions.scala index 49c29ff11..8ec06890a 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/TypeFunctions.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/TypeFunctions.scala @@ -9,6 +9,7 @@ package sbt.internal.util trait TypeFunctions { type Id[X] = X + type NothingK[X] = Nothing sealed trait Const[A] { type Apply[B] = A } sealed trait ConstK[A] { type l[L[x]] = A } sealed trait Compose[A[_], B[_]] { type Apply[T] = A[B[T]] } diff --git a/internal/util-collection/src/test/scala/SettingsTest.scala b/internal/util-collection/src/test/scala/SettingsTest.scala index 235cf5508..83d444079 100644 --- a/internal/util-collection/src/test/scala/SettingsTest.scala +++ b/internal/util-collection/src/test/scala/SettingsTest.scala @@ -7,8 +7,7 @@ package sbt.internal.util -import org.scalacheck._ -import Prop._ +import org.scalacheck._, Prop._ object SettingsTest extends Properties("settings") { val settingsExample: SettingsExample = SettingsExample() @@ -160,7 +159,7 @@ object SettingsTest extends Properties("settings") { final def checkCircularReferences(intermediate: Int): Prop = { val ccr = new CCR(intermediate) try { evaluate(setting(chk, ccr.top) :: Nil); false } catch { - case e: java.lang.Exception => true + case _: java.lang.Exception => true } } @@ -197,18 +196,18 @@ object SettingsTest extends Properties("settings") { def evaluate(settings: Seq[Setting[_]]): Settings[Scope] = try { make(settings)(delegates, scopeLocal, showFullKey) } catch { - case e: Throwable => e.printStackTrace; throw e + case e: Throwable => e.printStackTrace(); throw e } } // This setup is a workaround for module synchronization issues final class CCR(intermediate: Int) { import SettingsTest.settingsExample._ - lazy val top = iterate(value(intermediate), intermediate) - def iterate(init: Initialize[Int], i: Int): Initialize[Int] = + lazy val top = iterate(value(intermediate)) + def iterate(init: Initialize[Int]): Initialize[Int] = bind(init) { t => if (t <= 0) top else - iterate(value(t - 1), t - 1) + iterate(value(t - 1)) } } From 34136fb70b92f562b98781363f480d2f037f070f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 Dec 2017 17:48:44 +0000 Subject: [PATCH 009/356] Remove all warnings from logicProj --- internal/util-logic/src/test/scala/sbt/logic/Test.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/util-logic/src/test/scala/sbt/logic/Test.scala b/internal/util-logic/src/test/scala/sbt/logic/Test.scala index 51d2f2f67..f83835f39 100644 --- a/internal/util-logic/src/test/scala/sbt/logic/Test.scala +++ b/internal/util-logic/src/test/scala/sbt/logic/Test.scala @@ -24,14 +24,14 @@ object LogicTest extends Properties("Logic") { property("Properly orders results.") = secure(expect(ordering, Set(B, A, C, E, F))) property("Detects cyclic negation") = secure( Logic.reduceAll(badClauses, Set()) match { - case Right(res) => false - case Left(err: Logic.CyclicNegation) => true - case Left(err) => sys.error(s"Expected cyclic error, got: $err") + case Right(_) => false + case Left(_: Logic.CyclicNegation) => true + case Left(err) => sys.error(s"Expected cyclic error, got: $err") } ) def expect(result: Either[LogicException, Matched], expected: Set[Atom]) = result match { - case Left(err) => false + case Left(_) => false case Right(res) => val actual = res.provenSet if (actual != expected) From 4a5ff4fc0db1a0026cacc12606f2d445426da615 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 Dec 2017 16:18:08 +0000 Subject: [PATCH 010/356] Remove all warnings from completeProj --- build.sbt | 5 ++ .../internal/util/complete/EditDistance.scala | 3 +- .../sbt/internal/util/complete/History.scala | 10 +-- .../util/complete/JLineCompletion.scala | 4 +- .../scala/sbt/complete/FileExamplesTest.scala | 70 +++++++++---------- .../src/main/scala/sbt/BasicCommands.scala | 5 +- main-command/src/main/scala/sbt/Command.scala | 19 ++--- 7 files changed, 56 insertions(+), 60 deletions(-) diff --git a/build.sbt b/build.sbt index 8625be777..a54dd57a3 100644 --- a/build.sbt +++ b/build.sbt @@ -174,6 +174,11 @@ val completeProj = (project in file("internal") / "util-complete") name := "Completion", libraryDependencies += jline, mimaSettings, + mimaBinaryIssueFilters ++= Seq( + // Changed signature or removed something in the internal pacakge + exclude[DirectMissingMethodProblem]("sbt.internal.*"), + exclude[IncompatibleResultTypeProblem]("sbt.internal.*"), + ), ) .configure(addSbtIO, addSbtUtilControl) diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/complete/EditDistance.scala b/internal/util-complete/src/main/scala/sbt/internal/util/complete/EditDistance.scala index 5a8efe1be..cad2e5002 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/complete/EditDistance.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/complete/EditDistance.scala @@ -10,7 +10,7 @@ package complete import java.lang.Character.{ toLowerCase => lower } -/** @author Paul Phillips*/ +/** @author Paul Phillips */ object EditDistance { /** @@ -24,7 +24,6 @@ object EditDistance { insertCost: Int = 1, deleteCost: Int = 1, subCost: Int = 1, - transposeCost: Int = 1, matchCost: Int = 0, caseCost: Int = 1, transpositions: Boolean = false diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/complete/History.scala b/internal/util-complete/src/main/scala/sbt/internal/util/complete/History.scala index 1decc7197..8c63bf592 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/complete/History.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/complete/History.scala @@ -11,11 +11,7 @@ package complete import History.number import java.io.File -final class History private ( - val lines: IndexedSeq[String], - val path: Option[File], - error: String => Unit -) { +final class History private (val lines: IndexedSeq[String], val path: Option[File]) { private def reversed = lines.reverse def all: Seq[String] = lines @@ -52,8 +48,8 @@ final class History private ( } object History { - def apply(lines: Seq[String], path: Option[File], error: String => Unit): History = - new History(lines.toIndexedSeq, path, sys.error) + def apply(lines: Seq[String], path: Option[File]): History = + new History(lines.toIndexedSeq, path) def number(s: String): Option[Int] = try { Some(s.toInt) } catch { case _: NumberFormatException => None } diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/complete/JLineCompletion.scala b/internal/util-complete/src/main/scala/sbt/internal/util/complete/JLineCompletion.scala index 68d03b841..700dafa35 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/complete/JLineCompletion.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/complete/JLineCompletion.scala @@ -11,7 +11,7 @@ package complete import jline.console.ConsoleReader import jline.console.completer.{ Completer, CompletionHandler } import scala.annotation.tailrec -import scala.collection.JavaConverters +import scala.collection.JavaConverters._ object JLineCompletion { def installCustomCompletor(reader: ConsoleReader, parser: Parser[_]): Unit = @@ -154,7 +154,7 @@ object JLineCompletion { if (line.charAt(line.length - 1) != '\n') reader.println() } - reader.printColumns(JavaConverters.seqAsJavaList(columns.map(_.trim))) + reader.printColumns(columns.map(_.trim).asJava) } def hasNewline(s: String): Boolean = s.indexOf('\n') >= 0 diff --git a/internal/util-complete/src/test/scala/sbt/complete/FileExamplesTest.scala b/internal/util-complete/src/test/scala/sbt/complete/FileExamplesTest.scala index 61c6e53b1..173b276fc 100644 --- a/internal/util-complete/src/test/scala/sbt/complete/FileExamplesTest.scala +++ b/internal/util-complete/src/test/scala/sbt/complete/FileExamplesTest.scala @@ -9,60 +9,64 @@ package sbt.internal.util package complete import java.io.File -import sbt.io.IO._ +import sbt.io.IO class FileExamplesTest extends UnitSpec { "listing all files in an absolute base directory" should "produce the entire base directory's contents" in { - val _ = new DirectoryStructure { - fileExamples().toList should contain theSameElementsAs (allRelativizedPaths) + withDirectoryStructure() { ds => + ds.fileExamples().toList should contain theSameElementsAs (ds.allRelativizedPaths) } } - "listing files with a prefix that matches none" should - "produce an empty list" in { - val _ = new DirectoryStructure(withCompletionPrefix = "z") { - fileExamples().toList shouldBe empty + "listing files with a prefix that matches none" should "produce an empty list" in { + withDirectoryStructure(withCompletionPrefix = "z") { ds => + ds.fileExamples().toList shouldBe empty } } - "listing single-character prefixed files" should - "produce matching paths only" in { - val _ = new DirectoryStructure(withCompletionPrefix = "f") { - fileExamples().toList should contain theSameElementsAs (prefixedPathsOnly) + "listing single-character prefixed files" should "produce matching paths only" in { + withDirectoryStructure(withCompletionPrefix = "f") { ds => + ds.fileExamples().toList should contain theSameElementsAs (ds.prefixedPathsOnly) } } - "listing directory-prefixed files" should - "produce matching paths only" in { - val _ = new DirectoryStructure(withCompletionPrefix = "far") { - fileExamples().toList should contain theSameElementsAs (prefixedPathsOnly) + "listing directory-prefixed files" should "produce matching paths only" in { + withDirectoryStructure(withCompletionPrefix = "far") { ds => + ds.fileExamples().toList should contain theSameElementsAs (ds.prefixedPathsOnly) } } it should "produce sub-dir contents only when appending a file separator to the directory" in { - val _ = new DirectoryStructure(withCompletionPrefix = "far" + File.separator) { - fileExamples().toList should contain theSameElementsAs (prefixedPathsOnly) + withDirectoryStructure(withCompletionPrefix = "far" + File.separator) { ds => + ds.fileExamples().toList should contain theSameElementsAs (ds.prefixedPathsOnly) } } - "listing files with a sub-path prefix" should - "produce matching paths only" in { - val _ = new DirectoryStructure(withCompletionPrefix = "far" + File.separator + "ba") { - fileExamples().toList should contain theSameElementsAs (prefixedPathsOnly) + "listing files with a sub-path prefix" should "produce matching paths only" in { + withDirectoryStructure(withCompletionPrefix = "far" + File.separator + "ba") { ds => + ds.fileExamples().toList should contain theSameElementsAs (ds.prefixedPathsOnly) } } - "completing a full path" should - "produce a list with an empty string" in { - val _ = new DirectoryStructure(withCompletionPrefix = "bazaar") { - fileExamples().toList shouldEqual List("") + "completing a full path" should "produce a list with an empty string" in { + withDirectoryStructure(withCompletionPrefix = "bazaar") { ds => + ds.fileExamples().toList shouldEqual List("") } } - // TODO: Remove DelayedInit - https://github.com/scala/scala/releases/tag/v2.11.0-RC1 - class DirectoryStructure(withCompletionPrefix: String = "") extends DelayedInit { + def withDirectoryStructure[A](withCompletionPrefix: String = "")( + thunk: DirectoryStructure => A): Unit = { + IO.withTemporaryDirectory { tempDir => + val ds = new DirectoryStructure(withCompletionPrefix) + ds.createSampleDirStructure(tempDir) + ds.fileExamples = new FileExamples(ds.baseDir, withCompletionPrefix) + thunk(ds) + } + } + + final class DirectoryStructure(withCompletionPrefix: String) { var fileExamples: FileExamples = _ var baseDir: File = _ var childFiles: List[File] = _ @@ -72,22 +76,14 @@ class FileExamplesTest extends UnitSpec { def allRelativizedPaths: List[String] = (childFiles ++ childDirectories ++ nestedFiles ++ nestedDirectories) - .map(relativize(baseDir, _).get) + .map(IO.relativize(baseDir, _).get) def prefixedPathsOnly: List[String] = allRelativizedPaths .filter(_ startsWith withCompletionPrefix) .map(_ substring withCompletionPrefix.length) - override def delayedInit(testBody: => Unit): Unit = { - withTemporaryDirectory { tempDir => - createSampleDirStructure(tempDir) - fileExamples = new FileExamples(baseDir, withCompletionPrefix) - testBody - } - } - - private def createSampleDirStructure(tempDir: File): Unit = { + def createSampleDirStructure(tempDir: File): Unit = { childFiles = toChildFiles(tempDir, List("foo", "bar", "bazaar")) childDirectories = toChildFiles(tempDir, List("moo", "far")) nestedFiles = toChildFiles(childDirectories(1), List("farfile1", "barfile2")) diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index 82bb7b883..584e1196e 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -236,10 +236,9 @@ object BasicCommands { def historyParser(s: State): Parser[() => State] = Command.applyEffect(HistoryCommands.actionParser) { histFun => - val logError = (msg: String) => s.log.error(msg) - val hp = s get historyPath getOrElse None + val hp = (s get historyPath).flatten val lines = hp.toList.flatMap(p => IO.readLines(p)).toIndexedSeq - histFun(CHistory(lines, hp, logError)) match { + histFun(CHistory(lines, hp)) match { case Some(commands) => commands foreach println //printing is more appropriate than logging (commands ::: s).continue diff --git a/main-command/src/main/scala/sbt/Command.scala b/main-command/src/main/scala/sbt/Command.scala index 53158daa4..9d9be7a33 100644 --- a/main-command/src/main/scala/sbt/Command.scala +++ b/main-command/src/main/scala/sbt/Command.scala @@ -178,15 +178,16 @@ object Command { bs map (b => (b, distance(a, b))) filter (_._2 <= maxDistance) sortBy (_._2) take (maxSuggestions) map (_._1) def distance(a: String, b: String): Int = - EditDistance.levenshtein(a, - b, - insertCost = 1, - deleteCost = 1, - subCost = 2, - transposeCost = 1, - matchCost = -1, - caseCost = 1, - transpositions = true) + EditDistance.levenshtein( + a, + b, + insertCost = 1, + deleteCost = 1, + subCost = 2, + matchCost = -1, + caseCost = 1, + transpositions = true + ) def spacedAny(name: String): Parser[String] = spacedC(name, any) From f274aaa8111979fd505d5c7338ce3882e8860fe9 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 Dec 2017 16:04:20 +0000 Subject: [PATCH 011/356] Remove all warnings from taskProj --- tasks/src/main/scala/sbt/Execute.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tasks/src/main/scala/sbt/Execute.scala b/tasks/src/main/scala/sbt/Execute.scala index 267a1383a..bba13f448 100644 --- a/tasks/src/main/scala/sbt/Execute.scala +++ b/tasks/src/main/scala/sbt/Execute.scala @@ -360,8 +360,11 @@ private[sbt] final class Execute[A[_] <: AnyRef]( // cyclic reference checking def snapshotCycleCheck(): Unit = - for ((called: A[c], callers) <- callers.toSeq; caller <- callers) - cycleCheck(caller.asInstanceOf[A[c]], called) + callers.toSeq foreach { + case (called: A[c], callers) => + for (caller <- callers) cycleCheck(caller.asInstanceOf[A[c]], called) + case _ => () + } def cycleCheck[T](node: A[T], target: A[T]): Unit = { if (node eq target) cyclic(node, target, "Cannot call self") From 87dfb2c0f54b11e86633cd2752188ae330849a09 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 Dec 2017 16:33:20 +0000 Subject: [PATCH 012/356] Remove all warnings from stdTaskProj --- tasks-standard/src/main/scala/sbt/Action.scala | 2 +- tasks-standard/src/test/scala/TaskRunnerCircular.scala | 3 ++- tasks-standard/src/test/scala/TaskRunnerFork.scala | 4 ++-- tasks-standard/src/test/scala/Test.scala | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tasks-standard/src/main/scala/sbt/Action.scala b/tasks-standard/src/main/scala/sbt/Action.scala index 0be3a8ae5..be5585cbd 100644 --- a/tasks-standard/src/main/scala/sbt/Action.scala +++ b/tasks-standard/src/main/scala/sbt/Action.scala @@ -25,7 +25,7 @@ sealed trait Action[T] { * If `inline` is true, `f` will be evaluated on the scheduler thread without the overhead of normal scheduling when possible. * This is intended as an optimization for already evaluated values or very short pure computations. */ -final case class Pure[T](f: () => T, inline: Boolean) extends Action[T] { +final case class Pure[T](f: () => T, `inline`: Boolean) extends Action[T] { private[sbt] def mapTask(f: Task ~> Task) = this } diff --git a/tasks-standard/src/test/scala/TaskRunnerCircular.scala b/tasks-standard/src/test/scala/TaskRunnerCircular.scala index 1350a22b0..3d542a9db 100644 --- a/tasks-standard/src/test/scala/TaskRunnerCircular.scala +++ b/tasks-standard/src/test/scala/TaskRunnerCircular.scala @@ -42,8 +42,9 @@ object TaskRunnerCircularTest extends Properties("TaskRunner Circular") { } try { tryRun(top, true, workers); false } catch { case i: Incomplete => cyclic(i) } } + def cyclic(i: Incomplete) = Incomplete .allExceptions(i) - .exists(_.isInstanceOf[Execute[({ type A[_] <: AnyRef })#A]#CyclicException[_]]) + .exists(_.isInstanceOf[Execute[({ type A[_] <: AnyRef })#A @unchecked]#CyclicException[_]]) } diff --git a/tasks-standard/src/test/scala/TaskRunnerFork.scala b/tasks-standard/src/test/scala/TaskRunnerFork.scala index b469dc38e..105f4a2de 100644 --- a/tasks-standard/src/test/scala/TaskRunnerFork.scala +++ b/tasks-standard/src/test/scala/TaskRunnerFork.scala @@ -31,8 +31,8 @@ object TaskRunnerForkTest extends Properties("TaskRunner Fork") { true } def runDoubleJoin(a: Int, b: Int, workers: Int): Unit = { - def inner(i: Int) = List.range(0, b).map(j => task(j).named(j.toString)).join - tryRun(List.range(0, a).map(inner).join, false, workers) + def inner = List.range(0, b).map(j => task(j).named(j.toString)).join + tryRun(List.range(0, a).map(_ => inner).join, false, workers) } property("fork and reduce") = forAll(TaskListGen, MaxWorkersGen) { (m: List[Int], workers: Int) => m.nonEmpty ==> { diff --git a/tasks-standard/src/test/scala/Test.scala b/tasks-standard/src/test/scala/Test.scala index 0574118ca..ab4fdb1b3 100644 --- a/tasks-standard/src/test/scala/Test.scala +++ b/tasks-standard/src/test/scala/Test.scala @@ -34,12 +34,12 @@ object Test extends std.TaskExtra { val d2 = t3(a, b2, c) mapR f val f2: Values => Task[Any] = { case (Value(aa), Value(bb), Value(cc)) => task(aa + " " + bb + " " + cc) - case x => d3 + case _ => d3 } lazy val d = t3(a, b, c) flatMapR f2 val f3: Values => Task[Any] = { case (Value(aa), Value(bb), Value(cc)) => task(aa + " " + bb + " " + cc) - case x => d2 + case _ => d2 } lazy val d3 = t3(a, b, c) flatMapR f3 From b4c5d9de31cf2d3a91cf309d7b84458c5ff35d9e Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 Dec 2017 16:07:40 +0000 Subject: [PATCH 013/356] Remove all warnings from protocolProj --- build.sbt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.sbt b/build.sbt index a54dd57a3..0ed479347 100644 --- a/build.sbt +++ b/build.sbt @@ -303,6 +303,8 @@ lazy val protocolProj = (project in file("protocol")) .enablePlugins(ContrabandPlugin, JsonCodecPlugin) .settings( testedBaseSettings, + scalacOptions -= "-Ywarn-unused", + scalacOptions += "-Xlint:-unused", name := "Protocol", libraryDependencies ++= Seq(sjsonNewScalaJson.value), managedSourceDirectories in Compile += From d99147d18c58997cbd6b6bbf17a142119921572a Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 Dec 2017 17:45:18 +0000 Subject: [PATCH 014/356] Remove all warnings from commandProj --- .../src/main/scala/sbt/BasicCommands.scala | 21 +++++++++++-------- main-command/src/main/scala/sbt/Watched.scala | 4 ++-- .../scala/sbt/internal/ConsoleChannel.scala | 4 ++-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index 584e1196e..cd095f0b5 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -94,10 +94,13 @@ object BasicCommands { } def completionsCommand: Command = - Command(CompletionsCommand, CompletionsBrief, CompletionsDetailed)(completionsParser)( + Command(CompletionsCommand, CompletionsBrief, CompletionsDetailed)(_ => completionsParser)( runCompletions(_)(_)) - def completionsParser(state: State): Parser[String] = { + @deprecated("No longer public", "1.1.1") + def completionsParser(state: State): Parser[String] = completionsParser + + private[this] def completionsParser: Parser[String] = { val notQuoted = (NotQuoted ~ any.*) map { case (nq, s) => nq ++ s } val quotedOrUnquotedSingleArgument = Space ~> (StringVerbatim | StringEscapable | notQuoted) token(quotedOrUnquotedSingleArgument ?? "" examples ("", " ")) @@ -175,19 +178,19 @@ object BasicCommands { } def reboot: Command = - Command(RebootCommand, Help.more(RebootCommand, RebootDetailed))(rebootOptionParser) { + Command(RebootCommand, Help.more(RebootCommand, RebootDetailed))(_ => rebootOptionParser) { case (s, (full, currentOnly)) => s.reboot(full, currentOnly) } @deprecated("Use rebootOptionParser", "1.1.0") - def rebootParser(s: State): Parser[Boolean] = - rebootOptionParser(s) map { case (full, currentOnly) => full } + def rebootParser(s: State): Parser[Boolean] = rebootOptionParser map { case (full, _) => full } - private[sbt] def rebootOptionParser(s: State): Parser[(Boolean, Boolean)] = - token( - Space ~> (("full" ^^^ ((true, false))) | - ("dev" ^^^ ((false, true))))) ?? ((false, false)) + private[sbt] def rebootOptionParser: Parser[(Boolean, Boolean)] = { + val fullOption = "full" ^^^ ((true, false)) + val devOption = "dev" ^^^ ((false, true)) + token(Space ~> (fullOption | devOption)) ?? ((false, false)) + } def call: Command = Command(ApplyCommand, Help.more(ApplyCommand, ApplyDetailed))(_ => callParser) { diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index fa1dee03f..248eedd5b 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -23,8 +23,8 @@ import scala.util.Properties trait Watched { - /** The files watched when an action is run with a preceeding ~ */ - def watchSources(s: State): Seq[Watched.WatchSource] = Nil + /** The files watched when an action is run with a proceeding ~ */ + def watchSources(@deprecated("unused", "") s: State): Seq[Watched.WatchSource] = Nil def terminateWatch(key: Int): Boolean = Watched.isEnter(key) /** diff --git a/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala b/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala index 1ac026e2d..a0356234e 100644 --- a/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala +++ b/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala @@ -50,7 +50,7 @@ private[sbt] final class ConsoleChannel(val name: String) extends CommandChannel case _ => val x = makeAskUserThread(e.state) askUserThread = Some(x) - x.start + x.start() } case e: ConsoleUnpromptEvent => e.lastSource match { @@ -70,7 +70,7 @@ private[sbt] final class ConsoleChannel(val name: String) extends CommandChannel def shutdown(): Unit = askUserThread match { case Some(x) if x.isAlive => - x.interrupt + x.interrupt() askUserThread = None case _ => () } From 5d99bea89fff44e0357036fe274404b9725d17e3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 Dec 2017 15:56:07 +0000 Subject: [PATCH 015/356] Remove all warnings from coreMacrosProj --- build.sbt | 2 +- .../main/scala/sbt/internal/util/appmacro/ContextUtil.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 0ed479347..516a4e2d0 100644 --- a/build.sbt +++ b/build.sbt @@ -357,7 +357,7 @@ lazy val commandProj = (project in file("main-command")) lazy val coreMacrosProj = (project in file("core-macros")) .dependsOn(collectionProj) .settings( - commonSettings, + baseSettings, name := "Core Macros", libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value, mimaSettings, diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala index b70461e53..7e627317d 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala @@ -33,9 +33,9 @@ object ContextUtil { f: (c.Expr[Any], c.Position) => c.Expr[T]): c.Expr[T] = { import c.universe._ c.macroApplication match { - case s @ Select(Apply(_, t :: Nil), tp) => f(c.Expr[Any](t), s.pos) - case a @ Apply(_, t :: Nil) => f(c.Expr[Any](t), a.pos) - case x => unexpectedTree(x) + case s @ Select(Apply(_, t :: Nil), _) => f(c.Expr[Any](t), s.pos) + case a @ Apply(_, t :: Nil) => f(c.Expr[Any](t), a.pos) + case x => unexpectedTree(x) } } From 7a8c89effc510ee216983091894073b0b063d631 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 12 Dec 2017 16:26:00 +0000 Subject: [PATCH 016/356] Update version to 1.1.0-SNAPSHOT .. & fix project/cross-plugins-defaults. --- build.sbt | 2 +- .../project/cross-plugins-defaults/build.sbt | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.sbt b/build.sbt index 3ebd1cbdf..ffe47d96f 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def buildLevelSettings: Seq[Setting[_]] = inThisBuild( Seq( organization := "org.scala-sbt", - version := "1.0.3-SNAPSHOT", + version := "1.1.0-SNAPSHOT", description := "sbt is an interactive build tool", bintrayOrganization := Some("sbt"), bintrayRepository := { diff --git a/sbt/src/sbt-test/project/cross-plugins-defaults/build.sbt b/sbt/src/sbt-test/project/cross-plugins-defaults/build.sbt index b1d361b5a..55b3bac97 100644 --- a/sbt/src/sbt-test/project/cross-plugins-defaults/build.sbt +++ b/sbt/src/sbt-test/project/cross-plugins-defaults/build.sbt @@ -1,4 +1,4 @@ -val baseSbt = "1.0" +val baseSbt = "1." val buildCrossList = List("2.10.6", "2.11.11", "2.12.2") scalaVersion in ThisBuild := "2.12.2" @@ -10,20 +10,20 @@ lazy val root = (project in file(".")) .settings( sbtPlugin := true, - TaskKey[Unit]("check") := mkCheck("2.12", "1.0").value, - TaskKey[Unit]("check2") := mkCheck("2.10", "0.13").value + TaskKey[Unit]("check") := mkCheck("2.12", "1.0", "1.").value, + TaskKey[Unit]("check2") := mkCheck("2.10", "0.13", "0.13").value ) lazy val app = (project in file("app")) -def mkCheck(scalaBinV: String, sbtBinVer: String) = Def task { +def mkCheck(scalaBinV: String, sbtBinVer: String, sbtVerPrefix: String) = Def task { val crossV = (sbtVersion in pluginCrossBuild).value val crossBinV = (sbtBinaryVersion in pluginCrossBuild).value val sv = projectID.value.extraAttributes("e:scalaVersion") assert(sbtVersion.value startsWith baseSbt, s"Wrong sbt version: ${sbtVersion.value}") assert(sv == scalaBinV, s"Wrong e:scalaVersion: $sv") assert(scalaBinaryVersion.value == scalaBinV, s"Wrong Scala binary version: ${scalaBinaryVersion.value}") - assert(crossV startsWith sbtBinVer, s"Wrong `sbtVersion in pluginCrossBuild`: $crossV") + assert(crossV startsWith sbtVerPrefix, s"Wrong `sbtVersion in pluginCrossBuild`: $crossV") val ur = update.value val cr = ur.configuration(Compile).get @@ -31,7 +31,7 @@ def mkCheck(scalaBinV: String, sbtBinVer: String) = Def task { val plugSv = mr.module.extraAttributes("scalaVersion") val plugSbtV = mr.module.extraAttributes("sbtVersion") assert(plugSv == scalaBinV, s"Wrong plugin scalaVersion: $plugSv") - assert(plugSbtV == sbtBinVer, s"Wrong plugin scalaVersion: $sbtBinVer") + assert(plugSbtV == sbtBinVer, s"Wrong plugin sbtVersion: $plugSbtV") // crossScalaVersions in app should not be affected, per se or after ^^ val appCrossScalaVersions = (crossScalaVersions in app).value.toList From 06b85919babefcab2e44afebd2c94bd16708199e Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 11 Dec 2017 02:53:59 -0500 Subject: [PATCH 017/356] Encode POSIX file path to URI using u3 (file:///) Ref https://github.com/sbt/io/pull/96 Under RFC 8089, both u1 and u3 are legal, but many of the other platforms expect traditional u3. This will increase the compatibility/usability of sbt server, for example to integrate with Vim. --- main-command/src/main/scala/sbt/internal/server/Server.scala | 2 +- main/src/main/scala/sbt/internal/CommandExchange.scala | 4 ++-- main/src/main/scala/sbt/internal/server/Definition.scala | 3 ++- .../scala/sbt/internal/server/LanguageServerReporter.scala | 5 +++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/main-command/src/main/scala/sbt/internal/server/Server.scala b/main-command/src/main/scala/sbt/internal/server/Server.scala index 0b8ee4b32..4fb8a7cb4 100644 --- a/main-command/src/main/scala/sbt/internal/server/Server.scala +++ b/main-command/src/main/scala/sbt/internal/server/Server.scala @@ -174,7 +174,7 @@ private[sbt] object Server { auth match { case _ if auth(ServerAuthentication.Token) => writeTokenfile() - PortFile(uri, Option(tokenfile.toString), Option(tokenfile.toURI.toString)) + PortFile(uri, Option(tokenfile.toString), Option(IO.toURI(tokenfile).toString)) case _ => PortFile(uri, None, None) } diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 32175d256..39259ee28 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -28,7 +28,7 @@ import scala.concurrent.Await import scala.concurrent.duration.Duration import scala.util.{ Success, Failure } import sbt.io.syntax._ -import sbt.io.Hash +import sbt.io.{ Hash, IO } import sbt.internal.server._ import sbt.internal.langserver.{ LogMessageParams, MessageType } import sbt.internal.util.{ StringEvent, ObjectEvent, MainAppender } @@ -135,7 +135,7 @@ private[sbt] final class CommandExchange { case Some(_) => // do nothing case _ => val portfile = (new File(".")).getAbsoluteFile / "project" / "target" / "active.json" - val h = Hash.halfHashString(portfile.toURI.toString) + val h = Hash.halfHashString(IO.toURI(portfile).toString) val tokenfile = BuildPaths.getGlobalBase(s) / "server" / h / "token.json" val socketfile = BuildPaths.getGlobalBase(s) / "server" / h / "sock" val pipeName = "sbt-server-" + h diff --git a/main/src/main/scala/sbt/internal/server/Definition.scala b/main/src/main/scala/sbt/internal/server/Definition.scala index 4459861db..0c39d15cd 100644 --- a/main/src/main/scala/sbt/internal/server/Definition.scala +++ b/main/src/main/scala/sbt/internal/server/Definition.scala @@ -9,6 +9,7 @@ package sbt package internal package server +import sbt.io.IO import sbt.internal.inc.MixedAnalyzingCompiler import sbt.internal.langserver.ErrorCodes import sbt.util.Logger @@ -297,7 +298,7 @@ private[sbt] object Definition { textProcessor.markPosition(classFile, sym).collect { case (file, line, from, to) => import sbt.internal.langserver.{ Location, Position, Range } - Location(file.toURI.toURL.toString, + Location(IO.toURI(file).toString, Range(Position(line, from), Position(line, to))) } } diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerReporter.scala b/main/src/main/scala/sbt/internal/server/LanguageServerReporter.scala index 90d947a3d..28551be7c 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerReporter.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerReporter.scala @@ -24,6 +24,7 @@ import sbt.internal.langserver.{ import sbt.internal.inc.JavaInterfaceUtil._ import scala.collection.mutable import scala.collection.JavaConverters._ +import sbt.io.IO /** * Defines a compiler reporter that uses event logging provided by a [[ManagedLogger]]. @@ -82,7 +83,7 @@ class LanguageServerReporter( import sbt.internal.langserver.codec.JsonProtocol._ val files = analysis.readSourceInfos.getAllSourceInfos.keySet.asScala files foreach { f => - val params = PublishDiagnosticsParams(f.toURI.toString, Vector()) + val params = PublishDiagnosticsParams(IO.toURI(f).toString, Vector()) exchange.notifyEvent("textDocument/publishDiagnostics", params) } } @@ -94,7 +95,7 @@ class LanguageServerReporter( problemsByFile.get(sourceFile) match { case Some(xs: List[Problem]) => val ds = toDiagnostics(xs) - val params = PublishDiagnosticsParams(sourceFile.toURI.toString, ds) + val params = PublishDiagnosticsParams(IO.toURI(sourceFile).toString, ds) exchange.notifyEvent("textDocument/publishDiagnostics", params) case _ => } From ab2dfc7ec5c8db5a3d8239898a6c3d60dede88d9 Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Tue, 14 Nov 2017 18:54:27 +0100 Subject: [PATCH 018/356] Restore test run/error This reverts commit 4ac231dd49f8884e0eac057639c2c60eef0342d2. --- sbt/src/sbt-test/run/error/test | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sbt/src/sbt-test/run/error/test b/sbt/src/sbt-test/run/error/test index a603016e6..d3599f40d 100644 --- a/sbt/src/sbt-test/run/error/test +++ b/sbt/src/sbt-test/run/error/test @@ -13,7 +13,6 @@ $ copy-file changes/ThreadRunError.scala src/main/scala/Run.scala $ copy-file changes/RunExplicitSuccess.scala src/main/scala/Run.scala > run -# https://github.com/sbt/sbt/issues/3543 -# # explicitly calling System.exit(1) should fail the 'run' task -# $ copy-file changes/RunExplicitFailure.scala src/main/scala/Run.scala -# -> run +# explicitly calling System.exit(1) should fail the 'run' task +$ copy-file changes/RunExplicitFailure.scala src/main/scala/Run.scala +-> run \ No newline at end of file From 843210598b60154a598ef32c393c38d77fb8cc75 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 14 Dec 2017 12:25:49 +0000 Subject: [PATCH 019/356] Exclude contraband generated files from diff by default In both local git diff and GitHub diff you can still see the diff if you want. This is just to remove the noise by default. --- .gitattributes | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitattributes b/.gitattributes index a5d9c6403..f3dbe80d8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,7 @@ # to native line endings on checkout. *.scala text *.java text + +# Exclude contraband generated files from diff (by default - you can see it if you want) +**/contraband-scala/**/* -diff merge=ours +**/contraband-scala/**/* linguist-generated=true From 2390fdfac63e24120f2358c80b9bd79bd14fd1e4 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 Dec 2017 18:13:10 +0000 Subject: [PATCH 020/356] Remove all warnings from mainSettingsProj --- main-settings/src/main/scala/sbt/Def.scala | 103 +++++++++++++----- .../src/main/scala/sbt/InputTask.scala | 9 +- main-settings/src/main/scala/sbt/Scope.scala | 50 +++++---- .../src/main/scala/sbt/Structure.scala | 15 ++- .../src/main/scala/sbt/std/InputWrapper.scala | 18 +-- .../src/main/scala/sbt/std/KeyMacro.scala | 4 +- .../main/scala/sbt/std/TaskLinterDSL.scala | 2 +- .../src/main/scala/sbt/std/TaskMacro.scala | 90 ++++++++------- .../src/test/scala/sbt/std/TaskPosSpec.scala | 91 +++++++--------- .../src/test/scala/sbt/std/TestUtil.scala | 4 +- main/src/main/scala/sbt/Project.scala | 5 +- main/src/main/scala/sbt/internal/Act.scala | 4 +- main/src/main/scala/sbt/internal/Load.scala | 1 - .../sbt/internal/server/SettingQuery.scala | 4 +- .../test/scala/sbt/internal/TestBuild.scala | 3 +- 15 files changed, 232 insertions(+), 171 deletions(-) diff --git a/main-settings/src/main/scala/sbt/Def.scala b/main-settings/src/main/scala/sbt/Def.scala index cfde273f9..d03b4ba10 100644 --- a/main-settings/src/main/scala/sbt/Def.scala +++ b/main-settings/src/main/scala/sbt/Def.scala @@ -38,30 +38,44 @@ object Def extends Init[Scope] with TaskMacroExtra { def showFullKey(keyNameColor: Option[String]): Show[ScopedKey[_]] = Show[ScopedKey[_]]((key: ScopedKey[_]) => displayFull(key, keyNameColor)) + @deprecated("Use showRelativeKey2 which doesn't take the unused multi param", "1.1.1") def showRelativeKey( current: ProjectRef, multi: Boolean, keyNameColor: Option[String] = None ): Show[ScopedKey[_]] = - Show[ScopedKey[_]]( - key => - Scope.display( - key.scope, - withColor(key.key.label, keyNameColor), - ref => displayRelative(current, multi, ref) - )) + showRelativeKey2(current, keyNameColor) - def showBuildRelativeKey( - currentBuild: URI, - multi: Boolean, - keyNameColor: Option[String] = None + def showRelativeKey2( + current: ProjectRef, + keyNameColor: Option[String] = None, ): Show[ScopedKey[_]] = Show[ScopedKey[_]]( key => Scope.display( key.scope, withColor(key.key.label, keyNameColor), - ref => displayBuildRelative(currentBuild, multi, ref) + ref => displayRelative2(current, ref) + )) + + @deprecated("Use showBuildRelativeKey2 which doesn't take the unused multi param", "1.1.1") + def showBuildRelativeKey( + currentBuild: URI, + multi: Boolean, + keyNameColor: Option[String] = None, + ): Show[ScopedKey[_]] = + showBuildRelativeKey2(currentBuild, keyNameColor) + + def showBuildRelativeKey2( + currentBuild: URI, + keyNameColor: Option[String] = None, + ): Show[ScopedKey[_]] = + Show[ScopedKey[_]]( + key => + Scope.display( + key.scope, + withColor(key.key.label, keyNameColor), + ref => displayBuildRelative(currentBuild, ref) )) /** @@ -71,8 +85,11 @@ object Def extends Init[Scope] with TaskMacroExtra { def displayRelativeReference(current: ProjectRef, project: Reference): String = displayRelative(current, project, false) - @deprecated("Use displayRelativeReference", "1.1.0") + @deprecated("Use displayRelative2 which doesn't take the unused multi param", "1.1.1") def displayRelative(current: ProjectRef, multi: Boolean, project: Reference): String = + displayRelative2(current, project) + + def displayRelative2(current: ProjectRef, project: Reference): String = displayRelative(current, project, true) /** @@ -91,7 +108,11 @@ object Def extends Init[Scope] with TaskMacroExtra { } } + @deprecated("Use variant without multi", "1.1.1") def displayBuildRelative(currentBuild: URI, multi: Boolean, project: Reference): String = + displayBuildRelative(currentBuild, project) + + def displayBuildRelative(currentBuild: URI, project: Reference): String = project match { case BuildRef(`currentBuild`) => "ThisBuild /" case ProjectRef(`currentBuild`, x) => x + " /" @@ -173,16 +194,31 @@ object Def extends Init[Scope] with TaskMacroExtra { // The following conversions enable the types Initialize[T], Initialize[Task[T]], and Task[T] to // be used in task and setting macros as inputs with an ultimate result of type T - implicit def macroValueI[T](in: Initialize[T]): MacroValue[T] = ??? - implicit def macroValueIT[T](in: Initialize[Task[T]]): MacroValue[T] = ??? - implicit def macroValueIInT[T](in: Initialize[InputTask[T]]): InputEvaluated[T] = ??? - implicit def taskMacroValueIT[T](in: Initialize[Task[T]]): MacroTaskValue[T] = ??? - implicit def macroPrevious[T](in: TaskKey[T]): MacroPrevious[T] = ??? + implicit def macroValueI[T](@deprecated("unused", "") in: Initialize[T]): MacroValue[T] = ??? - // The following conversions enable the types Parser[T], Initialize[Parser[T]], and Initialize[State => Parser[T]] to - // be used in the inputTask macro as an input with an ultimate result of type T - implicit def parserInitToInput[T](p: Initialize[Parser[T]]): ParserInput[T] = ??? - implicit def parserInitStateToInput[T](p: Initialize[State => Parser[T]]): ParserInput[T] = ??? + implicit def macroValueIT[T](@deprecated("unused", "") in: Initialize[Task[T]]): MacroValue[T] = + ??? + + implicit def macroValueIInT[T]( + @deprecated("unused", "") in: Initialize[InputTask[T]] + ): InputEvaluated[T] = ??? + + implicit def taskMacroValueIT[T]( + @deprecated("unused", "") in: Initialize[Task[T]] + ): MacroTaskValue[T] = ??? + + implicit def macroPrevious[T](@deprecated("unused", "") in: TaskKey[T]): MacroPrevious[T] = ??? + + // The following conversions enable the types Parser[T], Initialize[Parser[T]], and + // Initialize[State => Parser[T]] to be used in the inputTask macro as an input with an ultimate + // result of type T + implicit def parserInitToInput[T]( + @deprecated("unused", "") p: Initialize[Parser[T]] + ): ParserInput[T] = ??? + + implicit def parserInitStateToInput[T]( + @deprecated("unused", "") p: Initialize[State => Parser[T]] + ): ParserInput[T] = ??? def settingKey[T](description: String): SettingKey[T] = macro std.KeyMacro.settingKeyImpl[T] def taskKey[T](description: String): TaskKey[T] = macro std.KeyMacro.taskKeyImpl[T] @@ -190,27 +226,40 @@ object Def extends Init[Scope] with TaskMacroExtra { private[sbt] def dummy[T: Manifest](name: String, description: String): (TaskKey[T], Task[T]) = (TaskKey[T](name, description, DTask), dummyTask(name)) + private[sbt] def dummyTask[T](name: String): Task[T] = { import std.TaskExtra.{ task => newTask, _ } val base: Task[T] = newTask( sys.error("Dummy task '" + name + "' did not get converted to a full task.")) named name base.copy(info = base.info.set(isDummyTask, true)) } + private[sbt] def isDummy(t: Task[_]): Boolean = t.info.attributes.get(isDummyTask) getOrElse false + private[sbt] val isDummyTask = AttributeKey[Boolean]( "is-dummy-task", "Internal: used to identify dummy tasks. sbt injects values for these tasks at the start of task execution.", Invisible) + private[sbt] val (stateKey, dummyState) = dummy[State]("state", "Current build state.") + private[sbt] val (streamsManagerKey, dummyStreamsManager) = Def.dummy[std.Streams[ScopedKey[_]]]( "streams-manager", "Streams manager, which provides streams for different contexts.") } -// these need to be mixed into the sbt package object because the target doesn't involve Initialize or anything in Def + +// these need to be mixed into the sbt package object +// because the target doesn't involve Initialize or anything in Def trait TaskMacroExtra { - implicit def macroValueT[T](in: Task[T]): std.MacroValue[T] = ??? - implicit def macroValueIn[T](in: InputTask[T]): std.InputEvaluated[T] = ??? - implicit def parserToInput[T](in: Parser[T]): std.ParserInput[T] = ??? - implicit def stateParserToInput[T](in: State => Parser[T]): std.ParserInput[T] = ??? + implicit def macroValueT[T](@deprecated("unused", "") in: Task[T]): std.MacroValue[T] = ??? + + implicit def macroValueIn[T](@deprecated("unused", "") in: InputTask[T]): std.InputEvaluated[T] = + ??? + + implicit def parserToInput[T](@deprecated("unused", "") in: Parser[T]): std.ParserInput[T] = ??? + + implicit def stateParserToInput[T]( + @deprecated("unused", "") in: State => Parser[T] + ): std.ParserInput[T] = ??? } diff --git a/main-settings/src/main/scala/sbt/InputTask.scala b/main-settings/src/main/scala/sbt/InputTask.scala index fbdb4a221..2a722de45 100644 --- a/main-settings/src/main/scala/sbt/InputTask.scala +++ b/main-settings/src/main/scala/sbt/InputTask.scala @@ -49,8 +49,13 @@ object InputTask { ) } - implicit def inputTaskParsed[T](in: InputTask[T]): std.ParserInputTask[T] = ??? - implicit def inputTaskInitParsed[T](in: Initialize[InputTask[T]]): std.ParserInputTask[T] = ??? + implicit def inputTaskParsed[T]( + @deprecated("unused", "") in: InputTask[T] + ): std.ParserInputTask[T] = ??? + + implicit def inputTaskInitParsed[T]( + @deprecated("unused", "") in: Initialize[InputTask[T]] + ): std.ParserInputTask[T] = ??? def make[T](p: State => Parser[Task[T]]): InputTask[T] = new InputTask[T](p) diff --git a/main-settings/src/main/scala/sbt/Scope.scala b/main-settings/src/main/scala/sbt/Scope.scala index 0bd3e27c6..23955959b 100644 --- a/main-settings/src/main/scala/sbt/Scope.scala +++ b/main-settings/src/main/scala/sbt/Scope.scala @@ -201,23 +201,6 @@ object Scope { if (s == "") "" else s + " " - // sbt 0.12 style - def display012StyleMasked(scope: Scope, - sep: String, - showProject: Reference => String, - mask: ScopeMask): String = { - import scope.{ project, config, task, extra } - val configPrefix = config.foldStrict(displayConfigKey012Style, "*:", ".:") - val taskPrefix = task.foldStrict(_.label + "::", "", ".::") - val extras = extra.foldStrict(_.entries.map(_.toString).toList, Nil, Nil) - val postfix = if (extras.isEmpty) "" else extras.mkString("(", ", ", ")") - mask.concatShow(projectPrefix012Style(project, showProject012Style), - configPrefix, - taskPrefix, - sep, - postfix) - } - def equal(a: Scope, b: Scope, mask: ScopeMask): Boolean = (!mask.project || a.project == b.project) && (!mask.config || a.config == b.config) && @@ -241,7 +224,7 @@ object Scope { (parts.take(1) ++ parts.drop(1).map(_.capitalize)).mkString } - // *Inherit functions should be immediate delegates and not include argument itself. Transitivity will be provided by this method + @deprecated("Use variant without extraInherit", "1.1.1") def delegates[Proj]( refs: Seq[(ProjectRef, Proj)], configurations: Proj => Seq[ConfigKey], @@ -251,18 +234,47 @@ object Scope { configInherit: (ResolvedReference, ConfigKey) => Seq[ConfigKey], taskInherit: AttributeKey[_] => Seq[AttributeKey[_]], extraInherit: (ResolvedReference, AttributeMap) => Seq[AttributeMap] + ): Scope => Seq[Scope] = + delegates( + refs, + configurations, + resolve, + rootProject, + projectInherit, + configInherit, + taskInherit, + ) + + // *Inherit functions should be immediate delegates and not include argument itself. Transitivity will be provided by this method + def delegates[Proj]( + refs: Seq[(ProjectRef, Proj)], + configurations: Proj => Seq[ConfigKey], + resolve: Reference => ResolvedReference, + rootProject: URI => String, + projectInherit: ProjectRef => Seq[ProjectRef], + configInherit: (ResolvedReference, ConfigKey) => Seq[ConfigKey], + taskInherit: AttributeKey[_] => Seq[AttributeKey[_]], ): Scope => Seq[Scope] = { val index = delegates(refs, configurations, projectInherit, configInherit) scope => - indexedDelegates(resolve, index, rootProject, taskInherit, extraInherit)(scope) + indexedDelegates(resolve, index, rootProject, taskInherit)(scope) } + @deprecated("Use variant without extraInherit", "1.1.1") def indexedDelegates( resolve: Reference => ResolvedReference, index: DelegateIndex, rootProject: URI => String, taskInherit: AttributeKey[_] => Seq[AttributeKey[_]], extraInherit: (ResolvedReference, AttributeMap) => Seq[AttributeMap] + )(rawScope: Scope): Seq[Scope] = + indexedDelegates(resolve, index, rootProject, taskInherit)(rawScope) + + def indexedDelegates( + resolve: Reference => ResolvedReference, + index: DelegateIndex, + rootProject: URI => String, + taskInherit: AttributeKey[_] => Seq[AttributeKey[_]], )(rawScope: Scope): Seq[Scope] = { val scope = Scope.replaceThis(GlobalScope)(rawScope) diff --git a/main-settings/src/main/scala/sbt/Structure.scala b/main-settings/src/main/scala/sbt/Structure.scala index bcedc4171..3c1d705b4 100644 --- a/main-settings/src/main/scala/sbt/Structure.scala +++ b/main-settings/src/main/scala/sbt/Structure.scala @@ -379,8 +379,10 @@ object Scoped { sealed abstract class RichInitTaskBase[S, R[_]] { protected def onTask[T](f: Task[S] => Task[T]): Initialize[R[T]] - def flatMap[T](f: S => Task[T]): Initialize[R[T]] = flatMapR(f compose successM) - def map[T](f: S => T): Initialize[R[T]] = mapR(f compose successM) + def flatMap[T](f: S => Task[T]): Initialize[R[T]] = + onTask(_.result flatMap (f compose successM)) + + def map[T](f: S => T): Initialize[R[T]] = onTask(_.result map (f compose successM)) def andFinally(fin: => Unit): Initialize[R[S]] = onTask(_ andFinally fin) def doFinally(t: Task[Unit]): Initialize[R[S]] = onTask(_ doFinally t) @@ -393,22 +395,23 @@ object Scoped { @deprecated( "Use the `result` method to create a task that returns the full Result of this task. Then, call `flatMap` on the new task.", "0.13.0") - def flatMapR[T](f: Result[S] => Task[T]): Initialize[R[T]] = onTask(_ flatMapR f) + def flatMapR[T](f: Result[S] => Task[T]): Initialize[R[T]] = onTask(_.result flatMap f) @deprecated( "Use the `result` method to create a task that returns the full Result of this task. Then, call `map` on the new task.", "0.13.0") - def mapR[T](f: Result[S] => T): Initialize[R[T]] = onTask(_ mapR f) + def mapR[T](f: Result[S] => T): Initialize[R[T]] = onTask(_.result map f) @deprecated( "Use the `failure` method to create a task that returns Incomplete when this task fails and then call `flatMap` on the new task.", "0.13.0") - def flatFailure[T](f: Incomplete => Task[T]): Initialize[R[T]] = flatMapR(f compose failM) + def flatFailure[T](f: Incomplete => Task[T]): Initialize[R[T]] = + onTask(_.result flatMap (f compose failM)) @deprecated( "Use the `failure` method to create a task that returns Incomplete when this task fails and then call `map` on the new task.", "0.13.0") - def mapFailure[T](f: Incomplete => T): Initialize[R[T]] = mapR(f compose failM) + def mapFailure[T](f: Incomplete => T): Initialize[R[T]] = onTask(_.result map (f compose failM)) } type AnyInitTask = Initialize[Task[T]] forSome { type T } diff --git a/main-settings/src/main/scala/sbt/std/InputWrapper.scala b/main-settings/src/main/scala/sbt/std/InputWrapper.scala index b6dcc7b46..fb6b6bf70 100644 --- a/main-settings/src/main/scala/sbt/std/InputWrapper.scala +++ b/main-settings/src/main/scala/sbt/std/InputWrapper.scala @@ -31,27 +31,27 @@ object InputWrapper { @compileTimeOnly( "`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task.") - def wrapTask_\u2603\u2603[T](in: Any): T = implDetailError + def wrapTask_\u2603\u2603[T](@deprecated("unused", "") in: Any): T = implDetailError @compileTimeOnly( "`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.") - def wrapInit_\u2603\u2603[T](in: Any): T = implDetailError + def wrapInit_\u2603\u2603[T](@deprecated("unused", "") in: Any): T = implDetailError @compileTimeOnly( "`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task.") - def wrapInitTask_\u2603\u2603[T](in: Any): T = implDetailError + def wrapInitTask_\u2603\u2603[T](@deprecated("unused", "") in: Any): T = implDetailError @compileTimeOnly( "`value` can only be called on an input task within a task definition macro, such as := or Def.inputTask.") - def wrapInputTask_\u2603\u2603[T](in: Any): T = implDetailError + def wrapInputTask_\u2603\u2603[T](@deprecated("unused", "") in: Any): T = implDetailError @compileTimeOnly( "`value` can only be called on an input task within a task definition macro, such as := or Def.inputTask.") - def wrapInitInputTask_\u2603\u2603[T](in: Any): T = implDetailError + def wrapInitInputTask_\u2603\u2603[T](@deprecated("unused", "") in: Any): T = implDetailError @compileTimeOnly( "`previous` can only be called on a task within a task or input task definition macro, such as :=, +=, ++=, Def.task, or Def.inputTask.") - def wrapPrevious_\u2603\u2603[T](in: Any): T = implDetailError + def wrapPrevious_\u2603\u2603[T](@deprecated("unused", "") in: Any): T = implDetailError private[this] def implDetailError = sys.error("This method is an implementation detail and should not be referenced.") @@ -164,7 +164,7 @@ object InputWrapper { format: c.Expr[sjsonnew.JsonFormat[T]]): c.Expr[Option[T]] = { import c.universe._ c.macroApplication match { - case a @ Apply(Select(Apply(_, t :: Nil), tp), fmt) => + case a @ Apply(Select(Apply(_, t :: Nil), _), _) => if (t.tpe <:< c.weakTypeOf[TaskKey[T]]) { val tsTyped = c.Expr[TaskKey[T]](t) val newTree = c.universe.reify { Previous.runtime[T](tsTyped.splice)(format.splice) } @@ -224,12 +224,12 @@ object ParserInput { @compileTimeOnly( "`parsed` can only be used within an input task macro, such as := or Def.inputTask.") - def parser_\u2603\u2603[T](i: Any): T = + def parser_\u2603\u2603[T](@deprecated("unused", "") i: Any): T = sys.error("This method is an implementation detail and should not be referenced.") @compileTimeOnly( "`parsed` can only be used within an input task macro, such as := or Def.inputTask.") - def initParser_\u2603\u2603[T](i: Any): T = + def initParser_\u2603\u2603[T](@deprecated("unused", "") i: Any): T = sys.error("This method is an implementation detail and should not be referenced.") private[std] def wrap[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any], diff --git a/main-settings/src/main/scala/sbt/std/KeyMacro.scala b/main-settings/src/main/scala/sbt/std/KeyMacro.scala index aa08c14e6..697963ab0 100644 --- a/main-settings/src/main/scala/sbt/std/KeyMacro.scala +++ b/main-settings/src/main/scala/sbt/std/KeyMacro.scala @@ -61,10 +61,10 @@ private[sbt] object KeyMacro { n.decodedName.toString.trim // trim is not strictly correct, but macros don't expose the API necessary @tailrec def enclosingVal(trees: List[c.Tree]): String = { trees match { - case vd @ ValDef(_, name, _, _) :: ts => processName(name) + case ValDef(_, name, _, _) :: _ => processName(name) case (_: ApplyTree | _: Select | _: TypeApply) :: xs => enclosingVal(xs) // lazy val x: X = has this form for some reason (only when the explicit type is present, though) - case Block(_, _) :: DefDef(mods, name, _, _, _, _) :: xs if mods.hasFlag(Flag.LAZY) => + case Block(_, _) :: DefDef(mods, name, _, _, _, _) :: _ if mods.hasFlag(Flag.LAZY) => processName(name) case _ => c.error(c.enclosingPosition, invalidEnclosingTree(methodName.decodedName.toString)) diff --git a/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala b/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala index ec6b04288..8ae98e8de 100644 --- a/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala +++ b/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala @@ -73,7 +73,7 @@ abstract class BaseTaskLinterDSL extends LinterDSL { val (qualName, isSettingKey) = Option(qual.symbol) .map(sym => (sym.name.decodedName.toString, qual.tpe <:< typeOf[SettingKey[_]])) - .getOrElse((ap.pos.lineContent, false)) + .getOrElse((ap.pos.source.lineToString(ap.pos.line - 1), false)) if (!isSettingKey && !shouldIgnore && isTask(wrapperName, tpe.tpe, qual)) { if (insideIf && !isDynamicTask) { diff --git a/main-settings/src/main/scala/sbt/std/TaskMacro.scala b/main-settings/src/main/scala/sbt/std/TaskMacro.scala index efeff7877..85e19c3f6 100644 --- a/main-settings/src/main/scala/sbt/std/TaskMacro.scala +++ b/main-settings/src/main/scala/sbt/std/TaskMacro.scala @@ -130,37 +130,41 @@ object TaskMacro { // These macros are there just so we can fail old operators like `<<=` and provide useful migration information. def fakeSettingAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)( - app: c.Expr[Initialize[T]]): c.Expr[Setting[T]] = - ContextUtil.selectMacroImpl[Setting[T]](c) { (ts, pos) => - c.abort(pos, assignMigration) - } - def fakeSettingAppend1Position[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)( - v: c.Expr[Initialize[V]])(a: c.Expr[Append.Value[S, V]]): c.Expr[Setting[S]] = - ContextUtil.selectMacroImpl[Setting[S]](c) { (ts, pos) => - c.abort(pos, append1Migration) - } - def fakeSettingAppendNPosition[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)( - vs: c.Expr[Initialize[V]])(a: c.Expr[Append.Values[S, V]]): c.Expr[Setting[S]] = - ContextUtil.selectMacroImpl[Setting[S]](c) { (ts, pos) => - c.abort(pos, appendNMigration) - } - def fakeItaskAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)( - app: c.Expr[Initialize[Task[T]]]): c.Expr[Setting[Task[T]]] = - ContextUtil.selectMacroImpl[Setting[Task[T]]](c) { (ts, pos) => - c.abort(pos, assignMigration) - } - def fakeTaskAppend1Position[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)( - v: c.Expr[Initialize[Task[V]]])(a: c.Expr[Append.Value[S, V]]): c.Expr[Setting[Task[S]]] = - ContextUtil.selectMacroImpl[Setting[Task[S]]](c) { (ts, pos) => - c.abort(pos, append1Migration) - } - def fakeTaskAppendNPosition[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)( - vs: c.Expr[Initialize[Task[V]]])(a: c.Expr[Append.Values[S, V]]): c.Expr[Setting[Task[S]]] = - ContextUtil.selectMacroImpl[Setting[Task[S]]](c) { (ts, pos) => - c.abort(pos, appendNMigration) - } + @deprecated("unused", "") app: c.Expr[Initialize[T]] + ): c.Expr[Setting[T]] = + ContextUtil.selectMacroImpl[Setting[T]](c)((_, pos) => c.abort(pos, assignMigration)) - /* Implementations of <<= macro variations for tasks and settings. These just get the source position of the call site.*/ + def fakeSettingAppend1Position[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)( + @deprecated("unused", "") v: c.Expr[Initialize[V]])( + @deprecated("unused", "") a: c.Expr[Append.Value[S, V]] + ): c.Expr[Setting[S]] = + ContextUtil.selectMacroImpl[Setting[S]](c)((_, pos) => c.abort(pos, append1Migration)) + + def fakeSettingAppendNPosition[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)( + @deprecated("unused", "") vs: c.Expr[Initialize[V]])( + @deprecated("unused", "") a: c.Expr[Append.Values[S, V]] + ): c.Expr[Setting[S]] = + ContextUtil.selectMacroImpl[Setting[S]](c)((_, pos) => c.abort(pos, appendNMigration)) + + def fakeItaskAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)( + @deprecated("unused", "") app: c.Expr[Initialize[Task[T]]] + ): c.Expr[Setting[Task[T]]] = + ContextUtil.selectMacroImpl[Setting[Task[T]]](c)((_, pos) => c.abort(pos, assignMigration)) + + def fakeTaskAppend1Position[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)( + @deprecated("unused", "") v: c.Expr[Initialize[Task[V]]])( + @deprecated("unused", "") a: c.Expr[Append.Value[S, V]] + ): c.Expr[Setting[Task[S]]] = + ContextUtil.selectMacroImpl[Setting[Task[S]]](c)((_, pos) => c.abort(pos, append1Migration)) + + def fakeTaskAppendNPosition[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)( + @deprecated("unused", "") vs: c.Expr[Initialize[Task[V]]])( + @deprecated("unused", "") a: c.Expr[Append.Values[S, V]] + ): c.Expr[Setting[Task[S]]] = + ContextUtil.selectMacroImpl[Setting[Task[S]]](c)((_, pos) => c.abort(pos, appendNMigration)) + + // Implementations of <<= macro variations for tasks and settings. + // These just get the source position of the call site. def itaskAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)( app: c.Expr[Initialize[Task[T]]]): c.Expr[Setting[Task[T]]] = @@ -221,7 +225,7 @@ object TaskMacro { if typeArgs.nonEmpty && (typeArgs.head weak_<:< c.weakTypeOf[Task[_]]) && (tpe weak_<:< c.weakTypeOf[Initialize[_]]) => c.macroApplication match { - case Apply(Apply(TypeApply(Select(preT, nmeT), targs), _), _) => + case Apply(Apply(TypeApply(Select(preT, _), _), _), _) => val tree = Apply( TypeApply(Select(preT, TermName("+=").encodedName), TypeTree(typeArgs.head) :: Nil), Select(v.tree, TermName("taskValue").encodedName) :: Nil) @@ -287,10 +291,14 @@ object TaskMacro { newName: String): c.Tree = { import c.universe._ c.macroApplication match { - case Apply(Apply(TypeApply(Select(preT, nmeT), targs), _), _) => - Apply(Apply(TypeApply(Select(preT, TermName(newName).encodedName), targs), - init :: sourcePosition(c).tree :: Nil), - append :: Nil) + case Apply(Apply(TypeApply(Select(preT, _), targs), _), _) => + Apply( + Apply( + TypeApply(Select(preT, TermName(newName).encodedName), targs), + init :: sourcePosition(c).tree :: Nil + ), + append :: Nil + ) case x => ContextUtil.unexpectedTree(x) } } @@ -299,10 +307,14 @@ object TaskMacro { newName: String): c.Tree = { import c.universe._ c.macroApplication match { - case Apply(Apply(TypeApply(Select(preT, nmeT), targs), _), r) => - Apply(Apply(TypeApply(Select(preT, TermName(newName).encodedName), targs), - init :: sourcePosition(c).tree :: Nil), - r) + case Apply(Apply(TypeApply(Select(preT, _), targs), _), _) => + Apply( + Apply( + TypeApply(Select(preT, TermName(newName).encodedName), targs), + init :: sourcePosition(c).tree :: Nil + ), + remove :: Nil + ) case x => ContextUtil.unexpectedTree(x) } } diff --git a/main-settings/src/test/scala/sbt/std/TaskPosSpec.scala b/main-settings/src/test/scala/sbt/std/TaskPosSpec.scala index 68d8f9cdf..a7df2aba8 100644 --- a/main-settings/src/test/scala/sbt/std/TaskPosSpec.scala +++ b/main-settings/src/test/scala/sbt/std/TaskPosSpec.scala @@ -10,12 +10,11 @@ package sbt.std class TaskPosSpec { // Dynamic tasks can have task invocations inside if branches locally { - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = taskKey[String]("") val bar = taskKey[String]("") - var condition = true - val baz = Def.taskDyn[String] { + val condition = true + Def.taskDyn[String] { if (condition) foo else bar } @@ -23,23 +22,21 @@ class TaskPosSpec { // Dynamic settings can have setting invocations inside if branches locally { - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = settingKey[String]("") val bar = settingKey[String]("") - var condition = true - val baz = Def.settingDyn[String] { + val condition = true + Def.settingDyn[String] { if (condition) foo else bar } } locally { - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = taskKey[String]("") - var condition = true - val baz = Def.task[String] { + val condition = true + Def.task[String] { val fooAnon = () => foo.value: @sbtUnchecked if (condition) fooAnon() else fooAnon() @@ -47,11 +44,10 @@ class TaskPosSpec { } locally { - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = taskKey[String]("") - var condition = true - val baz = Def.task[String] { + val condition = true + Def.task[String] { val fooAnon = () => (foo.value: @sbtUnchecked) + "" if (condition) fooAnon() else fooAnon() @@ -59,12 +55,11 @@ class TaskPosSpec { } locally { - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = taskKey[String]("") val bar = taskKey[String]("") - var condition = true - val baz = Def.task[String] { + val condition = true + Def.task[String] { if (condition) foo.value: @sbtUnchecked else bar.value: @sbtUnchecked } @@ -72,11 +67,10 @@ class TaskPosSpec { locally { // This is fix 1 for appearance of tasks inside anons - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = taskKey[String]("") - var condition = true - val baz = Def.task[String] { + val condition = true + Def.task[String] { val fooResult = foo.value val anon = () => fooResult + " " if (condition) anon() @@ -86,11 +80,10 @@ class TaskPosSpec { locally { // This is fix 2 for appearance of tasks inside anons - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = taskKey[String]("") - var condition = true - val baz = Def.taskDyn[String] { + val condition = true + Def.taskDyn[String] { val anon1 = (value: String) => value + " " if (condition) { Def.task(anon1(foo.value)) @@ -100,31 +93,27 @@ class TaskPosSpec { locally { // missing .value error should not happen inside task dyn - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = taskKey[String]("") - val baz = Def.taskDyn[String] { + Def.taskDyn[String] { foo } } locally { - // missing .value error should not happen inside task dyn - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = taskKey[String]("") val avoidDCE = "" - val baz = Def.task[String] { - foo: @sbtUnchecked + Def.task[String] { + val _ = foo: @sbtUnchecked avoidDCE } } locally { - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = taskKey[String]("") - val baz = Def.task[String] { + Def.task[String] { def inner(s: KeyedInitialize[_]) = println(s) inner(foo) "" @@ -133,11 +122,10 @@ class TaskPosSpec { locally { // In theory, this should be reported, but missing .value analysis is dumb at the cost of speed - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = taskKey[String]("") def avoidDCE = { println(""); "" } - val baz = Def.task[String] { + Def.task[String] { val (_, _) = "" match { case _ => (foo, 1 + 2) } @@ -146,15 +134,14 @@ class TaskPosSpec { } locally { - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = taskKey[String]("") - def avoidDCE = { println(""); "" } - val baz = Def.task[String] { + def avoidDCE(x: TaskKey[String]) = x.toString + Def.task[String] { val hehe = foo // We do not detect `hehe` because guessing that the user did the wrong thing would require // us to run the unused name traverser defined in Typer (and hence proxy it from context util) - avoidDCE + avoidDCE(hehe) } } @@ -168,11 +155,10 @@ class TaskPosSpec { } locally { - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = settingKey[String]("") val condition = true - val baz = Def.task[String] { + Def.task[String] { // settings can be evaluated in a condition if (condition) foo.value else "..." @@ -180,10 +166,9 @@ class TaskPosSpec { } locally { - import sbt._ - import sbt.Def._ + import sbt._, Def._ val foo = settingKey[String]("") - val baz = Def.task[Seq[String]] { + Def.task[Seq[String]] { (1 to 10).map(_ => foo.value) } } diff --git a/main-settings/src/test/scala/sbt/std/TestUtil.scala b/main-settings/src/test/scala/sbt/std/TestUtil.scala index ed6f15530..43beb90fb 100644 --- a/main-settings/src/test/scala/sbt/std/TestUtil.scala +++ b/main-settings/src/test/scala/sbt/std/TestUtil.scala @@ -7,11 +7,9 @@ package sbt.std -import scala.reflect._ +import scala.tools.reflect.ToolBox object TestUtil { - import tools.reflect.ToolBox - def eval(code: String, compileOptions: String = ""): Any = { val tb = mkToolbox(compileOptions) tb.eval(tb.parse(code)) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 680bba811..0282184be 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -284,15 +284,14 @@ object Project extends ProjectExtra { structure: BuildStructure, keyNameColor: Option[String] = None ): Show[ScopedKey[_]] = - Def.showRelativeKey(session.current, structure.allProjects.size > 1, keyNameColor) + Def.showRelativeKey2(session.current, keyNameColor) def showLoadingKey( loaded: LoadedBuild, keyNameColor: Option[String] = None ): Show[ScopedKey[_]] = - Def.showRelativeKey( + Def.showRelativeKey2( ProjectRef(loaded.root, loaded.units(loaded.root).rootProjects.head), - loaded.allProjectRefs.size > 1, keyNameColor ) diff --git a/main/src/main/scala/sbt/internal/Act.scala b/main/src/main/scala/sbt/internal/Act.scala index 91051f3ec..2ab067db0 100644 --- a/main/src/main/scala/sbt/internal/Act.scala +++ b/main/src/main/scala/sbt/internal/Act.scala @@ -8,7 +8,7 @@ package sbt package internal -import Def.{ showRelativeKey, ScopedKey } +import Def.{ showRelativeKey2, ScopedKey } import Keys.sessionSettings import sbt.internal.util.complete.{ DefaultParsers, Parser } import Aggregation.{ KeyValue, Values } @@ -56,7 +56,7 @@ object Act { keyMap: Map[String, AttributeKey[_]], data: Settings[Scope]): Parser[ParsedKey] = scopedKeyFull(index, current, defaultConfigs, keyMap) flatMap { choices => - select(choices, data)(showRelativeKey(current, index.buildURIs.size > 1)) + select(choices, data)(showRelativeKey2(current)) } def scopedKeyFull(index: KeyIndex, diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index c6264f671..c87d583ab 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -208,7 +208,6 @@ private[sbt] object Load { project => projectInherit(lb, project), (project, config) => configInherit(lb, project, config, rootProject), task => task.extend, - (project, extra) => Nil ) } diff --git a/main/src/main/scala/sbt/internal/server/SettingQuery.scala b/main/src/main/scala/sbt/internal/server/SettingQuery.scala index 25dd1b66a..fa106d23e 100644 --- a/main/src/main/scala/sbt/internal/server/SettingQuery.scala +++ b/main/src/main/scala/sbt/internal/server/SettingQuery.scala @@ -21,7 +21,7 @@ import sjsonnew.support.scalajson.unsafe._ object SettingQuery { import sbt.internal.util.{ AttributeKey, Settings } import sbt.internal.util.complete.{ DefaultParsers, Parser }, DefaultParsers._ - import sbt.Def.{ showBuildRelativeKey, ScopedKey } + import sbt.Def.{ showBuildRelativeKey2, ScopedKey } // Similar to Act.ParsedAxis / Act.projectRef / Act.resolveProject except you can't omit the project reference @@ -67,7 +67,7 @@ object SettingQuery { data: Settings[Scope] ): Parser[ParsedKey] = scopedKeyFull(index, currentBuild, defaultConfigs, keyMap) flatMap { choices => - Act.select(choices, data)(showBuildRelativeKey(currentBuild, index.buildURIs.size > 1)) + Act.select(choices, data)(showBuildRelativeKey2(currentBuild)) } def scopedKey( diff --git a/main/src/test/scala/sbt/internal/TestBuild.scala b/main/src/test/scala/sbt/internal/TestBuild.scala index 7b8a70db5..e7bb2ead0 100644 --- a/main/src/test/scala/sbt/internal/TestBuild.scala +++ b/main/src/test/scala/sbt/internal/TestBuild.scala @@ -142,7 +142,6 @@ abstract class TestBuild { inheritProject, inheritConfig, inheritTask, - (ref, mp) => Nil ) lazy val allFullScopes: Seq[Scope] = for { @@ -213,7 +212,7 @@ abstract class TestBuild { } def structure(env: Env, settings: Seq[Setting[_]], current: ProjectRef): Structure = { - implicit val display = Def.showRelativeKey(current, env.allProjects.size > 1) + implicit val display = Def.showRelativeKey2(current) if (settings.isEmpty) { try { sys.error("settings is empty") From 072366d48e6a896e9f6cdf11f19dd18e0093be0f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 Dec 2017 17:48:03 +0000 Subject: [PATCH 021/356] Remove all warnings from testingProj --- testing/src/main/scala/sbt/TestFramework.scala | 4 ++-- testing/src/main/scala/sbt/TestReportListener.scala | 2 +- testing/src/main/scala/sbt/internal/testing/TestLogger.scala | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/testing/src/main/scala/sbt/TestFramework.scala b/testing/src/main/scala/sbt/TestFramework.scala index ffd7ef36d..95fa55788 100644 --- a/testing/src/main/scala/sbt/TestFramework.scala +++ b/testing/src/main/scala/sbt/TestFramework.scala @@ -52,8 +52,8 @@ final class TestFramework(val implClassNames: String*) extends Serializable { case oldFramework: OldFramework => new FrameworkWrapper(oldFramework) }) } catch { - case e: ClassNotFoundException => - log.debug("Framework implementation '" + head + "' not present."); + case _: ClassNotFoundException => + log.debug("Framework implementation '" + head + "' not present.") createFramework(loader, log, tail) } case Nil => diff --git a/testing/src/main/scala/sbt/TestReportListener.scala b/testing/src/main/scala/sbt/TestReportListener.scala index 090ddc7ef..12566c707 100644 --- a/testing/src/main/scala/sbt/TestReportListener.scala +++ b/testing/src/main/scala/sbt/TestReportListener.scala @@ -25,7 +25,7 @@ trait TestReportListener { def endGroup(name: String, result: TestResult): Unit /** Used by the test framework for logging test results */ - def contentLogger(test: TestDefinition): Option[ContentLogger] = None + def contentLogger(@deprecated("unused", "") test: TestDefinition): Option[ContentLogger] = None } diff --git a/testing/src/main/scala/sbt/internal/testing/TestLogger.scala b/testing/src/main/scala/sbt/internal/testing/TestLogger.scala index 1cd5ff4b4..6c49f793a 100644 --- a/testing/src/main/scala/sbt/internal/testing/TestLogger.scala +++ b/testing/src/main/scala/sbt/internal/testing/TestLogger.scala @@ -9,7 +9,7 @@ package sbt package internal.testing import testing.{ Logger => TLogger } -import sbt.internal.util.{ ManagedLogger, BufferedAppender } +import sbt.internal.util.{ BufferedAppender, ConsoleAppender, ManagedLogger } import sbt.util.{ Level, LogExchange, ShowLines } import sbt.protocol.testing._ import java.util.concurrent.atomic.AtomicInteger @@ -89,7 +89,7 @@ object TestLogger { def debug(s: String) = log(Level.Debug, TestStringEvent(s)) def trace(t: Throwable) = logger.trace(t) private def log(level: Level.Value, event: TestStringEvent) = logger.logEvent(level, event) - def ansiCodesSupported() = logger.ansiCodesSupported + def ansiCodesSupported() = ConsoleAppender.formatEnabledInEnv } private[sbt] def toTestItemEvent(event: TestEvent): TestItemEvent = From f50260218d046bcce7db02677c0fceda04470b01 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 5 Dec 2017 16:16:26 +0000 Subject: [PATCH 022/356] Remove all warnings from actionsProj --- build.sbt | 6 ++ main-actions/src/main/scala/sbt/Doc.scala | 100 +++++------------- .../src/main/scala/sbt/ForkTests.scala | 3 +- main-actions/src/main/scala/sbt/Package.scala | 16 ++- .../src/main/scala/sbt/RawCompileLike.scala | 11 +- main-actions/src/main/scala/sbt/Sync.scala | 26 +++-- .../src/main/scala/sbt/TestResultLogger.scala | 11 +- main-actions/src/main/scala/sbt/Tests.scala | 21 +++- main/src/main/scala/sbt/Defaults.scala | 2 +- 9 files changed, 91 insertions(+), 105 deletions(-) diff --git a/build.sbt b/build.sbt index 516a4e2d0..6ef429611 100644 --- a/build.sbt +++ b/build.sbt @@ -285,6 +285,12 @@ lazy val actionsProj = (project in file("main-actions")) name := "Actions", libraryDependencies += sjsonNewScalaJson.value, mimaSettings, + mimaBinaryIssueFilters ++= Seq( + // Removed unused private[sbt] nested class + exclude[MissingClassProblem]("sbt.Doc$Scaladoc"), + // Removed no longer used private[sbt] method + exclude[DirectMissingMethodProblem]("sbt.Doc.generate"), + ), ) .configure( addSbtIO, diff --git a/main-actions/src/main/scala/sbt/Doc.scala b/main-actions/src/main/scala/sbt/Doc.scala index db8a70db9..12df12c9e 100644 --- a/main-actions/src/main/scala/sbt/Doc.scala +++ b/main-actions/src/main/scala/sbt/Doc.scala @@ -10,10 +10,6 @@ package sbt import java.io.File import sbt.internal.inc.AnalyzingCompiler -import Predef.{ conforms => _, _ } -import sbt.io.syntax._ -import sbt.io.IO - import sbt.util.CacheStoreFactory import xsbti.Reporter import xsbti.compile.JavaTools @@ -23,10 +19,12 @@ import sbt.internal.util.ManagedLogger object Doc { import RawCompileLike._ + def scaladoc(label: String, cacheStoreFactory: CacheStoreFactory, compiler: AnalyzingCompiler): Gen = scaladoc(label, cacheStoreFactory, compiler, Seq()) + def scaladoc(label: String, cacheStoreFactory: CacheStoreFactory, compiler: AnalyzingCompiler, @@ -34,82 +32,32 @@ object Doc { cached(cacheStoreFactory, fileInputOptions, prepare(label + " Scala API documentation", compiler.doc)) - def javadoc(label: String, - cacheStoreFactory: CacheStoreFactory, - doc: JavaTools, - log: Logger, - reporter: Reporter): Gen = - javadoc(label, cacheStoreFactory, doc, log, reporter, Seq()) - def javadoc(label: String, - cacheStoreFactory: CacheStoreFactory, - doc: JavaTools, - log: Logger, - reporter: Reporter, - fileInputOptions: Seq[String]): Gen = - cached( - cacheStoreFactory, - fileInputOptions, - prepare( - label + " Java API documentation", - filterSources( - javaSourcesOnly, - (sources: Seq[File], - classpath: Seq[File], - outputDirectory: File, - options: Seq[String], - maxErrors: Int, - log: Logger) => { - // doc.doc - ??? - } - ) - ) - ) + @deprecated("Going away", "1.1.1") + def javadoc( + label: String, + cacheStoreFactory: CacheStoreFactory, + doc: JavaTools, + log: Logger, + reporter: Reporter, + ): Gen = ??? + + @deprecated("Going away", "1.1.1") + def javadoc( + label: String, + cacheStoreFactory: CacheStoreFactory, + doc: JavaTools, + log: Logger, + reporter: Reporter, + fileInputOptions: Seq[String], + ): Gen = ??? + + @deprecated("Going away", "1.1.1") val javaSourcesOnly: File => Boolean = _.getName.endsWith(".java") - - private[sbt] final class Scaladoc(maximumErrors: Int, compiler: AnalyzingCompiler) extends Doc { - def apply(label: String, - sources: Seq[File], - classpath: Seq[File], - outputDirectory: File, - options: Seq[String], - log: ManagedLogger): Unit = { - generate("Scala", - label, - compiler.doc, - sources, - classpath, - outputDirectory, - options, - maximumErrors, - log) - } - } } +@deprecated("Going away", "1.1.1") sealed trait Doc { + @deprecated("Going away", "1.1.1") type Gen = (Seq[File], Seq[File], File, Seq[String], Int, ManagedLogger) => Unit - - private[sbt] final def generate(variant: String, - label: String, - docf: Gen, - sources: Seq[File], - classpath: Seq[File], - outputDirectory: File, - options: Seq[String], - maxErrors: Int, - log: ManagedLogger): Unit = { - val logSnip = variant + " API documentation" - if (sources.isEmpty) - log.info("No sources available, skipping " + logSnip + "...") - else { - log.info( - "Generating " + logSnip + " for " + label + " sources to " + outputDirectory.absolutePath + "...") - IO.delete(outputDirectory) - IO.createDirectory(outputDirectory) - docf(sources, classpath, outputDirectory, options, maxErrors, log) - log.info(logSnip + " generation successful.") - } - } } diff --git a/main-actions/src/main/scala/sbt/ForkTests.scala b/main-actions/src/main/scala/sbt/ForkTests.scala index 5beec08d3..c6eb96a1e 100755 --- a/main-actions/src/main/scala/sbt/ForkTests.scala +++ b/main-actions/src/main/scala/sbt/ForkTests.scala @@ -17,6 +17,7 @@ import sbt.io.IO import sbt.util.Logger import sbt.ConcurrentRestrictions.Tag import sbt.protocol.testing._ +import sbt.internal.util.ConsoleAppender private[sbt] object ForkTests { def apply(runners: Map[TestFramework, Runner], @@ -78,7 +79,7 @@ private[sbt] object ForkTests { val is = new ObjectInputStream(socket.getInputStream) try { - val config = new ForkConfiguration(log.ansiCodesSupported, parallel) + val config = new ForkConfiguration(ConsoleAppender.formatEnabledInEnv, parallel) os.writeObject(config) val taskdefs = opts.tests.map( diff --git a/main-actions/src/main/scala/sbt/Package.scala b/main-actions/src/main/scala/sbt/Package.scala index 76333963e..3297d3b4a 100644 --- a/main-actions/src/main/scala/sbt/Package.scala +++ b/main-actions/src/main/scala/sbt/Package.scala @@ -100,10 +100,18 @@ object Package { org: String, orgName: String): PackageOption = { import Attributes.Name._ - val attribKeys = Seq(IMPLEMENTATION_TITLE, - IMPLEMENTATION_VERSION, - IMPLEMENTATION_VENDOR, - IMPLEMENTATION_VENDOR_ID) + + // The ones in Attributes.Name are deprecated saying: + // "Extension mechanism will be removed in a future release. Use class path instead." + val IMPLEMENTATION_VENDOR_ID = new Attributes.Name("Implementation-Vendor-Id") + val IMPLEMENTATION_URL = new Attributes.Name("Implementation-URL") + + val attribKeys = Seq( + IMPLEMENTATION_TITLE, + IMPLEMENTATION_VERSION, + IMPLEMENTATION_VENDOR, + IMPLEMENTATION_VENDOR_ID, + ) val attribVals = Seq(name, version, orgName, org) ManifestAttributes((attribKeys zip attribVals) ++ { homepage map (h => (IMPLEMENTATION_URL, h.toString)) diff --git a/main-actions/src/main/scala/sbt/RawCompileLike.scala b/main-actions/src/main/scala/sbt/RawCompileLike.scala index f06e7e3a8..f8b85e25c 100644 --- a/main-actions/src/main/scala/sbt/RawCompileLike.scala +++ b/main-actions/src/main/scala/sbt/RawCompileLike.scala @@ -7,6 +7,7 @@ package sbt +import scala.annotation.tailrec import java.io.File import sbt.internal.inc.{ RawCompiler, ScalaInstance } @@ -30,7 +31,7 @@ object RawCompileLike { type Gen = (Seq[File], Seq[File], File, Seq[String], Int, ManagedLogger) => Unit private def optionFiles(options: Seq[String], fileInputOpts: Seq[String]): List[File] = { - @annotation.tailrec + @tailrec def loop(opt: List[String], result: List[File]): List[File] = { opt.dropWhile(!fileInputOpts.contains(_)) match { case List(_, fileOpt, tail @ _*) => { @@ -46,6 +47,7 @@ object RawCompileLike { def cached(cacheStoreFactory: CacheStoreFactory, doCompile: Gen): Gen = cached(cacheStoreFactory, Seq(), doCompile) + def cached(cacheStoreFactory: CacheStoreFactory, fileInputOpts: Seq[String], doCompile: Gen): Gen = @@ -67,6 +69,7 @@ object RawCompileLike { } cachedComp(inputs)(exists(outputDirectory.allPaths.get.toSet)) } + def prepare(description: String, doCompile: Gen): Gen = (sources, classpath, outputDirectory, options, maxErrors, log) => { if (sources.isEmpty) @@ -79,20 +82,22 @@ object RawCompileLike { log.info(description.capitalize + " successful.") } } + def filterSources(f: File => Boolean, doCompile: Gen): Gen = (sources, classpath, outputDirectory, options, maxErrors, log) => doCompile(sources filter f, classpath, outputDirectory, options, maxErrors, log) def rawCompile(instance: ScalaInstance, cpOptions: ClasspathOptions): Gen = - (sources, classpath, outputDirectory, options, maxErrors, log) => { + (sources, classpath, outputDirectory, options, _, log) => { val compiler = new RawCompiler(instance, cpOptions, log) compiler(sources, classpath, outputDirectory, options) } + def compile(label: String, cacheStoreFactory: CacheStoreFactory, instance: ScalaInstance, cpOptions: ClasspathOptions): Gen = cached(cacheStoreFactory, prepare(label + " sources", rawCompile(instance, cpOptions))) - val nop: Gen = (sources, classpath, outputDirectory, options, maxErrors, log) => () + val nop: Gen = (_, _, _, _, _, _) => () } diff --git a/main-actions/src/main/scala/sbt/Sync.scala b/main-actions/src/main/scala/sbt/Sync.scala index ec2eb73e3..b5024f2ae 100644 --- a/main-actions/src/main/scala/sbt/Sync.scala +++ b/main-actions/src/main/scala/sbt/Sync.scala @@ -30,10 +30,18 @@ import sjsonnew.{ Builder, JsonFormat, Unbuilder, deserializationError } * It is safe to use for its intended purpose: copying resources to a class output directory. */ object Sync { - def apply(store: CacheStore, - inStyle: FileInfo.Style = FileInfo.lastModified, - outStyle: FileInfo.Style = FileInfo.exists) - : Traversable[(File, File)] => Relation[File, File] = + @deprecated("Use sync, which doesn't take the unused outStyle param", "1.1.1") + def apply( + store: CacheStore, + inStyle: FileInfo.Style = FileInfo.lastModified, + outStyle: FileInfo.Style = FileInfo.exists, + ): Traversable[(File, File)] => Relation[File, File] = + sync(store, inStyle) + + def sync( + store: CacheStore, + inStyle: FileInfo.Style = FileInfo.lastModified, + ): Traversable[(File, File)] => Relation[File, File] = mappings => { val relation = Relation.empty ++ mappings noDuplicateTargets(relation) @@ -70,13 +78,9 @@ object Sync { } def noDuplicateTargets(relation: Relation[File, File]): Unit = { - val dups = relation.reverseMap.filter { - case (_, srcs) => - srcs.size >= 2 && srcs.exists(!_.isDirectory) - } map { - case (target, srcs) => - "\n\t" + target + "\nfrom\n\t" + srcs.mkString("\n\t\t") - } + val dups = relation.reverseMap + .filter { case (_, srcs) => srcs.size >= 2 && srcs.exists(!_.isDirectory) } + .map { case (target, srcs) => "\n\t" + target + "\nfrom\n\t" + srcs.mkString("\n\t\t") } if (dups.nonEmpty) sys.error("Duplicate mappings:" + dups.mkString) } diff --git a/main-actions/src/main/scala/sbt/TestResultLogger.scala b/main-actions/src/main/scala/sbt/TestResultLogger.scala index 01cc9e03f..8248946b3 100644 --- a/main-actions/src/main/scala/sbt/TestResultLogger.scala +++ b/main-actions/src/main/scala/sbt/TestResultLogger.scala @@ -133,17 +133,20 @@ object TestResultLogger { failuresCount, ignoredCount, canceledCount, - pendingCount) = + pendingCount, + ) = results.events.foldLeft((0, 0, 0, 0, 0, 0, 0)) { - case ((skippedAcc, errorAcc, passedAcc, failureAcc, ignoredAcc, canceledAcc, pendingAcc), - (name @ _, testEvent)) => + case (acc, (_, testEvent)) => + val (skippedAcc, errorAcc, passedAcc, failureAcc, ignoredAcc, canceledAcc, pendingAcc) = + acc (skippedAcc + testEvent.skippedCount, errorAcc + testEvent.errorCount, passedAcc + testEvent.passedCount, failureAcc + testEvent.failureCount, ignoredAcc + testEvent.ignoredCount, canceledAcc + testEvent.canceledCount, - pendingAcc + testEvent.pendingCount) + pendingAcc + testEvent.pendingCount, + ) } val totalCount = failuresCount + errorsCount + skippedCount + passedCount val base = diff --git a/main-actions/src/main/scala/sbt/Tests.scala b/main-actions/src/main/scala/sbt/Tests.scala index bd69e4c30..6885a0dc5 100644 --- a/main-actions/src/main/scala/sbt/Tests.scala +++ b/main-actions/src/main/scala/sbt/Tests.scala @@ -34,6 +34,7 @@ import sbt.util.Logger import sbt.protocol.testing.TestResult sealed trait TestOption + object Tests { /** @@ -227,7 +228,7 @@ object Tests { if (config.parallel) makeParallel(loader, runnables, setupTasks, config.tags) //.toSeq.join else - makeSerial(loader, runnables, setupTasks, config.tags) + makeSerial(loader, runnables, setupTasks) val taggedMainTasks = mainTasks.tagw(config.tags: _*) taggedMainTasks map processResults flatMap { results => val cleanupTasks = fj(partApp(userCleanup) :+ frameworkCleanup(results.overall)) @@ -294,10 +295,20 @@ object Tests { } } - def makeSerial(loader: ClassLoader, - runnables: Seq[TestRunnable], - setupTasks: Task[Unit], - tags: Seq[(Tag, Int)]): Task[List[(String, SuiteResult)]] = { + @deprecated("Use the variant without tags", "1.1.1") + def makeSerial( + loader: ClassLoader, + runnables: Seq[TestRunnable], + setupTasks: Task[Unit], + tags: Seq[(Tag, Int)], + ): Task[List[(String, SuiteResult)]] = + makeSerial(loader, runnables, setupTasks) + + def makeSerial( + loader: ClassLoader, + runnables: Seq[TestRunnable], + setupTasks: Task[Unit], + ): Task[List[(String, SuiteResult)]] = { @tailrec def processRunnable(runnableList: List[TestRunnable], acc: List[(String, SuiteResult)]): List[(String, SuiteResult)] = diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 1f2dcfd3a..d141175ce 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1533,7 +1533,7 @@ object Defaults extends BuildCommon { val cacheStore = s.cacheStoreFactory make "copy-resources" val mappings = (resources.value --- dirs) pair (rebase(dirs, t) | flat(t)) s.log.debug("Copy resource mappings: " + mappings.mkString("\n\t", "\n\t", "")) - Sync(cacheStore)(mappings) + Sync.sync(cacheStore)(mappings) mappings } From a90832b5935340425088a84a4fed96f35c4f4dac Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 14 Dec 2017 13:41:40 +0000 Subject: [PATCH 023/356] Remove all warnings from mainProj --- build.sbt | 18 +- .../src/main/scala/sbt/Structure.scala | 2 + main/src/main/scala/sbt/Cross.scala | 8 +- main/src/main/scala/sbt/Defaults.scala | 123 +++++---- main/src/main/scala/sbt/EvaluateTask.scala | 40 ++- main/src/main/scala/sbt/Extracted.scala | 40 +-- main/src/main/scala/sbt/Main.scala | 37 ++- main/src/main/scala/sbt/PluginCross.scala | 5 +- main/src/main/scala/sbt/Plugins.scala | 16 +- main/src/main/scala/sbt/Project.scala | 11 +- main/src/main/scala/sbt/ScopeFilter.scala | 2 +- main/src/main/scala/sbt/SessionVar.scala | 2 +- main/src/main/scala/sbt/TemplateCommand.scala | 5 +- main/src/main/scala/sbt/internal/Act.scala | 17 +- .../main/scala/sbt/internal/Aggregation.scala | 36 +-- .../main/scala/sbt/internal/BuildDef.scala | 2 +- .../scala/sbt/internal/BuildStructure.scala | 4 +- .../scala/sbt/internal/CommandExchange.scala | 69 ++--- .../DefaultBackgroundJobService.scala | 7 +- .../sbt/internal/EvaluateConfigurations.scala | 252 +++++++++++------- .../scala/sbt/internal/GlobalPlugin.scala | 2 +- .../sbt/internal/LibraryManagement.scala | 12 +- main/src/main/scala/sbt/internal/Load.scala | 5 +- .../main/scala/sbt/internal/LogManager.scala | 6 +- .../scala/sbt/internal/PluginsDebug.scala | 11 +- .../scala/sbt/internal/RelayAppender.scala | 4 +- .../sbt/internal/SettingCompletions.scala | 124 ++++----- .../scala/sbt/internal/SettingGraph.scala | 2 +- .../main/scala/sbt/internal/TaskTimings.scala | 2 +- .../scala/sbt/internal/parser/SbtParser.scala | 8 +- .../sbt/internal/parser/SbtRefactorings.scala | 5 +- .../sbt/internal/server/Definition.scala | 4 +- main/src/test/scala/Delegates.scala | 17 +- main/src/test/scala/ParseKey.scala | 12 +- main/src/test/scala/PluginsTest.scala | 20 +- .../scala/sbt/internal/parser/ErrorSpec.scala | 3 +- .../internal/server/SettingQueryTest.scala | 2 +- sbt/src/sbt-test/actions/run-task/A.scala | 9 +- .../project/Common.scala | 4 +- 39 files changed, 502 insertions(+), 446 deletions(-) diff --git a/build.sbt b/build.sbt index 6ef429611..58d677122 100644 --- a/build.sbt +++ b/build.sbt @@ -421,17 +421,17 @@ lazy val mainProj = (project in file("main")) sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", mimaSettings, mimaBinaryIssueFilters ++= Vector( - // Changed the signature of NetworkChannel ctor. internal. - exclude[DirectMissingMethodProblem]("sbt.internal.server.NetworkChannel.*"), - // ctor for ConfigIndex. internal. - exclude[DirectMissingMethodProblem]("sbt.internal.ConfigIndex.*"), + // Changed signature or removed something in the internal pacakge + exclude[DirectMissingMethodProblem]("sbt.internal.*"), + // New and changed methods on KeyIndex. internal. exclude[ReversedMissingMethodProblem]("sbt.internal.KeyIndex.*"), - exclude[DirectMissingMethodProblem]("sbt.internal.KeyIndex.*"), - // Removed unused val. internal. - exclude[DirectMissingMethodProblem]("sbt.internal.RelayAppender.jsonFormat"), - // Removed unused def. internal. - exclude[DirectMissingMethodProblem]("sbt.internal.Load.isProjectThis"), + + // Changed signature or removed private[sbt] methods + exclude[DirectMissingMethodProblem]("sbt.Classpaths.unmanagedLibs0"), + exclude[DirectMissingMethodProblem]("sbt.Defaults.allTestGroupsTask"), + exclude[DirectMissingMethodProblem]("sbt.Plugins.topologicalSort"), + exclude[IncompatibleMethTypeProblem]("sbt.Defaults.allTestGroupsTask"), ) ) .configure( diff --git a/main-settings/src/main/scala/sbt/Structure.scala b/main-settings/src/main/scala/sbt/Structure.scala index 3c1d705b4..abe27d015 100644 --- a/main-settings/src/main/scala/sbt/Structure.scala +++ b/main-settings/src/main/scala/sbt/Structure.scala @@ -324,6 +324,8 @@ object Scoped { "0.13.2") def task: SettingKey[Task[S]] = scopedSetting(scope, key) + def toSettingKey: SettingKey[Task[S]] = scopedSetting(scope, key) + def get(settings: Settings[Scope]): Option[Task[S]] = settings.get(scope, key) def ? : Initialize[Task[Option[S]]] = Def.optional(scopedKey) { diff --git a/main/src/main/scala/sbt/Cross.scala b/main/src/main/scala/sbt/Cross.scala index 745206a27..5d3de17c0 100644 --- a/main/src/main/scala/sbt/Cross.scala +++ b/main/src/main/scala/sbt/Cross.scala @@ -72,8 +72,7 @@ object Cross { } & spacedFirst(CrossCommand) } - private def crossRestoreSessionParser(state: State): Parser[String] = - token(CrossRestoreSessionCommand) + private def crossRestoreSessionParser: Parser[String] = token(CrossRestoreSessionCommand) private[sbt] def requireSession[T](p: State => Parser[T]): State => Parser[T] = s => if (s get sessionSettings isEmpty) failure("No project loaded") else p(s) @@ -189,9 +188,10 @@ object Cross { } def crossRestoreSession: Command = - Command.arb(crossRestoreSessionParser, crossRestoreSessionHelp)(crossRestoreSessionImpl) + Command.arb(_ => crossRestoreSessionParser, crossRestoreSessionHelp)((s, _) => + crossRestoreSessionImpl(s)) - private def crossRestoreSessionImpl(state: State, arg: String): State = { + private def crossRestoreSessionImpl(state: State): State = { restoreCapturedSession(state, Project.extract(state)) } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index d141175ce..cafe05227 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -670,7 +670,6 @@ object Defaults extends BuildCommon { (testGrouping in test).value, (testExecution in test).value, (fullClasspath in test).value, - (javaHome in test).value, testForkedParallel.value, (javaOptions in test).value ) @@ -828,7 +827,6 @@ object Defaults extends BuildCommon { testGrouping.value, newConfig, fullClasspath.value, - javaHome.value, testForkedParallel.value, javaOptions.value ) @@ -855,20 +853,20 @@ object Defaults extends BuildCommon { } } - private[sbt] def allTestGroupsTask(s: TaskStreams, - frameworks: Map[TestFramework, Framework], - loader: ClassLoader, - groups: Seq[Tests.Group], - config: Tests.Execution, - cp: Classpath, - javaHome: Option[File]): Initialize[Task[Tests.Output]] = { + private[sbt] def allTestGroupsTask( + s: TaskStreams, + frameworks: Map[TestFramework, Framework], + loader: ClassLoader, + groups: Seq[Tests.Group], + config: Tests.Execution, + cp: Classpath, + ): Initialize[Task[Tests.Output]] = { allTestGroupsTask(s, frameworks, loader, groups, config, cp, - javaHome, forkedParallelExecution = false, javaOptions = Nil) } @@ -880,7 +878,6 @@ object Defaults extends BuildCommon { groups: Seq[Tests.Group], config: Tests.Execution, cp: Classpath, - javaHome: Option[File], forkedParallelExecution: Boolean): Initialize[Task[Tests.Output]] = { allTestGroupsTask(s, frameworks, @@ -888,7 +885,6 @@ object Defaults extends BuildCommon { groups, config, cp, - javaHome, forkedParallelExecution, javaOptions = Nil) } @@ -899,12 +895,11 @@ object Defaults extends BuildCommon { groups: Seq[Tests.Group], config: Tests.Execution, cp: Classpath, - javaHome: Option[File], forkedParallelExecution: Boolean, javaOptions: Seq[String]): Initialize[Task[Tests.Output]] = { val runners = createTestRunners(frameworks, loader, config) val groupTasks = groups map { - case Tests.Group(name, tests, runPolicy) => + case Tests.Group(_, tests, runPolicy) => runPolicy match { case Tests.SubProcess(opts) => s.log.debug(s"javaOptions: ${opts.runJVMOptions}") @@ -1606,7 +1601,11 @@ object Defaults extends BuildCommon { val sv = (sbtVersion in pluginCrossBuild).value val scalaV = (scalaVersion in pluginCrossBuild).value val binVersion = (scalaBinaryVersion in pluginCrossBuild).value - val cross = if (id.crossVersioned) CrossVersion.binary else Disabled() + val cross = id.crossVersionedValue match { + case CrossValue.Disabled => Disabled() + case CrossValue.Full => CrossVersion.full + case CrossValue.Binary => CrossVersion.binary + } val base = ModuleID(id.groupID, id.name, sv).withCrossVersion(cross) CrossVersion(scalaV, binVersion)(base).withCrossVersion(Disabled()) } @@ -1699,7 +1698,7 @@ object Classpaths { } def packaged(pkgTasks: Seq[TaskKey[File]]): Initialize[Task[Map[Artifact, File]]] = - enabledOnly(packagedArtifact.task, pkgTasks) apply (_.join.map(_.toMap)) + enabledOnly(packagedArtifact.toSettingKey, pkgTasks) apply (_.join.map(_.toMap)) def artifactDefs(pkgTasks: Seq[TaskKey[File]]): Initialize[Seq[Artifact]] = enabledOnly(artifact, pkgTasks) @@ -1709,8 +1708,10 @@ object Classpaths { case (a, true) => a }) - def forallIn[T](key: Scoped.ScopingSetting[SettingKey[T]], - pkgTasks: Seq[TaskKey[_]]): Initialize[Seq[T]] = + def forallIn[T]( + key: Scoped.ScopingSetting[SettingKey[T]], // should be just SettingKey[T] (mea culpa) + pkgTasks: Seq[TaskKey[_]], + ): Initialize[Seq[T]] = pkgTasks.map(pkg => key in pkg.scope in pkg).join private[this] def publishGlobalDefaults = @@ -1740,9 +1741,9 @@ object Classpaths { deliver := deliverTask(makeIvyXmlConfiguration).value, deliverLocal := deliverTask(makeIvyXmlLocalConfiguration).value, makeIvyXml := deliverTask(makeIvyXmlConfiguration).value, - publish := publishTask(publishConfiguration, deliver).value, - publishLocal := publishTask(publishLocalConfiguration, deliverLocal).value, - publishM2 := publishTask(publishM2Configuration, deliverLocal).value + publish := publishTask(publishConfiguration).value, + publishLocal := publishTask(publishLocalConfiguration).value, + publishM2 := publishTask(publishM2Configuration).value ) private[this] def baseGlobalDefaults = @@ -1816,7 +1817,7 @@ object Classpaths { appResolvers.value, useJCenter.value) match { case (Some(delegated), Seq(), _, _) => delegated - case (_, rs, Some(ars), uj) => ars ++ rs + case (_, rs, Some(ars), _) => ars ++ rs case (_, rs, _, uj) => Resolver.combineDefaultResolvers(rs.toVector, uj, mavenCentral = true) }), appResolvers := { @@ -2027,7 +2028,6 @@ object Classpaths { val docTypes = docArtifactTypes.value val out = is.withIvy(s.log)(_.getSettings.getDefaultIvyUserDir) val uwConfig = (unresolvedWarningConfiguration in update).value - val scalaModule = scalaModuleInfo.value withExcludes(out, mod.classifiers, lock(app)) { excludes => lm.updateClassifiers( GetClassifiersConfiguration( @@ -2058,7 +2058,6 @@ object Classpaths { // Override the default to handle mixing in the sbtPlugin + scala dependencies. allDependencies := { val base = projectDependencies.value ++ libraryDependencies.value - val dependency = sbtDependency.value val isPlugin = sbtPlugin.value val sbtdeps = (sbtDependency in pluginCrossBuild).value.withConfigurations(Some(Provided.name)) @@ -2177,9 +2176,6 @@ object Classpaths { val log = s.log val out = is.withIvy(log)(_.getSettings.getDefaultIvyUserDir) val uwConfig = (unresolvedWarningConfiguration in update).value - val depDir = dependencyCacheDirectory.value - val ivy = scalaModuleInfo.value - val st = state.value withExcludes(out, mod.classifiers, lock(app)) { excludes => // val noExplicitCheck = ivy.map(_.withCheckExplicit(false)) @@ -2196,7 +2192,7 @@ object Classpaths { uwConfig, log ) match { - case Left(uw) => ??? + case Left(_) => ??? case Right(ur) => ur } } @@ -2227,16 +2223,20 @@ object Classpaths { IvyActions.deliver(ivyModule.value, config.value, streams.value.log) } - def publishTask(config: TaskKey[PublishConfiguration], - deliverKey: TaskKey[_]): Initialize[Task[Unit]] = + @deprecated("Use variant without delivery key", "1.1.1") + def publishTask( + config: TaskKey[PublishConfiguration], + deliverKey: TaskKey[_], + ): Initialize[Task[Unit]] = + publishTask(config) + + def publishTask(config: TaskKey[PublishConfiguration]): Initialize[Task[Unit]] = Def.taskDyn { val s = streams.value val skp = (skip in publish).value val ref = thisProjectRef.value if (skp) Def.task { s.log.debug(s"Skipping publish* for ${ref.project}") } else - Def.task { - IvyActions.publish(ivyModule.value, config.value, s.log) - } + Def.task { IvyActions.publish(ivyModule.value, config.value, s.log) } } tag (Tags.Publish, Tags.Network) val moduleIdJsonKeyFormat: sjsonnew.JsonKeyFormat[ModuleID] = @@ -2403,7 +2403,7 @@ object Classpaths { s.init.evaluate(empty) map { _ -> s.pos } }: _*) } catch { - case NonFatal(e) => Map() + case NonFatal(_) => Map() } val outCacheStore = cacheStoreFactory make "output_dsp" @@ -2708,14 +2708,16 @@ object Classpaths { data: Settings[Scope], deps: BuildDependencies): Initialize[Task[Classpath]] = Def.value { - interDependencies(projectRef, - deps, - conf, - conf, - data, - TrackLevel.TrackAlways, - true, - unmanagedLibs0) + interDependencies( + projectRef, + deps, + conf, + conf, + data, + TrackLevel.TrackAlways, + true, + (dep, conf, data, _) => unmanagedLibs(dep, conf, data), + ) } private[sbt] def internalDependenciesImplTask(projectRef: ProjectRef, conf: Configuration, @@ -2820,20 +2822,19 @@ object Classpaths { case TrackLevel.TrackIfMissing => getClasspath(exportedProductJarsIfMissing, dep, conf, data) case TrackLevel.TrackAlways => getClasspath(exportedProductJars, dep, conf, data) } - private[sbt] def unmanagedLibs0(dep: ResolvedReference, - conf: String, - data: Settings[Scope], - track: TrackLevel): Task[Classpath] = - unmanagedLibs(dep, conf, data) + def unmanagedLibs(dep: ResolvedReference, conf: String, data: Settings[Scope]): Task[Classpath] = getClasspath(unmanagedJars, dep, conf, data) + def getClasspath(key: TaskKey[Classpath], dep: ResolvedReference, conf: String, data: Settings[Scope]): Task[Classpath] = (key in (dep, ConfigKey(conf))) get data getOrElse constant(Nil) + def defaultConfigurationTask(p: ResolvedReference, data: Settings[Scope]): Configuration = flatten(defaultConfiguration in p get data) getOrElse Configurations.Default + def flatten[T](o: Option[Option[T]]): Option[T] = o flatMap idFun val sbtIvySnapshots: URLRepository = Resolver.sbtIvyRepo("snapshots") @@ -2866,7 +2867,7 @@ object Classpaths { up.filter(configurationFilter(config.name) && artifactFilter(`type` = jarTypes)) .toSeq .map { - case (conf, module, art, file) => + case (_, module, art, file) => Attributed(file)( AttributeMap.empty .put(artifact.key, art) @@ -3126,13 +3127,16 @@ trait BuildExtra extends BuildCommon with DefExtra { file.value, managedScalaInstance.value) - def externalPom(file: Initialize[File] = inBase("pom.xml"), - iScala: Initialize[Option[ScalaModuleInfo]] = scalaModuleInfo) - : Setting[Task[ModuleSettings]] = - moduleSettings := PomConfiguration(ivyValidate.value, - scalaModuleInfo.value, - file.value, - managedScalaInstance.value) + def externalPom( + file: Initialize[File] = inBase("pom.xml"), + iScala: Initialize[Option[ScalaModuleInfo]] = scalaModuleInfo, + ): Setting[Task[ModuleSettings]] = + moduleSettings := PomConfiguration( + ivyValidate.value, + iScala.value, + file.value, + managedScalaInstance.value, + ) def runInputTask(config: Configuration, mainClass: String, @@ -3161,7 +3165,10 @@ trait BuildExtra extends BuildCommon with DefExtra { config: Configuration, mainClass: String, baseArguments: String*): Vector[Setting[_]] = { - // Use Def.inputTask with the `Def.spaceDelimited()` parser + // TODO: Re-write to avoid InputTask.apply which is deprecated + // I tried "Def.spaceDelimited().parsed" (after importing Def.parserToInput) + // but it broke actions/run-task + // Maybe it needs to be defined inside a Def.inputTask? def inputTask[T](f: TaskKey[Seq[String]] => Initialize[Task[T]]): Initialize[InputTask[T]] = InputTask.apply(Def.value((s: State) => Def.spaceDelimited()))(f) @@ -3216,7 +3223,7 @@ trait BuildExtra extends BuildCommon with DefExtra { trait DefExtra { private[this] val ts: TaskSequential = new TaskSequential {} - implicit def toTaskSequential(d: Def.type): TaskSequential = ts + implicit def toTaskSequential(@deprecated("unused", "") d: Def.type): TaskSequential = ts } trait BuildCommon { @@ -3224,7 +3231,7 @@ trait BuildCommon { /** * Allows a String to be used where a `NameFilter` is expected. * Asterisks (`*`) in the string are interpreted as wildcards. - * All other characters must match exactly. See [[sbt.GlobFilter]]. + * All other characters must match exactly. See [[sbt.io.GlobFilter]]. */ implicit def globFilter(expression: String): NameFilter = GlobFilter(expression) diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 1d17f5cc3..fed8b4acb 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -8,7 +8,7 @@ package sbt import sbt.internal.{ Load, BuildStructure, TaskTimings, TaskName, GCUtil } -import sbt.internal.util.{ Attributed, ErrorHandling, HList, RMap, Signals, Types } +import sbt.internal.util.{ Attributed, ConsoleAppender, ErrorHandling, HList, RMap, Signals, Types } import sbt.util.{ Logger, Show } import sbt.librarymanagement.{ Resolver, UpdateReport } @@ -247,7 +247,11 @@ object EvaluateTask { (executionRoots in Global) ::= dummyRoots ) - def evalPluginDef(log: Logger)(pluginDef: BuildStructure, state: State): PluginData = { + @deprecated("Use variant which doesn't take a logger", "1.1.1") + def evalPluginDef(log: Logger)(pluginDef: BuildStructure, state: State): PluginData = + evalPluginDef(pluginDef, state) + + def evalPluginDef(pluginDef: BuildStructure, state: State): PluginData = { val root = ProjectRef(pluginDef.root, Load.getRootProject(pluginDef.units)(pluginDef.root)) val pluginKey = pluginData val config = extractedTaskConfig(Project.extract(state), pluginDef, state) @@ -256,7 +260,7 @@ object EvaluateTask { val (newS, result) = evaluated getOrElse sys.error( "Plugin data does not exist for plugin definition at " + pluginDef.root) Project.runUnloadHooks(newS) // discard states - processResult(result, log) + processResult2(result) } /** @@ -296,8 +300,8 @@ object EvaluateTask { def logIncomplete(result: Incomplete, state: State, streams: Streams): Unit = { val all = Incomplete linearize result - val keyed = for (Incomplete(Some(key: ScopedKey[_]), _, msg, _, ex) <- all) - yield (key, msg, ex) + val keyed = + all collect { case Incomplete(Some(key: ScopedKey[_]), _, msg, _, ex) => (key, msg, ex) } import ExceptionCategory._ for ((key, msg, Some(ex)) <- keyed) { @@ -312,7 +316,7 @@ object EvaluateTask { for ((key, msg, ex) <- keyed if (msg.isDefined || ex.isDefined)) { val msgString = (msg.toList ++ ex.toList.map(ErrorHandling.reducedToString)).mkString("\n\t") val log = getStreams(key, streams).log - val display = contextDisplay(state, log.ansiCodesSupported) + val display = contextDisplay(state, ConsoleAppender.formatEnabledInEnv) log.error("(" + display.show(key) + ") " + msgString) } } @@ -433,12 +437,21 @@ object EvaluateTask { case in @ Incomplete(Some(node: Task[_]), _, _, _, _) => in.copy(node = transformNode(node)) case i => i } + type AnyCyclic = Execute[({ type A[_] <: AnyRef })#A]#CyclicException[_] + def convertCyclicInc: Incomplete => Incomplete = { - case in @ Incomplete(_, _, _, _, Some(c: AnyCyclic)) => + case in @ Incomplete( + _, + _, + _, + _, + Some(c: Execute[({ type A[_] <: AnyRef })#A @unchecked]#CyclicException[_]) + ) => in.copy(directCause = Some(new RuntimeException(convertCyclic(c)))) case i => i } + def convertCyclic(c: AnyCyclic): String = (c.caller, c.target) match { case (caller: Task[_], target: Task[_]) => @@ -448,7 +461,7 @@ object EvaluateTask { } def liftAnonymous: Incomplete => Incomplete = { - case i @ Incomplete(node, tpe, None, causes, None) => + case i @ Incomplete(_, _, None, causes, None) => causes.find(inc => inc.node.isEmpty && (inc.message.isDefined || inc.directCause.isDefined)) match { case Some(lift) => i.copy(directCause = lift.directCause, message = lift.message) case None => i @@ -456,12 +469,19 @@ object EvaluateTask { case i => i } + @deprecated("Use processResult2 which doesn't take the unused log param", "1.1.1") def processResult[T](result: Result[T], log: Logger, show: Boolean = false): T = - onResult(result, log) { v => + processResult2(result, show) + + def processResult2[T](result: Result[T], show: Boolean = false): T = + onResult(result) { v => if (show) println("Result: " + v); v } - def onResult[T, S](result: Result[T], log: Logger)(f: T => S): S = + @deprecated("Use variant that doesn't take log", "1.1.1") + def onResult[T, S](result: Result[T], log: Logger)(f: T => S): S = onResult(result)(f) + + def onResult[T, S](result: Result[T])(f: T => S): S = result match { case Value(v) => f(v) case Inc(inc) => throw inc diff --git a/main/src/main/scala/sbt/Extracted.scala b/main/src/main/scala/sbt/Extracted.scala index 80243ada1..01139d0dd 100644 --- a/main/src/main/scala/sbt/Extracted.scala +++ b/main/src/main/scala/sbt/Extracted.scala @@ -54,12 +54,12 @@ final case class Extracted(structure: BuildStructure, * See `runAggregated` for that. */ def runTask[T](key: TaskKey[T], state: State): (State, T) = { - val rkey = resolve(key.scopedKey) + val rkey = resolve(key) val config = extractedTaskConfig(this, structure, state) val value: Option[(State, Result[T])] = EvaluateTask(structure, key.scopedKey, state, currentRef, config) val (newS, result) = getOrError(rkey.scope, rkey.key, value) - (newS, EvaluateTask.processResult(result, newS.log)) + (newS, EvaluateTask.processResult2(result)) } /** @@ -72,22 +72,22 @@ final case class Extracted(structure: BuildStructure, * This method requests execution of only the given task and does not aggregate execution. */ def runInputTask[T](key: InputKey[T], input: String, state: State): (State, T) = { - val scopedKey = ScopedKey( + val key2 = Scoped.scopedSetting( Scope.resolveScope(Load.projectScope(currentRef), currentRef.build, rootProject)(key.scope), key.key ) - val rkey = resolve(scopedKey) - val inputTask = get(Scoped.scopedSetting(rkey.scope, rkey.key)) + val rkey = resolve(key2) + val inputTask = get(rkey) val task = Parser.parse(input, inputTask.parser(state)) match { case Right(t) => t case Left(msg) => sys.error(s"Invalid programmatic input:\n$msg") } val config = extractedTaskConfig(this, structure, state) EvaluateTask.withStreams(structure, state) { str => - val nv = EvaluateTask.nodeView(state, str, rkey :: Nil) + val nv = EvaluateTask.nodeView(state, str, rkey.scopedKey :: Nil) val (newS, result) = EvaluateTask.runTask(task, state, str, structure.index.triggers, config)(nv) - (newS, EvaluateTask.processResult(result, newS.log)) + (newS, EvaluateTask.processResult2(result)) } } @@ -98,27 +98,29 @@ final case class Extracted(structure: BuildStructure, * Other axes are resolved to `Zero` if unspecified. */ def runAggregated[T](key: TaskKey[T], state: State): State = { - val rkey = resolve(key.scopedKey) + val rkey = resolve(key) val keys = Aggregation.aggregate(rkey, ScopeMask(), structure.extra) val tasks = Act.keyValues(structure)(keys) - Aggregation.runTasks(state, - structure, - tasks, - DummyTaskMap(Nil), - show = Aggregation.defaultShow(state, false))(showKey) + Aggregation.runTasks( + state, + tasks, + DummyTaskMap(Nil), + show = Aggregation.defaultShow(state, false), + )(showKey) } - private[this] def resolve[T](key: ScopedKey[T]): ScopedKey[T] = - Project.mapScope(Scope.resolveScope(GlobalScope, currentRef.build, rootProject))(key.scopedKey) + private[this] def resolve[K <: Scoped.ScopingSetting[K] with Scoped](key: K): K = + key in Scope.resolveScope(GlobalScope, currentRef.build, rootProject)(key.scope) private def getOrError[T](scope: Scope, key: AttributeKey[_], value: Option[T])( - implicit display: Show[ScopedKey[_]]): T = + implicit display: Show[ScopedKey[_]] + ): T = value getOrElse sys.error(display.show(ScopedKey(scope, key)) + " is undefined.") private def getOrError[T](scope: Scope, key: AttributeKey[T])( - implicit display: Show[ScopedKey[_]]): T = - structure.data.get(scope, key) getOrElse sys.error( - display.show(ScopedKey(scope, key)) + " is undefined.") + implicit display: Show[ScopedKey[_]] + ): T = + getOrError(scope, key, structure.data.get(scope, key))(display) def append(settings: Seq[Setting[_]], state: State): State = { val appendSettings = diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 09d668c4b..869c528ea 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -273,9 +273,9 @@ object BuiltinCommands { case _ => si.actualVersion } - private[this] def quiet[T](t: => T): Option[T] = try { Some(t) } catch { - case e: Exception => None - } + private[this] def quiet[T](t: => T): Option[T] = + try Some(t) + catch { case _: Exception => None } def settingsCommand: Command = showSettingLike(SettingsCommand, @@ -400,7 +400,7 @@ object BuiltinCommands { // For correct behavior, we also need to re-inject a settings logger, as we'll be re-evaluating settings val loggerInject = LogManager.settingsLogger(s) val withLogger = newSession.appendRaw(loggerInject :: Nil) - val show = Project.showContextKey(newSession, structure) + val show = Project.showContextKey2(newSession) val newStructure = Load.reapply(withLogger.mergeSettings, structure)(show) Project.setProject(newSession, newStructure, s) } @@ -424,19 +424,27 @@ object BuiltinCommands { )(cl) val setResult = if (all) SettingCompletions.setAll(extracted, settings) - else SettingCompletions.setThis(s, extracted, settings, arg) + else SettingCompletions.setThis(extracted, settings, arg) s.log.info(setResult.quietSummary) s.log.debug(setResult.verboseSummary) reapply(setResult.session, structure, s) } + @deprecated("Use variant that doesn't take a State", "1.1.1") def setThis( s: State, extracted: Extracted, settings: Seq[Def.Setting[_]], arg: String ): SetResult = - SettingCompletions.setThis(s, extracted, settings, arg) + setThis(extracted, settings, arg) + + def setThis( + extracted: Extracted, + settings: Seq[Def.Setting[_]], + arg: String + ): SetResult = + SettingCompletions.setThis(extracted, settings, arg) def inspect: Command = Command(InspectCommand, inspectBrief, inspectDetailed)(Inspect.parser) { case (s, (option, sk)) => @@ -448,10 +456,10 @@ object BuiltinCommands { Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) { case (s, (pattern, Some(sks))) => val (str, _, display) = extractLast(s) - Output.lastGrep(sks, str.streams(s), pattern, printLast(s))(display) + Output.lastGrep(sks, str.streams(s), pattern, printLast)(display) keepLastLog(s) case (s, (pattern, None)) => - for (logFile <- lastLogFile(s)) yield Output.lastGrep(logFile, pattern, printLast(s)) + for (logFile <- lastLogFile(s)) yield Output.lastGrep(logFile, pattern, printLast) keepLastLog(s) } @@ -493,7 +501,7 @@ object BuiltinCommands { lastOnly_keys <- keysParser kvs = Act.keyValues(structure)(lastOnly_keys._2) f <- if (lastOnly_keys._1) success(() => s) - else Aggregation.evaluatingParser(s, structure, show)(kvs) + else Aggregation.evaluatingParser(s, show)(kvs) } yield () => { def export0(s: State): State = lastImpl(s, kvs, Some(ExportStream)) @@ -516,7 +524,7 @@ object BuiltinCommands { def last: Command = Command(LastCommand, lastBrief, lastDetailed)(aggregatedKeyValueParser) { case (s, Some(sks)) => lastImpl(s, sks, None) case (s, None) => - for (logFile <- lastLogFile(s)) yield Output.last(logFile, printLast(s)) + for (logFile <- lastLogFile(s)) yield Output.last(logFile, printLast) keepLastLog(s) } @@ -525,7 +533,7 @@ object BuiltinCommands { private[this] def lastImpl(s: State, sks: AnyKeys, sid: Option[String]): State = { val (str, _, display) = extractLast(s) - Output.last(sks, str.streams(s), printLast(s), sid)(display) + Output.last(sks, str.streams(s), printLast, sid)(display) keepLastLog(s) } @@ -550,7 +558,10 @@ object BuiltinCommands { */ def isLastOnly(s: State): Boolean = s.history.previous.forall(_.commandLine == Shell) - def printLast(s: State): Seq[String] => Unit = _ foreach println + @deprecated("Use variant that doesn't take the state", "1.1.1") + def printLast(s: State): Seq[String] => Unit = printLast + + def printLast: Seq[String] => Unit = _ foreach println def autoImports(extracted: Extracted): EvalImports = new EvalImports(imports(extracted), "") @@ -620,7 +631,7 @@ object BuiltinCommands { val extraUpdated = Project.updateExtraBuilds(s, f) try doLoadProject(extraUpdated, LoadAction.Current) catch { - case e: Exception => + case _: Exception => s.log.error("Project loading failed: reverting to previous state.") Project.setExtraBuilds(s, original) } diff --git a/main/src/main/scala/sbt/PluginCross.scala b/main/src/main/scala/sbt/PluginCross.scala index 0e9e1462d..bf6039c74 100644 --- a/main/src/main/scala/sbt/PluginCross.scala +++ b/main/src/main/scala/sbt/PluginCross.scala @@ -16,7 +16,7 @@ import sbt.internal.Load import sbt.internal.CommandStrings._ import Cross.{ spacedFirst, requireSession } import sbt.librarymanagement.VersionNumber -import Project.{ inScope } +import Project.inScope /** * Module responsible for plugin cross building. @@ -24,8 +24,7 @@ import Project.{ inScope } private[sbt] object PluginCross { lazy val pluginSwitch: Command = { def switchParser(state: State): Parser[(String, String)] = { - val knownVersions = Nil - lazy val switchArgs = token(NotSpace.examples(knownVersions: _*)) ~ (token( + lazy val switchArgs = token(NotSpace.examples()) ~ (token( Space ~> matched(state.combinedParser)) ?? "") lazy val nextSpaced = spacedFirst(PluginSwitchCommand) token(PluginSwitchCommand ~ OptSpace) flatMap { _ => diff --git a/main/src/main/scala/sbt/Plugins.scala b/main/src/main/scala/sbt/Plugins.scala index 0b65c6aea..a0aa1ee2a 100644 --- a/main/src/main/scala/sbt/Plugins.scala +++ b/main/src/main/scala/sbt/Plugins.scala @@ -111,7 +111,7 @@ abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions { def extraProjects: Seq[Project] = Nil /** The [[Project]]s to add to the current build based on an existing project. */ - def derivedProjects(proj: ProjectDefinition[_]): Seq[Project] = Nil + def derivedProjects(@deprecated("unused", "") proj: ProjectDefinition[_]): Seq[Project] = Nil private[sbt] def unary_! : Exclude = Exclude(this) @@ -224,20 +224,19 @@ object Plugins extends PluginsFunctions { _.label }) } - val retval = topologicalSort(selectedPlugins, log) + val retval = topologicalSort(selectedPlugins) // log.debug(s" :: sorted deduced result: ${retval.toString}") retval } } } } - private[sbt] def topologicalSort(ns: List[AutoPlugin], log: Logger): List[AutoPlugin] = { - // log.debug(s"sorting: ns: ${ns.toString}") + + private[sbt] def topologicalSort(ns: List[AutoPlugin]): List[AutoPlugin] = { @tailrec def doSort(found0: List[AutoPlugin], notFound0: List[AutoPlugin], limit0: Int): List[AutoPlugin] = { - // log.debug(s" :: sorting:: found: ${found0.toString} not found ${notFound0.toString}") if (limit0 < 0) throw AutoPluginException(s"Failed to sort ${ns} topologically") else if (notFound0.isEmpty) found0 else { @@ -250,6 +249,7 @@ object Plugins extends PluginsFunctions { val (roots, nonRoots) = ns partition (_.isRoot) doSort(roots, nonRoots, ns.size * ns.size + 1) } + private[sbt] def translateMessage(e: LogicException) = e match { case ic: InitialContradictions => s"Contradiction in selected plugins. These plugins were both included and excluded: ${literalsString( @@ -260,6 +260,7 @@ object Plugins extends PluginsFunctions { case cn: CyclicNegation => s"Cycles in plugin requirements cannot involve excludes. The problematic cycle is: ${literalsString(cn.cycle)}" } + private[this] def literalsString(lits: Seq[Literal]): String = lits map { case Atom(l) => l; case Negated(Atom(l)) => l } mkString (", ") @@ -271,6 +272,7 @@ object Plugins extends PluginsFunctions { val message = s"Plugin$ns provided by multiple AutoPlugins:$nl${dupStrings.mkString(nl)}" throw AutoPluginException(message) } + private[this] def exclusionConflictError(requested: Plugins, selected: Seq[AutoPlugin], conflicting: Seq[AutoPlugin]): Unit = { @@ -360,14 +362,14 @@ ${listConflicts(conflicting)}""") // This would handle things like !!p or !(p && z) case Exclude(n) => hasInclude(n, p) case And(ns) => ns.forall(n => hasExclude(n, p)) - case b: Basic => false + case _: Basic => false case Empty => false } private[sbt] def hasInclude(n: Plugins, p: AutoPlugin): Boolean = n match { case `p` => true case Exclude(n) => hasExclude(n, p) case And(ns) => ns.forall(n => hasInclude(n, p)) - case b: Basic => false + case _: Basic => false case Empty => false } private[this] def flattenConvert(n: Plugins): Seq[Literal] = n match { diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 0282184be..0c5b70a58 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -276,13 +276,20 @@ object Project extends ProjectExtra { showContextKey(state, None) def showContextKey(state: State, keyNameColor: Option[String]): Show[ScopedKey[_]] = - if (isProjectLoaded(state)) showContextKey(session(state), structure(state), keyNameColor) + if (isProjectLoaded(state)) showContextKey2(session(state), keyNameColor) else Def.showFullKey + @deprecated("Use showContextKey2 which doesn't take the unused structure param", "1.1.1") def showContextKey( session: SessionSettings, structure: BuildStructure, keyNameColor: Option[String] = None + ): Show[ScopedKey[_]] = + showContextKey2(session, keyNameColor) + + def showContextKey2( + session: SessionSettings, + keyNameColor: Option[String] = None ): Show[ScopedKey[_]] = Def.showRelativeKey2(session.current, keyNameColor) @@ -402,7 +409,7 @@ object Project extends ProjectExtra { def extract(state: State): Extracted = extract(session(state), structure(state)) private[sbt] def extract(se: SessionSettings, st: BuildStructure): Extracted = - Extracted(st, se, se.current)(showContextKey(se, st)) + Extracted(st, se, se.current)(showContextKey2(se)) def getProjectForReference(ref: Reference, structure: BuildStructure): Option[ResolvedProject] = ref match { case pr: ProjectRef => getProject(pr, structure); case _ => None } diff --git a/main/src/main/scala/sbt/ScopeFilter.scala b/main/src/main/scala/sbt/ScopeFilter.scala index a303ca09a..3b9347532 100644 --- a/main/src/main/scala/sbt/ScopeFilter.scala +++ b/main/src/main/scala/sbt/ScopeFilter.scala @@ -104,7 +104,7 @@ object ScopeFilter { /** Selects all scopes that apply to a single project. Zero and build-level scopes are excluded. */ def inAnyProject: ProjectFilter = - selectAxis(const { case p: ProjectRef => true; case _ => false }) + selectAxis(const { case _: ProjectRef => true; case _ => false }) /** Accepts all values for the task axis except Zero. */ def inAnyTask: TaskFilter = selectAny[AttributeKey[_]] diff --git a/main/src/main/scala/sbt/SessionVar.scala b/main/src/main/scala/sbt/SessionVar.scala index adbda41a5..58ec38b15 100644 --- a/main/src/main/scala/sbt/SessionVar.scala +++ b/main/src/main/scala/sbt/SessionVar.scala @@ -63,7 +63,7 @@ object SessionVar { def read[T](key: ScopedKey[Task[T]], state: State)(implicit f: JsonFormat[T]): Option[T] = Project.structure(state).streams(state).use(key) { s => - try { Some(s.getInput(key, DefaultDataID).read[T]) } catch { case NonFatal(e) => None } + try { Some(s.getInput(key, DefaultDataID).read[T]) } catch { case NonFatal(_) => None } } def load[T](key: ScopedKey[Task[T]], state: State)(implicit f: JsonFormat[T]): Option[T] = diff --git a/main/src/main/scala/sbt/TemplateCommand.scala b/main/src/main/scala/sbt/TemplateCommand.scala index 2da6dcb33..e02b1a6a2 100644 --- a/main/src/main/scala/sbt/TemplateCommand.scala +++ b/main/src/main/scala/sbt/TemplateCommand.scala @@ -21,9 +21,10 @@ import BasicCommandStrings._, BasicKeys._ private[sbt] object TemplateCommandUtil { def templateCommand: Command = - Command(TemplateCommand, templateBrief, templateDetailed)(templateCommandParser)(runTemplate) + Command(TemplateCommand, templateBrief, templateDetailed)(_ => templateCommandParser)( + runTemplate) - private def templateCommandParser(state: State): Parser[Seq[String]] = + private def templateCommandParser: Parser[Seq[String]] = (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map (_ => Nil)) private def runTemplate(state: State, inputArg: Seq[String]): State = { diff --git a/main/src/main/scala/sbt/internal/Act.scala b/main/src/main/scala/sbt/internal/Act.scala index 2ab067db0..9349f2b64 100644 --- a/main/src/main/scala/sbt/internal/Act.scala +++ b/main/src/main/scala/sbt/internal/Act.scala @@ -100,7 +100,7 @@ object Act { conf <- configs(confAmb, defaultConfigs, proj, index) } yield for { - taskAmb <- taskAxis(conf, index.tasks(proj, conf), keyMap) + taskAmb <- taskAxis(index.tasks(proj, conf), keyMap) task = resolveTask(taskAmb) key <- key(index, proj, conf, task, keyMap) extra <- extraAxis(keyMap, IMap.empty) @@ -161,6 +161,7 @@ object Act { def examples(p: Parser[String], exs: Set[String], label: String): Parser[String] = p !!! ("Expected " + label) examples exs + def examplesStrict(p: Parser[String], exs: Set[String], label: String): Parser[String] = filterStrings(examples(p, exs, label), exs, label) @@ -168,6 +169,7 @@ object Act { p.? map { opt => toAxis(opt, ifNone) } + def toAxis[T](opt: Option[T], ifNone: ScopeAxis[T]): ScopeAxis[T] = opt match { case Some(t) => Select(t); case None => ifNone } @@ -231,8 +233,8 @@ object Act { // This queries the key index so tab completion will list the build-level keys. val buildKeys: Set[String] = proj match { - case Some(ProjectRef(uri, id)) => index.keys(Some(BuildRef(uri)), conf, task) - case _ => Set() + case Some(ProjectRef(uri, _)) => index.keys(Some(BuildRef(uri)), conf, task) + case _ => Set() } val keys: Set[String] = index.keys(proj, conf, task) ++ buildKeys keyParser(keys) @@ -255,9 +257,10 @@ object Act { optionalAxis(extras, Zero) } - def taskAxis(d: Option[String], - tasks: Set[AttributeKey[_]], - allKnown: Map[String, AttributeKey[_]]): Parser[ParsedAxis[AttributeKey[_]]] = { + def taskAxis( + tasks: Set[AttributeKey[_]], + allKnown: Map[String, AttributeKey[_]], + ): Parser[ParsedAxis[AttributeKey[_]]] = { val taskSeq = tasks.toSeq def taskKeys(f: AttributeKey[_] => String): Seq[(String, AttributeKey[_])] = taskSeq.map(key => (f(key), key)) @@ -380,7 +383,7 @@ object Act { def evaluate(kvs: Seq[ScopedKey[_]]): Parser[() => State] = { val preparedPairs = anyKeyValues(structure, kvs) val showConfig = Aggregation.defaultShow(state, showTasks = action == ShowAction) - evaluatingParser(state, structure, showConfig)(preparedPairs) map { evaluate => () => + evaluatingParser(state, showConfig)(preparedPairs) map { evaluate => () => { val keyStrings = preparedPairs.map(pp => showKey.show(pp.key)).mkString(", ") state.log.debug("Evaluating tasks: " + keyStrings) diff --git a/main/src/main/scala/sbt/internal/Aggregation.scala b/main/src/main/scala/sbt/internal/Aggregation.scala index 0d2da79d8..ea523df53 100644 --- a/main/src/main/scala/sbt/internal/Aggregation.scala +++ b/main/src/main/scala/sbt/internal/Aggregation.scala @@ -61,11 +61,10 @@ object Aggregation { def applyTasks[T]( s: State, - structure: BuildStructure, ps: Values[Parser[Task[T]]], show: ShowConfig )(implicit display: Show[ScopedKey[_]]): Parser[() => State] = - Command.applyEffect(seqParser(ps))(ts => runTasks(s, structure, ts, DummyTaskMap(Nil), show)) + Command.applyEffect(seqParser(ps))(ts => runTasks(s, ts, DummyTaskMap(Nil), show)) private def showRun[T](complete: Complete[T], show: ShowConfig)( implicit display: Show[ScopedKey[_]] @@ -104,7 +103,6 @@ object Aggregation { } def runTasks[HL <: HList, T](s: State, - structure: BuildStructure, ts: Values[Task[T]], extra: DummyTaskMap, show: ShowConfig)(implicit display: Show[ScopedKey[_]]): State = { @@ -128,33 +126,26 @@ object Aggregation { key in currentRef get structure.data getOrElse true if (get(showSuccess)) { if (get(showTiming)) { - val msg = timingString(start, stop, "", structure.data, currentRef, log) + val msg = timingString(start, stop, structure.data, currentRef) if (success) log.success(msg) else log.error(msg) } else if (success) log.success("") } } + private def timingString( startTime: Long, endTime: Long, - s: String, data: Settings[Scope], currentRef: ProjectRef, - log: Logger ): String = { val format = timingFormat in currentRef get data getOrElse defaultFormat - timing(format, startTime, endTime, "", log) + timing(format, startTime, endTime) } - def timing( - format: java.text.DateFormat, - startTime: Long, - endTime: Long, - s: String, - log: Logger - ): String = { - val ss = if (s.isEmpty) "" else s + " " + + def timing(format: java.text.DateFormat, startTime: Long, endTime: Long): String = { val nowString = format.format(new java.util.Date(endTime)) - "Total " + ss + "time: " + (endTime - startTime + 500) / 1000 + " s, completed " + nowString + "Total time: " + (endTime - startTime + 500) / 1000 + " s, completed " + nowString } def defaultFormat: DateFormat = { @@ -164,20 +155,19 @@ object Aggregation { def applyDynamicTasks[I]( s: State, - structure: BuildStructure, inputs: Values[InputTask[I]], show: ShowConfig )(implicit display: Show[ScopedKey[_]]): Parser[() => State] = { val parsers = for (KeyValue(k, it) <- inputs) yield it.parser(s).map(v => KeyValue(k, v)) Command.applyEffect(seq(parsers)) { roots => - runTasks(s, structure, roots, DummyTaskMap(Nil), show) + runTasks(s, roots, DummyTaskMap(Nil), show) } } - def evaluatingParser(s: State, structure: BuildStructure, show: ShowConfig)( - keys: Seq[KeyValue[_]] - )(implicit display: Show[ScopedKey[_]]): Parser[() => State] = { + def evaluatingParser(s: State, show: ShowConfig)(keys: Seq[KeyValue[_]])( + implicit display: Show[ScopedKey[_]] + ): Parser[() => State] = { // to make the call sites clearer def separate[L](in: Seq[KeyValue[_]])( @@ -210,12 +200,12 @@ object Aggregation { val otherStrings = other.map(_.key).mkString("Task(s)/setting(s):\n\t", "\n\t", "\n") failure(s"Cannot mix input tasks with plain tasks/settings. $inputStrings $otherStrings") } else - applyDynamicTasks(s, structure, maps(inputTasks)(castToAny), show) + applyDynamicTasks(s, maps(inputTasks)(castToAny), show) } else { val base = if (tasks.isEmpty) success(() => s) else - applyTasks(s, structure, maps(tasks)(x => success(castToAny(x))), show) + applyTasks(s, maps(tasks)(x => success(castToAny(x))), show) base.map { res => () => val newState = res() if (show.settingValues && settings.nonEmpty) printSettings(settings, show.print) diff --git a/main/src/main/scala/sbt/internal/BuildDef.scala b/main/src/main/scala/sbt/internal/BuildDef.scala index 96c4641f6..dd1d11aec 100644 --- a/main/src/main/scala/sbt/internal/BuildDef.scala +++ b/main/src/main/scala/sbt/internal/BuildDef.scala @@ -16,7 +16,7 @@ import sbt.internal.util.Attributed import sbt.internal.inc.ReflectUtilities trait BuildDef { - def projectDefinitions(baseDirectory: File): Seq[Project] = projects + def projectDefinitions(@deprecated("unused", "") baseDirectory: File): Seq[Project] = projects def projects: Seq[Project] = ReflectUtilities.allVals[Project](this).values.toSeq // TODO: Should we grab the build core settings here or in a plugin? def settings: Seq[Setting[_]] = Defaults.buildCore diff --git a/main/src/main/scala/sbt/internal/BuildStructure.scala b/main/src/main/scala/sbt/internal/BuildStructure.scala index 9b02e9dfd..626123638 100644 --- a/main/src/main/scala/sbt/internal/BuildStructure.scala +++ b/main/src/main/scala/sbt/internal/BuildStructure.scala @@ -172,9 +172,7 @@ final class DetectedPlugins(val autoPlugins: Seq[DetectedAutoPlugin], private[this] lazy val (autoPluginAutoImports, topLevelAutoPluginAutoImports) = autoPlugins .flatMap { - case DetectedAutoPlugin(name, ap, hasAutoImport) => - if (hasAutoImport) Some(name) - else None + case DetectedAutoPlugin(name, _, hasAutoImport) => if (hasAutoImport) Some(name) else None } .partition(nonTopLevelPlugin) diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 7bbb2c38b..3821fa807 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -36,9 +36,9 @@ 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 { - _.toLowerCase == "true" - } getOrElse true + private val autoStartServer = + sys.props get "sbt.server.autostart" forall (_.toLowerCase == "true") + private val lock = new AnyRef {} private var server: Option[ServerInstance] = None private var consoleChannel: Option[ConsoleChannel] = None @@ -83,7 +83,6 @@ private[sbt] final class CommandExchange { else s } - private def newChannelName: String = s"channel-${nextChannelId.incrementAndGet()}" private def newNetworkName: String = s"network-${nextChannelId.incrementAndGet()}" /** @@ -192,42 +191,24 @@ private[sbt] final class CommandExchange { val params = toLogMessageParams(entry) channels collect { case c: ConsoleChannel => - if (broadcastStringMessage) { + if (broadcastStringMessage || (entry.channelName forall (_ == c.name))) c.publishEvent(event) - } else { - if (entry.channelName.isEmpty || entry.channelName == Some(c.name)) { - c.publishEvent(event) - } - } case c: NetworkChannel => try { // Note that language server's LogMessageParams does not hold the execid, // so this is weaker than the StringMessage. We might want to double-send // in case we have a better client that can utilize the knowledge. import sbt.internal.langserver.codec.JsonProtocol._ - if (broadcastStringMessage) { + if (broadcastStringMessage || (entry.channelName contains c.name)) c.langNotify("window/logMessage", params) - } else { - if (entry.channelName == Some(c.name)) { - c.langNotify("window/logMessage", params) - } - } - } catch { - case _: IOException => - toDel += c - } + } catch { case _: IOException => toDel += c } } case _ => - channels collect { - case c: ConsoleChannel => - c.publishEvent(event) + channels foreach { + case c: ConsoleChannel => c.publishEvent(event) case c: NetworkChannel => - try { - c.publishEvent(event) - } catch { - case _: IOException => - toDel += c - } + try c.publishEvent(event) + catch { case _: IOException => toDel += c } } } toDel.toList match { @@ -283,6 +264,11 @@ private[sbt] final class CommandExchange { // fanout publishEvent def publishEventMessage(event: EventMessage): Unit = { val toDel: ListBuffer[CommandChannel] = ListBuffer.empty + + def tryTo(x: => Unit, c: CommandChannel): Unit = + try x + catch { case _: IOException => toDel += c } + event match { // Special treatment for ConsolePromptEvent since it's hand coded without codec. case entry: ConsolePromptEvent => @@ -296,32 +282,17 @@ private[sbt] final class CommandExchange { case entry: ExecStatusEvent => channels collect { case c: ConsoleChannel => - if (entry.channelName.isEmpty || entry.channelName == Some(c.name)) { - c.publishEventMessage(event) - } + if (entry.channelName forall (_ == c.name)) c.publishEventMessage(event) case c: NetworkChannel => - try { - if (entry.channelName == Some(c.name)) { - c.publishEventMessage(event) - } - } catch { - case e: IOException => - toDel += c - } + if (entry.channelName contains c.name) tryTo(c.publishEventMessage(event), c) } case _ => channels collect { - case c: ConsoleChannel => - c.publishEventMessage(event) - case c: NetworkChannel => - try { - c.publishEventMessage(event) - } catch { - case _: IOException => - toDel += c - } + case c: ConsoleChannel => c.publishEventMessage(event) + case c: NetworkChannel => tryTo(c.publishEventMessage(event), c) } } + toDel.toList match { case Nil => // do nothing case xs => diff --git a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala index dc73c66fd..a4dc0641a 100644 --- a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala +++ b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala @@ -36,12 +36,15 @@ private[sbt] abstract class BackgroundJob { } def shutdown(): Unit + // this should be true on construction and stay true until // the job is complete def isRunning(): Boolean + // called after stop or on spontaneous exit, closing the result // removes the listener def onStop(listener: () => Unit)(implicit ex: ExecutionContext): Closeable + // do we need this or is the spawning task good enough? // def tags: SomeType } @@ -57,8 +60,8 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe private val serviceTempDir = IO.createTemporaryDirectory // hooks for sending start/stop events - protected def onAddJob(job: JobHandle): Unit = {} - protected def onRemoveJob(job: JobHandle): Unit = {} + protected def onAddJob(@deprecated("unused", "") job: JobHandle): Unit = () + protected def onRemoveJob(@deprecated("unused", "") job: JobHandle): Unit = () // this mutable state could conceptually go on State except // that then every task that runs a background job would have diff --git a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala index 6b4e77f91..85b40293c 100644 --- a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -8,11 +8,18 @@ package sbt package internal -import sbt.internal.util.{ complete, AttributeEntry, AttributeKey, LineRange, MessageOnlyException, RangePosition, Settings } +import sbt.internal.util.{ + AttributeEntry, + AttributeKey, + LineRange, + MessageOnlyException, + RangePosition, + Settings +} import java.io.File import compiler.{ Eval, EvalImports } -import complete.DefaultParsers.validID +import sbt.internal.util.complete.DefaultParsers.validID import Def.{ ScopedKey, Setting } import Scope.GlobalScope import sbt.internal.parser.SbtParser @@ -37,7 +44,9 @@ private[sbt] object EvaluateConfigurations { /** * This represents the parsed expressions in a build sbt, as well as where they were defined. */ - private[this] final class ParsedFile(val imports: Seq[(String, Int)], val definitions: Seq[(String, LineRange)], val settings: Seq[(String, LineRange)]) + private[this] final class ParsedFile(val imports: Seq[(String, Int)], + val definitions: Seq[(String, LineRange)], + val settings: Seq[(String, LineRange)]) /** The keywords we look for when classifying a string as a definition. */ private[this] val DefinitionKeywords = Seq("lazy val ", "def ", "val ") @@ -48,18 +57,24 @@ private[sbt] object EvaluateConfigurations { * return a parsed, compiled + evaluated [[LoadedSbtFile]]. The result has * raw sbt-types that can be accessed and used. */ - def apply(eval: Eval, srcs: Seq[File], imports: Seq[String]): LazyClassLoaded[LoadedSbtFile] = - { - val loadFiles = srcs.sortBy(_.getName) map { src => evaluateSbtFile(eval, src, IO.readLines(src), imports, 0) } - loader => (LoadedSbtFile.empty /: loadFiles) { (loaded, load) => loaded merge load(loader) } + def apply(eval: Eval, srcs: Seq[File], imports: Seq[String]): LazyClassLoaded[LoadedSbtFile] = { + val loadFiles = srcs.sortBy(_.getName) map { src => + evaluateSbtFile(eval, src, IO.readLines(src), imports, 0) } + loader => + (LoadedSbtFile.empty /: loadFiles) { (loaded, load) => + loaded merge load(loader) + } + } /** * Reads a given .sbt file and evaluates it into a sequence of setting values. * * Note: This ignores any non-Setting[_] values in the file. */ - def evaluateConfiguration(eval: Eval, src: File, imports: Seq[String]): LazyClassLoaded[Seq[Setting[_]]] = + def evaluateConfiguration(eval: Eval, + src: File, + imports: Seq[String]): LazyClassLoaded[Seq[Setting[_]]] = evaluateConfiguration(eval, src, IO.readLines(src), imports, 0) /** @@ -68,13 +83,16 @@ private[sbt] object EvaluateConfigurations { * * @param builtinImports The set of import statements to add to those parsed in the .sbt file. */ - private[this] def parseConfiguration(file: File, lines: Seq[String], builtinImports: Seq[String], offset: Int): ParsedFile = - { - val (importStatements, settingsAndDefinitions) = splitExpressions(file, lines) - val allImports = builtinImports.map(s => (s, -1)) ++ addOffset(offset, importStatements) - val (definitions, settings) = splitSettingsDefinitions(addOffsetToRange(offset, settingsAndDefinitions)) - new ParsedFile(allImports, definitions, settings) - } + private[this] def parseConfiguration(file: File, + lines: Seq[String], + builtinImports: Seq[String], + offset: Int): ParsedFile = { + val (importStatements, settingsAndDefinitions) = splitExpressions(file, lines) + val allImports = builtinImports.map(s => (s, -1)) ++ addOffset(offset, importStatements) + val (definitions, settings) = splitSettingsDefinitions( + addOffsetToRange(offset, settingsAndDefinitions)) + new ParsedFile(allImports, definitions, settings) + } /** * Evaluates a parsed sbt configuration file. @@ -86,11 +104,15 @@ private[sbt] object EvaluateConfigurations { * * @return Just the Setting[_] instances defined in the .sbt file. */ - def evaluateConfiguration(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): LazyClassLoaded[Seq[Setting[_]]] = - { - val l = evaluateSbtFile(eval, file, lines, imports, offset) - loader => l(loader).settings - } + def evaluateConfiguration(eval: Eval, + file: File, + lines: Seq[String], + imports: Seq[String], + offset: Int): LazyClassLoaded[Seq[Setting[_]]] = { + val l = evaluateSbtFile(eval, file, lines, imports, offset) + loader => + l(loader).settings + } /** * Evaluates a parsed sbt configuration file. @@ -102,27 +124,33 @@ private[sbt] object EvaluateConfigurations { * @return A function which can take an sbt classloader and return the raw types/configuration * which was compiled/parsed for the given file. */ - private[sbt] def evaluateSbtFile(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): LazyClassLoaded[LoadedSbtFile] = - { - // TODO - Store the file on the LoadedSbtFile (or the parent dir) so we can accurately do - // detection for which project project manipulations should be applied. - val name = file.getPath - val parsed = parseConfiguration(file, lines, imports, offset) - val (importDefs, definitions) = - if (parsed.definitions.isEmpty) (Nil, DefinedSbtValues.empty) else { - val definitions = evaluateDefinitions(eval, name, parsed.imports, parsed.definitions, Some(file)) - val imp = BuildUtil.importAllRoot(definitions.enclosingModule :: Nil) - (imp, DefinedSbtValues(definitions)) - } - val allImports = importDefs.map(s => (s, -1)) ++ parsed.imports - val dslEntries = parsed.settings map { - case (dslExpression, range) => - evaluateDslEntry(eval, name, allImports, dslExpression, range) + private[sbt] def evaluateSbtFile(eval: Eval, + file: File, + lines: Seq[String], + imports: Seq[String], + offset: Int): LazyClassLoaded[LoadedSbtFile] = { + // TODO - Store the file on the LoadedSbtFile (or the parent dir) so we can accurately do + // detection for which project project manipulations should be applied. + val name = file.getPath + val parsed = parseConfiguration(file, lines, imports, offset) + val (importDefs, definitions) = + if (parsed.definitions.isEmpty) (Nil, DefinedSbtValues.empty) + else { + val definitions = + evaluateDefinitions(eval, name, parsed.imports, parsed.definitions, Some(file)) + val imp = BuildUtil.importAllRoot(definitions.enclosingModule :: Nil) + (imp, DefinedSbtValues(definitions)) } - eval.unlinkDeferred() - // Tracks all the files we generated from evaluating the sbt file. - val allGeneratedFiles = (definitions.generated ++ dslEntries.flatMap(_.generated)) - loader => { + val allImports = importDefs.map(s => (s, -1)) ++ parsed.imports + val dslEntries = parsed.settings map { + case (dslExpression, range) => + evaluateDslEntry(eval, name, allImports, dslExpression, range) + } + eval.unlinkDeferred() + // Tracks all the files we generated from evaluating the sbt file. + val allGeneratedFiles = (definitions.generated ++ dslEntries.flatMap(_.generated)) + loader => + { val projects = definitions.values(loader).collect { case p: Project => resolveBase(file.getParentFile, p) @@ -140,9 +168,14 @@ private[sbt] object EvaluateConfigurations { case DslEntry.ProjectManipulation(f) => f } // TODO -get project manipulations. - new LoadedSbtFile(settings, projects, importDefs, manipulations, definitions, allGeneratedFiles) + new LoadedSbtFile(settings, + projects, + importDefs, + manipulations, + definitions, + allGeneratedFiles) } - } + } /** move a project to be relative to this file after we've evaluated it. */ private[this] def resolveBase(f: File, p: Project) = p.copy(base = IO.resolve(f, p.base)) @@ -173,11 +206,19 @@ private[sbt] object EvaluateConfigurations { * @return A method that given an sbt classloader, can return the actual [[sbt.internal.DslEntry]] defined by * the expression, and the sequence of .class files generated. */ - private[sbt] def evaluateDslEntry(eval: Eval, name: String, imports: Seq[(String, Int)], expression: String, range: LineRange): TrackedEvalResult[DslEntry] = { + private[sbt] def evaluateDslEntry(eval: Eval, + name: String, + imports: Seq[(String, Int)], + expression: String, + range: LineRange): TrackedEvalResult[DslEntry] = { // TODO - Should we try to namespace these between.sbt files? IF they hash to the same value, they may actually be // exactly the same setting, so perhaps we don't care? val result = try { - eval.eval(expression, imports = new EvalImports(imports, name), srcName = name, tpeName = Some(SettingsDefinitionName), line = range.start) + eval.eval(expression, + imports = new EvalImports(imports, name), + srcName = name, + tpeName = Some(SettingsDefinitionName), + line = range.start) } catch { case e: sbt.compiler.EvalException => throw new MessageOnlyException(e.getMessage) } @@ -206,7 +247,11 @@ private[sbt] object EvaluateConfigurations { */ // Build DSL now includes non-Setting[_] type settings. // Note: This method is used by the SET command, so we may want to evaluate that sucker a bit. - def evaluateSetting(eval: Eval, name: String, imports: Seq[(String, Int)], expression: String, range: LineRange): LazyClassLoaded[Seq[Setting[_]]] = + def evaluateSetting(eval: Eval, + name: String, + imports: Seq[(String, Int)], + expression: String, + range: LineRange): LazyClassLoaded[Seq[Setting[_]]] = evaluateDslEntry(eval, name, imports, expression, range).result andThen { case DslEntry.ProjectSettings(values) => values case _ => Nil @@ -216,44 +261,59 @@ private[sbt] object EvaluateConfigurations { * Splits a set of lines into (imports, expressions). That is, * anything on the right of the tuple is a scala expression (definition or setting). */ - private[sbt] def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = - { - val split = SbtParser(file, lines) - // TODO - Look at pulling the parsed expression trees from the SbtParser and stitch them back into a different - // scala compiler rather than re-parsing. - (split.imports, split.settings) - } + private[sbt] def splitExpressions( + file: File, + lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = { + val split = SbtParser(file, lines) + // TODO - Look at pulling the parsed expression trees from the SbtParser and stitch them back into a different + // scala compiler rather than re-parsing. + (split.imports, split.settings) + } - private[this] def splitSettingsDefinitions(lines: Seq[(String, LineRange)]): (Seq[(String, LineRange)], Seq[(String, LineRange)]) = - lines partition { case (line, range) => isDefinition(line) } + private[this] def splitSettingsDefinitions( + lines: Seq[(String, LineRange)]): (Seq[(String, LineRange)], Seq[(String, LineRange)]) = + lines partition { case (line, _) => isDefinition(line) } - private[this] def isDefinition(line: String): Boolean = - { - val trimmed = line.trim - DefinitionKeywords.exists(trimmed startsWith _) - } + private[this] def isDefinition(line: String): Boolean = { + val trimmed = line.trim + DefinitionKeywords.exists(trimmed startsWith _) + } private[this] def extractedValTypes: Seq[String] = - Seq(classOf[Project], classOf[InputKey[_]], classOf[TaskKey[_]], classOf[SettingKey[_]]).map(_.getName) + Seq(classOf[Project], classOf[InputKey[_]], classOf[TaskKey[_]], classOf[SettingKey[_]]) + .map(_.getName) - private[this] def evaluateDefinitions(eval: Eval, name: String, imports: Seq[(String, Int)], definitions: Seq[(String, LineRange)], file: Option[File]): compiler.EvalDefinitions = - { - val convertedRanges = definitions.map { case (s, r) => (s, r.start to r.end) } - eval.evalDefinitions(convertedRanges, new EvalImports(imports, name), name, file, extractedValTypes) - } + private[this] def evaluateDefinitions(eval: Eval, + name: String, + imports: Seq[(String, Int)], + definitions: Seq[(String, LineRange)], + file: Option[File]): compiler.EvalDefinitions = { + val convertedRanges = definitions.map { case (s, r) => (s, r.start to r.end) } + eval.evalDefinitions(convertedRanges, + new EvalImports(imports, name), + name, + file, + extractedValTypes) + } } object Index { - def taskToKeyMap(data: Settings[Scope]): Map[Task[_], ScopedKey[Task[_]]] = - { - // AttributeEntry + the checked type test 'value: Task[_]' ensures that the cast is correct. - // (scalac couldn't determine that 'key' is of type AttributeKey[Task[_]] on its own and a type match still required the cast) - val pairs = for (scope <- data.scopes; AttributeEntry(key, value: Task[_]) <- data.data(scope).entries) yield (value, ScopedKey(scope, key.asInstanceOf[AttributeKey[Task[_]]])) // unclear why this cast is needed even with a type test in the above filter - pairs.toMap[Task[_], ScopedKey[Task[_]]] - } + def taskToKeyMap(data: Settings[Scope]): Map[Task[_], ScopedKey[Task[_]]] = { + + val pairs = data.scopes flatMap (scope => + data.data(scope).entries collect { + case AttributeEntry(key, value: Task[_]) => + (value, ScopedKey(scope, key.asInstanceOf[AttributeKey[Task[_]]])) + }) + + pairs.toMap[Task[_], ScopedKey[Task[_]]] + } def allKeys(settings: Seq[Setting[_]]): Set[ScopedKey[_]] = - settings.flatMap(s => if (s.key.key.isLocal) Nil else s.key +: s.dependencies).filter(!_.key.isLocal).toSet + settings + .flatMap(s => if (s.key.key.isLocal) Nil else s.key +: s.dependencies) + .filter(!_.key.isLocal) + .toSet def attributeKeys(settings: Settings[Scope]): Set[AttributeKey[_]] = settings.data.values.flatMap(_.keys).toSet[AttributeKey[_]] @@ -261,30 +321,36 @@ object Index { def stringToKeyMap(settings: Set[AttributeKey[_]]): Map[String, AttributeKey[_]] = stringToKeyMap0(settings)(_.label) - private[this] def stringToKeyMap0(settings: Set[AttributeKey[_]])(label: AttributeKey[_] => String): Map[String, AttributeKey[_]] = - { - val multiMap = settings.groupBy(label) - val duplicates = multiMap collect { case (k, xs) if xs.size > 1 => (k, xs.map(_.manifest)) } collect { case (k, xs) if xs.size > 1 => (k, xs) } - if (duplicates.isEmpty) - multiMap.collect { case (k, v) if validID(k) => (k, v.head) } toMap - else - sys.error(duplicates map { case (k, tps) => "'" + k + "' (" + tps.mkString(", ") + ")" } mkString ("Some keys were defined with the same name but different types: ", ", ", "")) + private[this] def stringToKeyMap0(settings: Set[AttributeKey[_]])( + label: AttributeKey[_] => String): Map[String, AttributeKey[_]] = { + val multiMap = settings.groupBy(label) + val duplicates = multiMap collect { case (k, xs) if xs.size > 1 => (k, xs.map(_.manifest)) } collect { + case (k, xs) if xs.size > 1 => (k, xs) } + if (duplicates.isEmpty) + multiMap.collect { case (k, v) if validID(k) => (k, v.head) } toMap + else + sys.error( + duplicates map { case (k, tps) => "'" + k + "' (" + tps.mkString(", ") + ")" } mkString ("Some keys were defined with the same name but different types: ", ", ", "")) + } - private[this]type TriggerMap = collection.mutable.HashMap[Task[_], Seq[Task[_]]] + private[this] type TriggerMap = collection.mutable.HashMap[Task[_], Seq[Task[_]]] - def triggers(ss: Settings[Scope]): Triggers[Task] = - { - val runBefore = new TriggerMap - val triggeredBy = new TriggerMap - for ((_, amap) <- ss.data; AttributeEntry(_, value: Task[_]) <- amap.entries) { - val as = value.info.attributes - update(runBefore, value, as get Keys.runBefore) - update(triggeredBy, value, as get Keys.triggeredBy) + def triggers(ss: Settings[Scope]): Triggers[Task] = { + val runBefore = new TriggerMap + val triggeredBy = new TriggerMap + ss.data.values foreach ( + _.entries foreach { + case AttributeEntry(_, value: Task[_]) => + val as = value.info.attributes + update(runBefore, value, as get Keys.runBefore) + update(triggeredBy, value, as get Keys.triggeredBy) + case _ => () } - val onComplete = Keys.onComplete in GlobalScope get ss getOrElse { () => () } - new Triggers[Task](runBefore, triggeredBy, map => { onComplete(); map }) - } + ) + val onComplete = Keys.onComplete in GlobalScope get ss getOrElse (() => ()) + new Triggers[Task](runBefore, triggeredBy, map => { onComplete(); map }) + } private[this] def update(map: TriggerMap, base: Task[_], tasksOpt: Option[Seq[Task[_]]]): Unit = for (tasks <- tasksOpt; task <- tasks) diff --git a/main/src/main/scala/sbt/internal/GlobalPlugin.scala b/main/src/main/scala/sbt/internal/GlobalPlugin.scala index e71b8ee24..0424a325c 100644 --- a/main/src/main/scala/sbt/internal/GlobalPlugin.scala +++ b/main/src/main/scala/sbt/internal/GlobalPlugin.scala @@ -94,7 +94,7 @@ object GlobalPlugin { val nv = nodeView(state, str, roots) val config = EvaluateTask.extractedTaskConfig(Project.extract(state), structure, state) val (newS, result) = runTask(t, state, str, structure.index.triggers, config)(nv) - (newS, processResult(result, newS.log)) + (newS, processResult2(result)) } } val globalPluginSettings = Project.inScope(Scope.GlobalScope in LocalRootProject)( diff --git a/main/src/main/scala/sbt/internal/LibraryManagement.scala b/main/src/main/scala/sbt/internal/LibraryManagement.scala index a245344dc..adf3f8f5d 100644 --- a/main/src/main/scala/sbt/internal/LibraryManagement.scala +++ b/main/src/main/scala/sbt/internal/LibraryManagement.scala @@ -36,18 +36,12 @@ private[sbt] object LibraryManagement { ): UpdateReport = { /* Resolve the module settings from the inputs. */ - def resolve(inputs: UpdateInputs): UpdateReport = { + def resolve: UpdateReport = { import sbt.util.ShowLines._ log.info(s"Updating $label...") val reportOrUnresolved: Either[UnresolvedWarning, UpdateReport] = - //try { lm.update(module, updateConfig, uwConfig, log) - // } catch { - // case e: Throwable => - // e.printStackTrace - // throw e - // } val report = reportOrUnresolved match { case Right(report0) => report0 case Left(unresolvedWarning) => @@ -95,12 +89,12 @@ private[sbt] object LibraryManagement { import sbt.librarymanagement.LibraryManagementCodec._ val cachedResolve = Tracked.lastOutput[UpdateInputs, UpdateReport](cache) { case (_, Some(out)) if upToDate(inChanged, out) => markAsCached(out) - case _ => resolve(updateInputs) + case _ => resolve } import scala.util.control.Exception.catching catching(classOf[NullPointerException], classOf[OutOfMemoryError]) .withApply { t => - val resolvedAgain = resolve(updateInputs) + val resolvedAgain = resolve val culprit = t.getClass.getSimpleName log.warn(s"Update task caching failed due to $culprit.") log.warn("Report the following output to sbt:") diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index c87d583ab..da6c2c8c1 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -61,7 +61,7 @@ private[sbt] object Load { val globalBase = getGlobalBase(state) val base = baseDirectory.getCanonicalFile val rawConfig = defaultPreGlobal(state, base, globalBase, log) - val config0 = defaultWithGlobal(state, base, rawConfig, globalBase, log) + val config0 = defaultWithGlobal(state, base, rawConfig, globalBase) val config = if (isPlugin) enableSbtPlugin(config0) else config0.copy(extraBuilds = topLevelExtras) (base, config) @@ -109,7 +109,7 @@ private[sbt] object Load { javaHome = None, scalac ) - val evalPluginDef = EvaluateTask.evalPluginDef(log) _ + val evalPluginDef: (BuildStructure, State) => PluginData = EvaluateTask.evalPluginDef _ val delegates = defaultDelegates val pluginMgmt = PluginManagement(loader) val inject = InjectSettings(injectGlobal(state), Nil, const(Nil)) @@ -145,7 +145,6 @@ private[sbt] object Load { base: File, rawConfig: LoadBuildConfiguration, globalBase: File, - log: Logger ): LoadBuildConfiguration = { val globalPluginsDir = getGlobalPluginsDirectory(state, globalBase) val withGlobal = loadGlobal(state, base, globalPluginsDir, rawConfig) diff --git a/main/src/main/scala/sbt/internal/LogManager.scala b/main/src/main/scala/sbt/internal/LogManager.scala index 9abec04c6..3d700667f 100644 --- a/main/src/main/scala/sbt/internal/LogManager.scala +++ b/main/src/main/scala/sbt/internal/LogManager.scala @@ -15,6 +15,7 @@ import Keys.{ logLevel, logManager, persistLogLevel, persistTraceLevel, sLog, tr import scala.Console.{ BLUE, RESET } import sbt.internal.util.{ AttributeKey, + ConsoleAppender, ConsoleOut, Settings, SuppressedTraceContext, @@ -105,7 +106,7 @@ object LogManager { def backgroundLog(data: Settings[Scope], state: State, task: ScopedKey[_]): ManagedLogger = { val console = screen(task, state) - LogManager.backgroundLog(data, state, task, console, relay(()), extra(task).toList) + LogManager.backgroundLog(data, state, task, console, relay(())) } } @@ -191,7 +192,6 @@ object LogManager { console: Appender, /* TODO: backed: Appender,*/ relay: Appender, - extra: List[Appender] ): ManagedLogger = { val scope = task.scope val screenLevel = getOr(logLevel.key, data, scope, state, Level.Info) @@ -253,7 +253,7 @@ object LogManager { private[this] def slog: Logger = Option(ref.get) getOrElse sys.error("Settings logger used after project was loaded.") - override val ansiCodesSupported = slog.ansiCodesSupported + override val ansiCodesSupported = ConsoleAppender.formatEnabledInEnv override def trace(t: => Throwable) = slog.trace(t) override def success(message: => String) = slog.success(message) override def log(level: Level.Value, message: => String) = slog.log(level, message) diff --git a/main/src/main/scala/sbt/internal/PluginsDebug.scala b/main/src/main/scala/sbt/internal/PluginsDebug.scala index 676a4caff..f908a29fe 100644 --- a/main/src/main/scala/sbt/internal/PluginsDebug.scala +++ b/main/src/main/scala/sbt/internal/PluginsDebug.scala @@ -57,7 +57,7 @@ private[sbt] class PluginsDebug( if (possible.nonEmpty) { val explained = possible.map(explainPluginEnable) val possibleString = - if (explained.size > 1) + if (explained.lengthCompare(1) > 0) explained.zipWithIndex .map { case (s, i) => s"$i. $s" } .mkString(s"Multiple plugins are available that can provide $notFoundKey:\n", "\n", "") @@ -111,7 +111,7 @@ private[sbt] class PluginsDebug( } private[this] def multi(strs: Seq[String]): String = - strs.mkString(if (strs.size > 4) "\n\t" else ", ") + strs.mkString(if (strs.lengthCompare(4) > 0) "\n\t" else ", ") } private[sbt] object PluginsDebug { @@ -377,7 +377,7 @@ private[sbt] object PluginsDebug { def explainPluginEnable(ps: PluginEnable): String = ps match { case PluginRequirements(plugin, - context, + _, blockingExcludes, enablingPlugins, extraEnabledPlugins, @@ -393,9 +393,8 @@ private[sbt] object PluginsDebug { note(willRemove(plugin, toBeRemoved.toList)) :: Nil parts.filterNot(_.isEmpty).mkString("\n") - case PluginImpossible(plugin, context, contradictions) => - pluginImpossible(plugin, contradictions) - case PluginActivated(plugin, context) => s"Plugin ${plugin.label} already activated." + case PluginImpossible(plugin, _, contradictions) => pluginImpossible(plugin, contradictions) + case PluginActivated(plugin, _) => s"Plugin ${plugin.label} already activated." } /** diff --git a/main/src/main/scala/sbt/internal/RelayAppender.scala b/main/src/main/scala/sbt/internal/RelayAppender.scala index 3e0daf474..2898b5bdc 100644 --- a/main/src/main/scala/sbt/internal/RelayAppender.scala +++ b/main/src/main/scala/sbt/internal/RelayAppender.scala @@ -26,7 +26,7 @@ class RelayAppender(name: String) val level = ConsoleAppender.toLevel(event.getLevel) val message = event.getMessage message match { - case o: ObjectMessage => appendEvent(level, o.getParameter) + case o: ObjectMessage => appendEvent(o.getParameter) case p: ParameterizedMessage => appendLog(level, p.getFormattedMessage) case r: RingBufferLogEvent => appendLog(level, r.getFormattedMessage) case _ => appendLog(level, message.toString) @@ -35,7 +35,7 @@ class RelayAppender(name: String) def appendLog(level: Level.Value, message: => String): Unit = { exchange.publishEventMessage(LogEvent(level.toString, message)) } - def appendEvent(level: Level.Value, event: AnyRef): Unit = + def appendEvent(event: AnyRef): Unit = event match { case x: StringEvent => { import JsonProtocol._ diff --git a/main/src/main/scala/sbt/internal/SettingCompletions.scala b/main/src/main/scala/sbt/internal/SettingCompletions.scala index d9bbafbff..3a3f8388e 100644 --- a/main/src/main/scala/sbt/internal/SettingCompletions.scala +++ b/main/src/main/scala/sbt/internal/SettingCompletions.scala @@ -15,7 +15,7 @@ import sbt.librarymanagement.Configuration import Project._ import Def.{ ScopedKey, Setting } import Scope.Global -import Types.{ const, idFun } +import Types.idFun import complete._ import DefaultParsers._ @@ -64,11 +64,10 @@ private[sbt] object SettingCompletions { setResult(session, r, redefined) } - /** Implementation of the `set` command that will reload the current project with `settings` appended to the current settings. */ - def setThis(s: State, - extracted: Extracted, - settings: Seq[Def.Setting[_]], - arg: String): SetResult = { + /** Implementation of the `set` command that will reload the current project with `settings` + * appended to the current settings. + */ + def setThis(extracted: Extracted, settings: Seq[Def.Setting[_]], arg: String): SetResult = { import extracted._ val append = Load.transformSettings(Load.projectScope(currentRef), currentRef.build, rootProject, settings) @@ -82,16 +81,19 @@ private[sbt] object SettingCompletions { private[this] def setResult( session: SessionSettings, r: Relation[ScopedKey[_], ScopedKey[_]], - redefined: Seq[Setting[_]])(implicit show: Show[ScopedKey[_]]): SetResult = { + redefined: Seq[Setting[_]], + )(implicit show: Show[ScopedKey[_]]): SetResult = { val redefinedKeys = redefined.map(_.key).toSet val affectedKeys = redefinedKeys.flatMap(r.reverse) def summary(verbose: Boolean): String = setSummary(redefinedKeys, affectedKeys, verbose) new SetResult(session, summary(true), summary(false)) } - private[this] def setSummary(redefined: Set[ScopedKey[_]], - affected: Set[ScopedKey[_]], - verbose: Boolean)(implicit display: Show[ScopedKey[_]]): String = { + private[this] def setSummary( + redefined: Set[ScopedKey[_]], + affected: Set[ScopedKey[_]], + verbose: Boolean, + )(implicit display: Show[ScopedKey[_]]): String = { val QuietLimit = 3 def strings(in: Set[ScopedKey[_]]): Seq[String] = in.toSeq.map(sk => display.show(sk)).sorted def lines(in: Seq[String]): (String, Boolean) = @@ -129,17 +131,17 @@ private[sbt] object SettingCompletions { * when there are fewer choices or tab is pressed multiple times. * The last part of the completion will generate a template for the value or function literal that will initialize the setting or task. */ - def settingParser(settings: Settings[Scope], - rawKeyMap: Map[String, AttributeKey[_]], - context: ResolvedProject): Parser[String] = { - val keyMap - : Map[String, AttributeKey[_]] = rawKeyMap.map { case (k, v) => (keyScalaID(k), v) }.toMap - def inputScopedKey(pred: AttributeKey[_] => Boolean): Parser[ScopedKey[_]] = - scopedKeyParser(keyMap.filter { case (_, k) => pred(k) }, settings, context) + def settingParser( + settings: Settings[Scope], + rawKeyMap: Map[String, AttributeKey[_]], + context: ResolvedProject, + ): Parser[String] = { + val keyMap: Map[String, AttributeKey[_]] = + rawKeyMap.map { case (k, v) => (keyScalaID(k), v) }.toMap val full = for { defineKey <- scopedKeyParser(keyMap, settings, context) a <- assign(defineKey) - _ <- valueParser(defineKey, a, inputScopedKey(keyFilter(defineKey.key))) + _ <- valueParser(defineKey, a) } yield () // parser is currently only for completion and the parsed data structures are not used @@ -167,9 +169,7 @@ private[sbt] object SettingCompletions { * Parser for the initialization expression for the assignment method `assign` on the key `sk`. * `scopedKeyP` is used to parse and complete the input keys for an initialization that depends on other keys. */ - def valueParser(sk: ScopedKey[_], - assign: Assign.Value, - scopedKeyP: Parser[ScopedKey[_]]): Parser[Seq[ScopedKey[_]]] = { + def valueParser(sk: ScopedKey[_], assign: Assign.Value): Parser[Seq[ScopedKey[_]]] = { val fullTypeString = keyTypeString(sk.key) val typeString = if (assignNoAppend(assign)) fullTypeString else "..." if (assign == Assign.Update) { @@ -181,14 +181,6 @@ private[sbt] object SettingCompletions { } } - /** - * For a setting definition `definingKey <<= (..., in, ...) { ... }`, - * `keyFilter(definingKey)(in)` returns true when `in` is an allowed input for `definingKey` based on whether they are settings or not. - * For example, if `definingKey` is for a setting, `in` may only be a setting itself. - */ - def keyFilter(definingKey: AttributeKey[_]): AttributeKey[_] => Boolean = - if (isSetting(definingKey)) isSetting _ else isTaskOrSetting _ - /** * Parser for a Scope for a `key` given the current project `context` and evaluated `settings`. * The completions are restricted to be more useful. Currently, this parser will suggest @@ -202,17 +194,20 @@ private[sbt] object SettingCompletions { val definedScopes = data.toSeq flatMap { case (scope, attrs) => if (attrs contains key) scope :: Nil else Nil } - scope(key, allScopes, definedScopes, context) + scope(allScopes, definedScopes, context) } - private[this] def scope(key: AttributeKey[_], - allScopes: Seq[Scope], - definedScopes: Seq[Scope], - context: ResolvedProject): Parser[Scope] = { - def axisParser[T](axis: Scope => ScopeAxis[T], - name: T => String, - description: T => Option[String], - label: String): Parser[ScopeAxis[T]] = { + private[this] def scope( + allScopes: Seq[Scope], + definedScopes: Seq[Scope], + context: ResolvedProject, + ): Parser[Scope] = { + def axisParser[T]( + axis: Scope => ScopeAxis[T], + name: T => String, + description: T => Option[String], + label: String, + ): Parser[ScopeAxis[T]] = { def getChoice(s: Scope): Seq[(String, T)] = axis(s) match { case Select(t) => (name(t), t) :: Nil case _ => Nil @@ -220,19 +215,23 @@ private[sbt] object SettingCompletions { def getChoices(scopes: Seq[Scope]): Map[String, T] = scopes.flatMap(getChoice).toMap val definedChoices: Set[String] = definedScopes.flatMap(s => axis(s).toOption.map(name)).toSet - val fullChoices: Map[String, T] = getChoices(allScopes.toSeq) + val fullChoices: Map[String, T] = getChoices(allScopes) val completions = fixedCompletions { (seen, level) => completeScope(seen, level, definedChoices, fullChoices)(description).toSet } - Act.optionalAxis(inParser ~> token(Space) ~> token(scalaID(fullChoices, label), completions), - This) + Act.optionalAxis( + inParser ~> token(Space) ~> token(scalaID(fullChoices, label), completions), + This, + ) } val configurations: Map[String, Configuration] = context.configurations.map(c => (configScalaID(c.name), c)).toMap - val configParser = axisParser[ConfigKey](_.config, - c => configScalaID(c.name), - ck => configurations.get(ck.name).map(_.description), - "configuration") + val configParser = axisParser[ConfigKey]( + _.config, + c => configScalaID(c.name), + ck => configurations.get(ck.name).map(_.description), + "configuration", + ) val taskParser = axisParser[AttributeKey[_]](_.task, k => keyScalaID(k.label), _.description, "task") val nonGlobal = (configParser ~ taskParser) map { case (c, t) => Scope(This, c, t, Zero) } @@ -242,8 +241,8 @@ private[sbt] object SettingCompletions { /** Parser for the assignment method (such as `:=`) for defining `key`. */ def assign(key: ScopedKey[_]): Parser[Assign.Value] = { - val completions = fixedCompletions { (seen, level) => - completeAssign(seen, level, key).toSet + val completions = fixedCompletions { (seen, _) => + completeAssign(seen, key).toSet } val identifier = Act.filterStrings(Op, Assign.values.map(_.toString), "assignment method") map Assign.withName token(Space) ~> token(optionallyQuoted(identifier), completions) @@ -267,7 +266,7 @@ private[sbt] object SettingCompletions { * Completions for an assignment method for `key` given the tab completion `level` and existing partial string `seen`. * This will filter possible assignment methods based on the underlying type of `key`, so that only `<<=` is shown for input tasks, for example. */ - def completeAssign(seen: String, level: Int, key: ScopedKey[_]): Seq[Completion] = { + def completeAssign(seen: String, key: ScopedKey[_]): Seq[Completion] = { val allowed: Iterable[Assign.Value] = if (appendable(key.key)) Assign.values else assignNoAppend @@ -284,7 +283,7 @@ private[sbt] object SettingCompletions { prominentCutoff: Int, detailLimit: Int): Seq[Completion] = completeSelectDescribed(seen, level, keys, detailLimit)(_.description) { - case (k, v) => v.rank <= prominentCutoff + case (_, v) => v.rank <= prominentCutoff } def completeScope[T]( @@ -293,17 +292,17 @@ private[sbt] object SettingCompletions { definedChoices: Set[String], allChoices: Map[String, T])(description: T => Option[String]): Seq[Completion] = completeSelectDescribed(seen, level, allChoices, 10)(description) { - case (k, v) => definedChoices(k) + case (k, _) => definedChoices(k) } def completeSelectDescribed[T](seen: String, level: Int, all: Map[String, T], detailLimit: Int)( description: T => Option[String])(prominent: (String, T) => Boolean): Seq[Completion] = { - val applicable = all.toSeq.filter { case (k, v) => k startsWith seen } + val applicable = all.toSeq.filter { case (k, _) => k startsWith seen } val prominentOnly = applicable filter { case (k, v) => prominent(k, v) } - val showAll = (level >= 3) || (level == 2 && prominentOnly.size <= detailLimit) || prominentOnly.isEmpty + val showAll = (level >= 3) || (level == 2 && prominentOnly.lengthCompare(detailLimit) <= 0) || prominentOnly.isEmpty val showKeys = if (showAll) applicable else prominentOnly - val showDescriptions = (level >= 2) || (showKeys.size <= detailLimit) + val showDescriptions = (level >= 2) || showKeys.lengthCompare(detailLimit) <= 0 completeDescribed(seen, showDescriptions, showKeys)(s => description(s).toList.mkString) } def completeDescribed[T](seen: String, showDescriptions: Boolean, in: Seq[(String, T)])( @@ -315,14 +314,11 @@ private[sbt] object SettingCompletions { val withDescriptions = in map { case (id, key) => (id, description(key)) } val padded = CommandUtil.aligned("", " ", withDescriptions) (padded, in).zipped.map { - case (line, (id, key)) => + case (line, (id, _)) => Completion.tokenDisplay(append = appendString(id), display = line + "\n") } } else - in map { - case (id, key) => - Completion.tokenDisplay(display = id, append = appendString(id)) - } + in map { case (id, _) => Completion.tokenDisplay(display = id, append = appendString(id)) } } /** @@ -364,18 +360,6 @@ private[sbt] object SettingCompletions { keyType(key)(mfToString, mfToString, mfToString) } - /** True if the `key` represents an input task, false if it represents a task or setting. */ - def isInputTask(key: AttributeKey[_]): Boolean = - keyType(key)(const(false), const(false), const(true)) - - /** True if the `key` represents a setting, false if it represents a task or an input task.*/ - def isSetting(key: AttributeKey[_]): Boolean = - keyType(key)(const(true), const(false), const(false)) - - /** True if the `key` represents a setting or task, false if it is for an input task. */ - def isTaskOrSetting(key: AttributeKey[_]): Boolean = - keyType(key)(const(true), const(true), const(false)) - /** True if the `key` represents a setting or task that may be appended using an assignment method such as `+=`. */ def appendable(key: AttributeKey[_]): Boolean = { val underlying = keyUnderlyingType(key).runtimeClass diff --git a/main/src/main/scala/sbt/internal/SettingGraph.scala b/main/src/main/scala/sbt/internal/SettingGraph.scala index 33f1b9a5c..8b74b51a4 100644 --- a/main/src/main/scala/sbt/internal/SettingGraph.scala +++ b/main/src/main/scala/sbt/internal/SettingGraph.scala @@ -99,7 +99,7 @@ object Graph { val withBar = childLines.zipWithIndex flatMap { case ((line, withBar), pos) if pos < (cs.size - 1) => (line +: withBar) map { insertBar(_, 2 * (level + 1)) } - case ((line, withBar), pos) if withBar.lastOption.getOrElse(line).trim != "" => + case ((line, withBar), _) if withBar.lastOption.getOrElse(line).trim != "" => (line +: withBar) ++ Vector(twoSpaces * (level + 1)) case ((line, withBar), _) => line +: withBar } diff --git a/main/src/main/scala/sbt/internal/TaskTimings.scala b/main/src/main/scala/sbt/internal/TaskTimings.scala index 2c39ffc43..51ccfcaa1 100644 --- a/main/src/main/scala/sbt/internal/TaskTimings.scala +++ b/main/src/main/scala/sbt/internal/TaskTimings.scala @@ -81,7 +81,7 @@ private[sbt] final class TaskTimings(shutdown: Boolean) extends ExecuteProgress[ println(s"Total time: $total $unit") import collection.JavaConverters._ def sumTimes(in: Seq[(Task[_], Long)]) = in.map(_._2).sum - val timingsByName = timings.asScala.toSeq.groupBy { case (t, time) => mappedName(t) } mapValues (sumTimes) + val timingsByName = timings.asScala.toSeq.groupBy { case (t, _) => mappedName(t) } mapValues (sumTimes) val times = timingsByName.toSeq .sortBy(_._2) .reverse diff --git a/main/src/main/scala/sbt/internal/parser/SbtParser.scala b/main/src/main/scala/sbt/internal/parser/SbtParser.scala index 6e89cdf28..d8ab96653 100644 --- a/main/src/main/scala/sbt/internal/parser/SbtParser.scala +++ b/main/src/main/scala/sbt/internal/parser/SbtParser.scala @@ -277,7 +277,7 @@ private[sbt] case class SbtParser(file: File, lines: Seq[String]) extends Parsed modifiedContent: String, imports: Seq[Tree] ): Seq[(String, Int)] = { - val toLineRange = imports map convertImport(modifiedContent) + val toLineRange = imports map convertImport val groupedByLineNumber = toLineRange.groupBy { case (_, lineNumber) => lineNumber } val mergedImports = groupedByLineNumber.map { case (l, seq) => (l, extractLine(modifiedContent, seq)) @@ -286,12 +286,10 @@ private[sbt] case class SbtParser(file: File, lines: Seq[String]) extends Parsed } /** - * - * @param modifiedContent - modifiedContent * @param t - tree - * @return ((start,end),lineNumber) + * @return ((start, end), lineNumber) */ - private def convertImport(modifiedContent: String)(t: Tree): ((Int, Int), Int) = { + private def convertImport(t: Tree): ((Int, Int), Int) = { val lineNumber = t.pos.line - 1 ((t.pos.start, t.pos.end), lineNumber) } diff --git a/main/src/main/scala/sbt/internal/parser/SbtRefactorings.scala b/main/src/main/scala/sbt/internal/parser/SbtRefactorings.scala index 14e09e2bd..1e3893672 100644 --- a/main/src/main/scala/sbt/internal/parser/SbtRefactorings.scala +++ b/main/src/main/scala/sbt/internal/parser/SbtRefactorings.scala @@ -57,10 +57,7 @@ private[sbt] object SbtRefactorings { commands.flatMap { case (_, command) => val map = toTreeStringMap(command) - map.flatMap { - case (name, statement) => - treesToReplacements(split, name, command) - } + map.flatMap { case (name, _) => treesToReplacements(split, name, command) } } private def treesToReplacements(split: SbtParser, name: String, command: Seq[String]) = diff --git a/main/src/main/scala/sbt/internal/server/Definition.scala b/main/src/main/scala/sbt/internal/server/Definition.scala index 4459861db..cd81e9650 100644 --- a/main/src/main/scala/sbt/internal/server/Definition.scala +++ b/main/src/main/scala/sbt/internal/server/Definition.scala @@ -227,7 +227,7 @@ private[sbt] object Definition { updateCache(StandardMain.cache)(cacheFile, useBinary) } - private[sbt] def getAnalyses(log: Logger): Future[Seq[Analysis]] = { + private[sbt] def getAnalyses: Future[Seq[Analysis]] = { import scalacache.modes.scalaFuture._ import scala.concurrent.ExecutionContext.Implicits.global StandardMain.cache @@ -260,7 +260,7 @@ private[sbt] object Definition { val LspDefinitionLogHead = "lsp-definition" import sjsonnew.support.scalajson.unsafe.CompactPrinter log.debug(s"$LspDefinitionLogHead json request: ${CompactPrinter(jsonDefinition)}") - lazy val analyses = getAnalyses(log) + lazy val analyses = getAnalyses val definition = getDefinition(jsonDefinition) definition .flatMap { definition => diff --git a/main/src/test/scala/Delegates.scala b/main/src/test/scala/Delegates.scala index 492cdcdd9..af77a8e23 100644 --- a/main/src/test/scala/Delegates.scala +++ b/main/src/test/scala/Delegates.scala @@ -47,7 +47,7 @@ object Delegates extends Properties("delegates") { } } property("Initial scope present with all combinations of Global axes") = allAxes( - globalCombinations) + (s, ds, _) => globalCombinations(s, ds)) property("initial scope first") = forAll { (keys: Keys) => allDelegates(keys) { (scope, ds) => @@ -66,6 +66,7 @@ object Delegates extends Properties("delegates") { all(f(s, ds, _.project), f(s, ds, _.config), f(s, ds, _.task), f(s, ds, _.extra)) } } + def allDelegates(keys: Keys)(f: (Scope, Seq[Scope]) => Prop): Prop = all(keys.scopes map { scope => val delegates = keys.env.delegates(scope) @@ -73,16 +74,20 @@ object Delegates extends Properties("delegates") { ("Delegates:\n\t" + delegates.map(scope => Scope.display(scope, "_")).mkString("\n\t")) |: f(scope, delegates) }: _*) + def alwaysZero(s: Scope, ds: Seq[Scope], axis: Scope => ScopeAxis[_]): Prop = (axis(s) != Zero) || all(ds map { d => (axis(d) == Zero): Prop }: _*) - def globalCombinations(s: Scope, ds: Seq[Scope], axis: Scope => ScopeAxis[_]): Prop = { - val mods = List[Scope => Scope](_.copy(project = Zero), - _.copy(config = Zero), - _.copy(task = Zero), - _.copy(extra = Zero)) + + def globalCombinations(s: Scope, ds: Seq[Scope]): Prop = { + val mods = List[Scope => Scope]( + _.copy(project = Zero), + _.copy(config = Zero), + _.copy(task = Zero), + _.copy(extra = Zero), + ) val modAndIdent = mods.map(_ :: idFun[Scope] :: Nil) def loop(cur: Scope, acc: List[Scope], rem: List[Seq[Scope => Scope]]): Seq[Scope] = diff --git a/main/src/test/scala/ParseKey.scala b/main/src/test/scala/ParseKey.scala index 2d6991af7..1f17d9288 100644 --- a/main/src/test/scala/ParseKey.scala +++ b/main/src/test/scala/ParseKey.scala @@ -55,9 +55,9 @@ object ParseKey extends Properties("Key parser test") { ("Mask: " + mask) |: ("Current: " + structure.current) |: parse(structure, string) { - case Left(err) => false - case Right(sk) if hasZeroConfig => true - case Right(sk) => sk.scope.project == Select(structure.current) + case Left(_) => false + case Right(_) if hasZeroConfig => true + case Right(sk) => sk.scope.project == Select(structure.current) } } @@ -70,7 +70,7 @@ object ParseKey extends Properties("Key parser test") { ("Key: " + displayPedantic(key)) |: ("Mask: " + mask) |: parse(structure, string) { - case Left(err) => false + case Left(_) => false case Right(sk) => sk.scope.task == Zero } } @@ -88,7 +88,7 @@ object ParseKey extends Properties("Key parser test") { ("Expected configuration: " + resolvedConfig.map(_.name)) |: parse(structure, string) { case Right(sk) => (sk.scope.config == resolvedConfig) || (sk.scope == Scope.GlobalScope) - case Left(err) => false + case Left(_) => false } } @@ -117,7 +117,7 @@ object ParseKey extends Properties("Key parser test") { ("Expected: " + displayFull(expected)) |: ("Mask: " + mask) |: parse(structure, s) { - case Left(err) => false + case Left(_) => false case Right(sk) => (s"${sk}.key == ${expected}.key: ${sk.key == expected.key}") |: (s"${sk.scope} == ${expected.scope}: ${Scope.equal(sk.scope, expected.scope, mask)}") |: diff --git a/main/src/test/scala/PluginsTest.scala b/main/src/test/scala/PluginsTest.scala index 0062a7782..620e6941c 100644 --- a/main/src/test/scala/PluginsTest.scala +++ b/main/src/test/scala/PluginsTest.scala @@ -39,18 +39,18 @@ object PluginsTest extends Specification { } "throw an AutoPluginException on conflicting requirements" in { deducePlugin(S, log) must throwAn[AutoPluginException]( - message = """Contradiction in enabled plugins: - - requested: sbt.AI\$S - - enabled: sbt.AI\$S, sbt.AI\$Q, sbt.AI\$R, sbt.AI\$B, sbt.AI\$A - - conflict: sbt.AI\$R is enabled by sbt.AI\$Q; excluded by sbt.AI\$S""") + message = s"""Contradiction in enabled plugins: + - requested: sbt.AI\\$$S + - enabled: sbt.AI\\$$S, sbt.AI\\$$Q, sbt.AI\\$$R, sbt.AI\\$$B, sbt.AI\\$$A + - conflict: sbt.AI\\$$R is enabled by sbt.AI\\$$Q; excluded by sbt.AI\\$$S""") } "generates a detailed report on conflicting requirements" in { - deducePlugin(T && U, log) must throwAn[AutoPluginException](message = - """Contradiction in enabled plugins: - - requested: sbt.AI\$T && sbt.AI\$U - - enabled: sbt.AI\$U, sbt.AI\$T, sbt.AI\$A, sbt.AI\$Q, sbt.AI\$R, sbt.AI\$B - - conflict: sbt.AI\$Q is enabled by sbt.AI\$A && sbt.AI\$B; required by sbt.AI\$T, sbt.AI\$R; excluded by sbt.AI\$U - - conflict: sbt.AI\$R is enabled by sbt.AI\$Q; excluded by sbt.AI\$T""") + deducePlugin(T && U, log) must throwAn[AutoPluginException]( + message = s"""Contradiction in enabled plugins: + - requested: sbt.AI\\$$T && sbt.AI\\$$U + - enabled: sbt.AI\\$$U, sbt.AI\\$$T, sbt.AI\\$$A, sbt.AI\\$$Q, sbt.AI\\$$R, sbt.AI\\$$B + - conflict: sbt.AI\\$$Q is enabled by sbt.AI\\$$A && sbt.AI\\$$B; required by sbt.AI\\$$T, sbt.AI\\$$R; excluded by sbt.AI\\$$U + - conflict: sbt.AI\\$$R is enabled by sbt.AI\\$$Q; excluded by sbt.AI\\$$T""") } } } diff --git a/main/src/test/scala/sbt/internal/parser/ErrorSpec.scala b/main/src/test/scala/sbt/internal/parser/ErrorSpec.scala index 6d7cc7b42..845baccd7 100644 --- a/main/src/test/scala/sbt/internal/parser/ErrorSpec.scala +++ b/main/src/test/scala/sbt/internal/parser/ErrorSpec.scala @@ -77,8 +77,7 @@ class ErrorSpec extends AbstractSpec { case exception: MessageOnlyException => val error = exception.getMessage """(\d+)""".r.findFirstIn(error) match { - case Some(x) => - true + case Some(_) => true case None => println(s"Number not found in $error") false diff --git a/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala b/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala index 7acf7955f..a80819af1 100644 --- a/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala +++ b/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala @@ -122,7 +122,7 @@ object SettingQueryTest extends org.specs2.mutable.Specification { .put(globalBaseDirectory, globalDirFile) val config0 = defaultPreGlobal(state, baseFile, globalDirFile, state.log) - val config = defaultWithGlobal(state, baseFile, config0, globalDirFile, state.log) + val config = defaultWithGlobal(state, baseFile, config0, globalDirFile) val buildUnit: BuildUnit = { val loadedPlugins: LoadedPlugins = diff --git a/sbt/src/sbt-test/actions/run-task/A.scala b/sbt/src/sbt-test/actions/run-task/A.scala index f12139e7b..f003b3e20 100644 --- a/sbt/src/sbt-test/actions/run-task/A.scala +++ b/sbt/src/sbt-test/actions/run-task/A.scala @@ -1,7 +1,6 @@ object A { - def main(args: Array[String]) = - { - assert(args(0).toInt == args(1).toInt) - assert(java.lang.Boolean.getBoolean("sbt.check.forked")) - } + def main(args: Array[String]) = { + assert(args(0).toInt == args(1).toInt) + assert(java.lang.Boolean.getBoolean("sbt.check.forked")) + } } diff --git a/sbt/src/sbt-test/project/session-update-from-cmd/project/Common.scala b/sbt/src/sbt-test/project/session-update-from-cmd/project/Common.scala index f977fa81a..423d628f6 100644 --- a/sbt/src/sbt-test/project/session-update-from-cmd/project/Common.scala +++ b/sbt/src/sbt-test/project/session-update-from-cmd/project/Common.scala @@ -11,7 +11,7 @@ object Common { val UpdateK1 = Command.command("UpdateK1") { st: State => val ex = Project extract st import ex._ - val session2 = BuiltinCommands.setThis(st, ex, Seq(k1 := {}), """k1 := { + val session2 = BuiltinCommands.setThis(ex, Seq(k1 := {}), """k1 := { |// |// |}""".stripMargin).session @@ -24,7 +24,7 @@ object Common { val UpdateK3 = Command.command("UpdateK3") { st: State => val ex = Project extract st import ex._ - val session2 = BuiltinCommands.setThis(st, ex, Seq(k3 := {}), """k3 := { + val session2 = BuiltinCommands.setThis(ex, Seq(k3 := {}), """k3 := { |// |// |}""".stripMargin).session From 657ff56011c6a2cc498ad32cd3b2b8d932b36849 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 6 Dec 2017 16:44:56 +0000 Subject: [PATCH 024/356] Remove all warnings from scriptedSbtProj --- scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala index 29b4f2258..3e2798300 100644 --- a/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala +++ b/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala @@ -385,8 +385,11 @@ class ScriptedRunner { instances: Int ): Unit = { val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, bootProperties, launchOpts) + val sbtVersion = bootProperties.getName.dropWhile(!_.isDigit).dropRight(".jar".length) + val accept = isTestCompatible(resourceBaseDirectory, sbtVersion) _ // The scripted tests mapped to the inputs that the user wrote after `scripted`. - val scriptedTests = get(tests, resourceBaseDirectory, logger).map(st => (st.group, st.name)) + val scriptedTests = + get(tests, resourceBaseDirectory, accept, logger).map(st => (st.group, st.name)) val scriptedRunners = runner.batchScriptedRunner(scriptedTests, prescripted, instances, logger) val parallelRunners = scriptedRunners.toParArray val pool = new java.util.concurrent.ForkJoinPool(instances) From b8bb8fe18509462a3f98d5118315c7ff11e2285f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 6 Dec 2017 12:26:05 +0000 Subject: [PATCH 025/356] Remove all warnings from sbtProj --- sbt/src/main/scala/Import.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/sbt/src/main/scala/Import.scala b/sbt/src/main/scala/Import.scala index 3a0adc48a..170a39e4a 100644 --- a/sbt/src/main/scala/Import.scala +++ b/sbt/src/main/scala/Import.scala @@ -67,6 +67,7 @@ trait Import { type Cache[I, O] = sbt.util.Cache[I, O] val Cache = sbt.util.Cache val CacheImplicits = sbt.util.CacheImplicits + @deprecated("Use Tracked.inputChanged and Tracked.outputChanged instead", "1.0.1") type Changed[O] = sbt.util.Changed[O] type ChangeReport[T] = sbt.util.ChangeReport[T] val ChangeReport = sbt.util.ChangeReport From 43a9bd25f0e300e97e2d68f9d672d5e291a974c9 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 6 Dec 2017 14:53:16 +0000 Subject: [PATCH 026/356] Remove all warnings from scriptedPluginProj --- scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala b/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala index 3e38a26ac..4788e0186 100644 --- a/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala +++ b/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala @@ -13,7 +13,6 @@ import sbt.internal.util.complete.{ Parser, DefaultParsers } import sbt.internal.inc.classpath.ClasspathUtilities import sbt.internal.inc.ModuleUtilities import java.lang.reflect.Method -import sbt.librarymanagement.CrossVersion.partialVersion object ScriptedPlugin extends AutoPlugin { override def requires = plugins.JvmPlugin @@ -40,7 +39,7 @@ object ScriptedPlugin extends AutoPlugin { scriptedSbt := (sbtVersion in pluginCrossBuild).value, sbtLauncher := getJars(ScriptedLaunchConf).map(_.get.head).value, sbtTestDirectory := sourceDirectory.value / "sbt-test", - libraryDependencies ++= (partialVersion(scriptedSbt.value) match { + libraryDependencies ++= (CrossVersion.partialVersion(scriptedSbt.value) match { case Some((0, 13)) => Seq( "org.scala-sbt" % "scripted-sbt" % scriptedSbt.value % ScriptedConf, @@ -51,13 +50,15 @@ object ScriptedPlugin extends AutoPlugin { "org.scala-sbt" %% "scripted-sbt" % scriptedSbt.value % ScriptedConf, "org.scala-sbt" % "sbt-launch" % scriptedSbt.value % ScriptedLaunchConf ) + case Some((x, y)) => sys error s"Unknown sbt version ${scriptedSbt.value} ($x.$y)" + case None => sys error s"Unknown sbt version ${scriptedSbt.value}" }), scriptedBufferLog := true, scriptedClasspath := getJars(ScriptedConf).value, scriptedTests := scriptedTestsTask.value, scriptedRun := scriptedRunTask.value, scriptedDependencies := { - def use[A](x: A*): Unit = () // avoid unused warnings + def use[A](@deprecated("unused", "") x: A*): Unit = () // avoid unused warnings val analysis = (compile in Test).value val pub = (publishLocal).value use(analysis, pub) From 5f0852818bcda9d03a27afa7cd2bac0a6fdb2f70 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 15 Dec 2017 01:43:12 +0000 Subject: [PATCH 027/356] Add project id to watching message We redefine watchingMessage in project scope so we can use thisProjectRef to make the watching message more precise. Fixes #2038 --- main-command/src/main/scala/sbt/Watched.scala | 13 +++++++++---- main/src/main/scala/sbt/Defaults.scala | 1 + notes/1.2.0/add-project-id-to-watching-message.md | 12 ++++++++++++ notes/{1.0.2 => }/sample.md | 0 4 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 notes/1.2.0/add-project-id-to-watching-message.md rename notes/{1.0.2 => }/sample.md (100%) diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index fa1dee03f..1960a337b 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -44,8 +44,13 @@ trait Watched { } object Watched { - val defaultWatchingMessage - : WatchState => String = _.count + ". Waiting for source changes... (press enter to interrupt)" + val defaultWatchingMessage: WatchState => String = ws => + s"${ws.count}. Waiting for source changes... (press enter to interrupt)" + + def projectWatchingMessage(projectId: String): WatchState => String = + ws => + s"${ws.count}. Waiting for source changes in project $projectId... (press enter to interrupt)" + val defaultTriggeredMessage: WatchState => String = const("") val clearWhenTriggered: WatchState => String = const(clearScreen) def clearScreen: String = "\u001b[2J\u001b[0;0H" @@ -70,8 +75,8 @@ object Watched { * @param base The base directory from which to include files. * @return An instance of `Source`. */ - def apply(base: File): Source = - apply(base, AllPassFilter, NothingFilter) + def apply(base: File): Source = apply(base, AllPassFilter, NothingFilter) + } private[this] class AWatched extends Watched diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 1f2dcfd3a..912cd2a50 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -530,6 +530,7 @@ object Defaults extends BuildCommon { clean := (Def.task { IO.delete(cleanFiles.value) } tag (Tags.Clean)).value, consoleProject := consoleProjectTask.value, watchTransitiveSources := watchTransitiveSourcesTask.value, + watchingMessage := Watched.projectWatchingMessage(thisProjectRef.value.project), watch := watchSetting.value ) diff --git a/notes/1.2.0/add-project-id-to-watching-message.md b/notes/1.2.0/add-project-id-to-watching-message.md new file mode 100644 index 000000000..7c6a1bf57 --- /dev/null +++ b/notes/1.2.0/add-project-id-to-watching-message.md @@ -0,0 +1,12 @@ +[@dwijnand]: https://github.com/dwijnand + +[#2038]: https://github.com/sbt/sbt/issues/2038 +[#3813]: https://github.com/sbt/sbt/pull/3813 + +### Fixes with compatibility implications + +### Improvements + +- Add the current project's id to `~`'s watching message. [#2038][]/[#3813][] by [@dwijnand][] + +### Bug fixes diff --git a/notes/1.0.2/sample.md b/notes/sample.md similarity index 100% rename from notes/1.0.2/sample.md rename to notes/sample.md From 8bd522511da896e742184eb0ff7ed901fe008540 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 15 Dec 2017 08:55:53 +0000 Subject: [PATCH 028/356] Make CaffeineCache a lazy val This is to avoid it initialising Log4J2 (via SLF4J), which we initialise ourselves programmatically in LogExchange. Also there's no need to removeAll in initialState. Fixes #3787 --- main/src/main/scala/sbt/Main.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 09d668c4b..6c605056e 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -103,7 +103,7 @@ object StandardMain { private[sbt] lazy val exchange = new CommandExchange() import scalacache._ import scalacache.caffeine._ - private[sbt] val cache: Cache[Any] = CaffeineCache[Any] + private[sbt] lazy val cache: Cache[Any] = CaffeineCache[Any] def runManaged(s: State): xsbti.MainResult = { val previous = TrapExit.installManager() @@ -135,9 +135,6 @@ object StandardMain { Exec(x, None) } val initAttrs = BuiltinCommands.initialAttributes - import scalacache.modes.scalaFuture._ - import scala.concurrent.ExecutionContext.Implicits.global - cache.removeAll() val s = State( configuration, initialDefinitions, From 4668faff7c48e30753cedbb554017cc03a3d4477 Mon Sep 17 00:00:00 2001 From: Michael Pollmeier Date: Thu, 23 Nov 2017 11:52:07 +1300 Subject: [PATCH 029/356] don't require publishTo specified if publishArtifact is `false` Even with `publishArtifact := false` the user is still forced to define a (dummy) resolver that's never used, e.g. `publishTo := { Some("publishMeNot" at "https://publish/me/not") }` Otherwise the following error is thrown: ``` publish [error] java.lang.RuntimeException: Repository for publishing is not specified. [error] at scala.sys.package$.error(package.scala:27) [error] at sbt.Classpaths$.$anonfun$getPublishTo$1(Defaults.scala:2436) [error] at scala.Option.getOrElse(Option.scala:121) [error] at sbt.Classpaths$.getPublishTo(Defaults.scala:2436) [error] at sbt.Classpaths$.$anonfun$ivyBaseSettings$48(Defaults.scala:1917) ``` --- main/src/main/scala/sbt/Defaults.scala | 6 ++++-- .../publishTo-not-required-if-not-publishing.md | 12 ++++++++++++ sbt/src/sbt-test/project/no-publish/build.sbt | 3 +++ sbt/src/sbt-test/project/no-publish/test | 1 + 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 notes/1.2.0/publishTo-not-required-if-not-publishing.md create mode 100644 sbt/src/sbt-test/project/no-publish/build.sbt create mode 100644 sbt/src/sbt-test/project/no-publish/test diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 1f2dcfd3a..3bc4a3a20 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1935,8 +1935,10 @@ object Classpaths { if (isSnapshot.value) "integration" else "release", ivyConfigurations.value.map(c => ConfigRef(c.name)).toVector, packagedArtifacts.in(publish).value.toVector, - checksums.in(publish).value.toVector, - getPublishTo(publishTo.value).name, + checksums.in(publish).value.toVector, { //resolvername: not required if publishTo is false + val publishToOption = publishTo.value + if (publishArtifact.value) getPublishTo(publishToOption).name else "local" + }, ivyLoggingLevel.value, isSnapshot.value ) diff --git a/notes/1.2.0/publishTo-not-required-if-not-publishing.md b/notes/1.2.0/publishTo-not-required-if-not-publishing.md new file mode 100644 index 000000000..b2c4ca842 --- /dev/null +++ b/notes/1.2.0/publishTo-not-required-if-not-publishing.md @@ -0,0 +1,12 @@ + +[@mpollmeier]: https://github.com/mpollmeier + +[#3760]: https://github.com/sbt/sbt/pull/3760 + +### Fixes with compatibility implications + +### Improvements + +### Bug fixes + +- Remove requirement on `publishTo` if `publishArtifact` is false. [#3760][] by [@mpollmeier][] diff --git a/sbt/src/sbt-test/project/no-publish/build.sbt b/sbt/src/sbt-test/project/no-publish/build.sbt new file mode 100644 index 000000000..36e1cdfd8 --- /dev/null +++ b/sbt/src/sbt-test/project/no-publish/build.sbt @@ -0,0 +1,3 @@ +// this is supposed to do nothing, and more importantly: not fail +// https://github.com/sbt/sbt/pull/3760 +publishArtifact := false diff --git a/sbt/src/sbt-test/project/no-publish/test b/sbt/src/sbt-test/project/no-publish/test new file mode 100644 index 000000000..c78ab3f9f --- /dev/null +++ b/sbt/src/sbt-test/project/no-publish/test @@ -0,0 +1 @@ +> publish \ No newline at end of file From fa2c48ed84a13d5d3bb4e233a232bc22ddb330a9 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 15 Dec 2017 15:44:21 +0000 Subject: [PATCH 030/356] Drop 0.14.0 references Fixes #3411 --- main/src/main/scala/sbt/internal/PluginDiscovery.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/PluginDiscovery.scala b/main/src/main/scala/sbt/internal/PluginDiscovery.scala index fb82ea5e1..af2097298 100644 --- a/main/src/main/scala/sbt/internal/PluginDiscovery.scala +++ b/main/src/main/scala/sbt/internal/PluginDiscovery.scala @@ -65,7 +65,7 @@ object PluginDiscovery { new DiscoveredNames(discover[AutoPlugin], discover[BuildDef]) } - // TODO: for 0.14.0, consider consolidating into a single file, which would make the classpath search 4x faster + // TODO: consider consolidating into a single file, which would make the classpath search 4x faster /** Writes discovered module `names` to zero or more files in `dir` as per [[writeDescriptor]] and returns the list of files written. */ def writeDescriptors(names: DiscoveredNames, dir: File): Seq[File] = { import Paths._ From 699b53262a627e809956af3c9e025c10d3930eac Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Fri, 1 Dec 2017 05:10:31 +0100 Subject: [PATCH 031/356] Convert lastModified() to sbt.io.Milli.getModifiedTime(), more precise --- main-actions/src/main/scala/sbt/compiler/Eval.scala | 4 +++- main/src/main/scala/sbt/Defaults.scala | 3 ++- main/src/main/scala/sbt/internal/LibraryManagement.scala | 3 ++- project/SiteMap.scala | 2 ++ project/Util.scala | 2 ++ 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/main-actions/src/main/scala/sbt/compiler/Eval.scala b/main-actions/src/main/scala/sbt/compiler/Eval.scala index 3bd69b09a..829a8c777 100644 --- a/main-actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main-actions/src/main/scala/sbt/compiler/Eval.scala @@ -21,6 +21,7 @@ import java.net.URLClassLoader import Eval.{ getModule, getValue, WrapValName } import sbt.io.{ DirectoryFilter, FileFilter, GlobFilter, Hash, IO, Path } +import sbt.io.Milli.getModifiedTime // TODO: provide a way to cleanup backing directory @@ -485,7 +486,8 @@ private[sbt] object Eval { def filesModifiedBytes(fs: Array[File]): Array[Byte] = if (fs eq null) filesModifiedBytes(Array[File]()) else seqBytes(fs)(fileModifiedBytes) def fileModifiedBytes(f: File): Array[Byte] = - (if (f.isDirectory) filesModifiedBytes(f listFiles classDirFilter) else bytes(f.lastModified)) ++ + (if (f.isDirectory) filesModifiedBytes(f listFiles classDirFilter) + else bytes(getModifiedTime(f))) ++ bytes(f.getAbsolutePath) def fileExistsBytes(f: File): Array[Byte] = bytes(f.exists) ++ diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 1f2dcfd3a..88fbdcca0 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -47,6 +47,7 @@ import sbt.io.{ DirectoryFilter, Hash }, Path._ +import sbt.io.Milli.getModifiedTime import sbt.librarymanagement.Artifact.{ DocClassifier, SourceClassifier } import sbt.librarymanagement.Configurations.{ Compile, @@ -2315,7 +2316,7 @@ object Classpaths { case Some(period) => val fullUpdateOutput = cacheDirectory / "out" val now = System.currentTimeMillis - val diff = now - fullUpdateOutput.lastModified() + val diff = now - getModifiedTime(fullUpdateOutput) val elapsedDuration = new FiniteDuration(diff, TimeUnit.MILLISECONDS) fullUpdateOutput.exists() && elapsedDuration > period } diff --git a/main/src/main/scala/sbt/internal/LibraryManagement.scala b/main/src/main/scala/sbt/internal/LibraryManagement.scala index a245344dc..a594d0439 100644 --- a/main/src/main/scala/sbt/internal/LibraryManagement.scala +++ b/main/src/main/scala/sbt/internal/LibraryManagement.scala @@ -13,6 +13,7 @@ import sbt.internal.librarymanagement._ import sbt.librarymanagement._ import sbt.librarymanagement.syntax._ import sbt.util.{ CacheStore, CacheStoreFactory, Logger, Tracked } +import sbt.io.Milli.getModifiedTime private[sbt] object LibraryManagement { @@ -126,7 +127,7 @@ private[sbt] object LibraryManagement { } private[this] def fileUptodate(file: File, stamps: Map[File, Long]): Boolean = - stamps.get(file).forall(_ == file.lastModified) + stamps.get(file).forall(_ == getModifiedTime(file)) private[sbt] def transitiveScratch( lm: DependencyResolution, diff --git a/project/SiteMap.scala b/project/SiteMap.scala index ed1f5239e..2cfb5d8ea 100644 --- a/project/SiteMap.scala +++ b/project/SiteMap.scala @@ -68,6 +68,8 @@ object SiteMap { // generates a string suitable for a sitemap file representing the last modified time of the given File private[this] def lastModifiedString(f: File): String = { val formatter = new java.text.SimpleDateFormat("yyyy-MM-dd") + // TODO: replace lastModified() with sbt.io.Milli.getModifiedTime(), once the build + // has been upgraded to a version of sbt that includes sbt.io.Milli. formatter.format(new java.util.Date(f.lastModified)) } // writes the provided XML node to `output` and then gzips it to `gzipped` if `gzip` is true diff --git a/project/Util.scala b/project/Util.scala index 89556cd2e..0b87b02db 100644 --- a/project/Util.scala +++ b/project/Util.scala @@ -105,6 +105,8 @@ object Util { val timestamp = formatter.format(new Date) val content = versionLine(version) + "\ntimestamp=" + timestamp val f = dir / "xsbt.version.properties" + // TODO: replace lastModified() with sbt.io.Milli.getModifiedTime(), once the build + // has been upgraded to a version of sbt that includes sbt.io.Milli. if (!f.exists || f.lastModified < lastCompilationTime(analysis) || !containsVersion(f, version)) { s.log.info("Writing version information to " + f + " :\n" + content) IO.write(f, content) From 8c7b781d3f0d41e373c13f69c7086716fc3b085d Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Mon, 4 Dec 2017 22:25:55 +0100 Subject: [PATCH 032/356] Moved Milli._ to IO. --- main-actions/src/main/scala/sbt/compiler/Eval.scala | 2 +- main/src/main/scala/sbt/Defaults.scala | 2 +- main/src/main/scala/sbt/internal/LibraryManagement.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/main-actions/src/main/scala/sbt/compiler/Eval.scala b/main-actions/src/main/scala/sbt/compiler/Eval.scala index 829a8c777..5620306e3 100644 --- a/main-actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main-actions/src/main/scala/sbt/compiler/Eval.scala @@ -21,7 +21,7 @@ import java.net.URLClassLoader import Eval.{ getModule, getValue, WrapValName } import sbt.io.{ DirectoryFilter, FileFilter, GlobFilter, Hash, IO, Path } -import sbt.io.Milli.getModifiedTime +import sbt.io.IO.getModifiedTime // TODO: provide a way to cleanup backing directory diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 88fbdcca0..abe66d6be 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -47,7 +47,7 @@ import sbt.io.{ DirectoryFilter, Hash }, Path._ -import sbt.io.Milli.getModifiedTime +import sbt.io.IO.getModifiedTime import sbt.librarymanagement.Artifact.{ DocClassifier, SourceClassifier } import sbt.librarymanagement.Configurations.{ Compile, diff --git a/main/src/main/scala/sbt/internal/LibraryManagement.scala b/main/src/main/scala/sbt/internal/LibraryManagement.scala index a594d0439..2849ce17b 100644 --- a/main/src/main/scala/sbt/internal/LibraryManagement.scala +++ b/main/src/main/scala/sbt/internal/LibraryManagement.scala @@ -13,7 +13,7 @@ import sbt.internal.librarymanagement._ import sbt.librarymanagement._ import sbt.librarymanagement.syntax._ import sbt.util.{ CacheStore, CacheStoreFactory, Logger, Tracked } -import sbt.io.Milli.getModifiedTime +import sbt.io.IO.getModifiedTime private[sbt] object LibraryManagement { From 2129f8ceb550fb26746f8a9121de6214b1ecb420 Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Tue, 5 Dec 2017 02:44:46 +0100 Subject: [PATCH 033/356] Use IO.copyModifiedTime() in place of IO.copyLastModified() --- main-actions/src/main/scala/sbt/Sync.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main-actions/src/main/scala/sbt/Sync.scala b/main-actions/src/main/scala/sbt/Sync.scala index ec2eb73e3..17f169e47 100644 --- a/main-actions/src/main/scala/sbt/Sync.scala +++ b/main-actions/src/main/scala/sbt/Sync.scala @@ -66,7 +66,7 @@ object Sync { else if (!target.exists) // we don't want to update the last modified time of an existing directory { IO.createDirectory(target) - IO.copyLastModified(source, target) + IO.copyModifiedTime(source, target) } def noDuplicateTargets(relation: Relation[File, File]): Unit = { From 295bcff85156921c8eea2904b1d9f883d41f6d1d Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Thu, 7 Dec 2017 01:50:42 +0100 Subject: [PATCH 034/356] Hash calculation may throw exception w/ getModifiedTime(); fix In Eval there is a calculation of hashes by scanning the elements of classpath, and getting the last modification time of each directory. When lastModified() was in use, non-existent elements would return 0L, but getModifiedTime() will throw an exception instead (like getLastModifiedTime(), incidentally). So, we catch the FileNotFoundException and return 0L now as well. --- main-actions/src/main/scala/sbt/compiler/Eval.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/main-actions/src/main/scala/sbt/compiler/Eval.scala b/main-actions/src/main/scala/sbt/compiler/Eval.scala index 5620306e3..a628e5959 100644 --- a/main-actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main-actions/src/main/scala/sbt/compiler/Eval.scala @@ -487,8 +487,12 @@ private[sbt] object Eval { if (fs eq null) filesModifiedBytes(Array[File]()) else seqBytes(fs)(fileModifiedBytes) def fileModifiedBytes(f: File): Array[Byte] = (if (f.isDirectory) filesModifiedBytes(f listFiles classDirFilter) - else bytes(getModifiedTime(f))) ++ - bytes(f.getAbsolutePath) + else + bytes(try { + getModifiedTime(f) + } catch { + case _: java.io.FileNotFoundException => 0L + })) ++ bytes(f.getAbsolutePath) def fileExistsBytes(f: File): Array[Byte] = bytes(f.exists) ++ bytes(f.getAbsolutePath) From 46d6b01f79e5c1e2b1d295ec53a040375fb1d753 Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Wed, 13 Dec 2017 15:09:52 +0100 Subject: [PATCH 035/356] Use copyLastModified() in Sync.scala It turns out the code in Sync.scala activiely tries to transfer the invalid modifed time of non-existent files in the source directory over the time of the target file, which may or may not exist. In case it exists, the modification time is set to January 1, 1970. This is arguably a bug in Sync, which should be adjusted to better handle mappings with source files that do not exist. For now, however, we preserve the current behavior, using the deprecated copyLastModified(). --- main-actions/src/main/scala/sbt/Sync.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main-actions/src/main/scala/sbt/Sync.scala b/main-actions/src/main/scala/sbt/Sync.scala index 17f169e47..ec2eb73e3 100644 --- a/main-actions/src/main/scala/sbt/Sync.scala +++ b/main-actions/src/main/scala/sbt/Sync.scala @@ -66,7 +66,7 @@ object Sync { else if (!target.exists) // we don't want to update the last modified time of an existing directory { IO.createDirectory(target) - IO.copyModifiedTime(source, target) + IO.copyLastModified(source, target) } def noDuplicateTargets(relation: Relation[File, File]): Unit = { From ae99922101560c30e45af715bdfd2e63b4dbb3dd Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 13 Dec 2017 16:54:21 +0000 Subject: [PATCH 036/356] Use IO.getModified over importing the method --- main-actions/src/main/scala/sbt/compiler/Eval.scala | 8 ++------ main/src/main/scala/sbt/Defaults.scala | 3 +-- main/src/main/scala/sbt/internal/LibraryManagement.scala | 4 ++-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/main-actions/src/main/scala/sbt/compiler/Eval.scala b/main-actions/src/main/scala/sbt/compiler/Eval.scala index a628e5959..f756d92b8 100644 --- a/main-actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main-actions/src/main/scala/sbt/compiler/Eval.scala @@ -21,7 +21,6 @@ import java.net.URLClassLoader import Eval.{ getModule, getValue, WrapValName } import sbt.io.{ DirectoryFilter, FileFilter, GlobFilter, Hash, IO, Path } -import sbt.io.IO.getModifiedTime // TODO: provide a way to cleanup backing directory @@ -488,11 +487,8 @@ private[sbt] object Eval { def fileModifiedBytes(f: File): Array[Byte] = (if (f.isDirectory) filesModifiedBytes(f listFiles classDirFilter) else - bytes(try { - getModifiedTime(f) - } catch { - case _: java.io.FileNotFoundException => 0L - })) ++ bytes(f.getAbsolutePath) + bytes(try IO.getModifiedTime(f) catch { case _: java.io.FileNotFoundException => 0L })) ++ + bytes(f.getAbsolutePath) def fileExistsBytes(f: File): Array[Byte] = bytes(f.exists) ++ bytes(f.getAbsolutePath) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index abe66d6be..b006c115d 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -47,7 +47,6 @@ import sbt.io.{ DirectoryFilter, Hash }, Path._ -import sbt.io.IO.getModifiedTime import sbt.librarymanagement.Artifact.{ DocClassifier, SourceClassifier } import sbt.librarymanagement.Configurations.{ Compile, @@ -2316,7 +2315,7 @@ object Classpaths { case Some(period) => val fullUpdateOutput = cacheDirectory / "out" val now = System.currentTimeMillis - val diff = now - getModifiedTime(fullUpdateOutput) + val diff = now - IO.getModifiedTime(fullUpdateOutput) val elapsedDuration = new FiniteDuration(diff, TimeUnit.MILLISECONDS) fullUpdateOutput.exists() && elapsedDuration > period } diff --git a/main/src/main/scala/sbt/internal/LibraryManagement.scala b/main/src/main/scala/sbt/internal/LibraryManagement.scala index 2849ce17b..fc832ffff 100644 --- a/main/src/main/scala/sbt/internal/LibraryManagement.scala +++ b/main/src/main/scala/sbt/internal/LibraryManagement.scala @@ -13,7 +13,7 @@ import sbt.internal.librarymanagement._ import sbt.librarymanagement._ import sbt.librarymanagement.syntax._ import sbt.util.{ CacheStore, CacheStoreFactory, Logger, Tracked } -import sbt.io.IO.getModifiedTime +import sbt.io.IO private[sbt] object LibraryManagement { @@ -127,7 +127,7 @@ private[sbt] object LibraryManagement { } private[this] def fileUptodate(file: File, stamps: Map[File, Long]): Boolean = - stamps.get(file).forall(_ == getModifiedTime(file)) + stamps.get(file).forall(_ == IO.getModifiedTime(file)) private[sbt] def transitiveScratch( lm: DependencyResolution, From 40b86ae41265c49e6e52fa2ac043413be82bd0cc Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 16 Dec 2017 01:41:13 -0500 Subject: [PATCH 037/356] bump modules and plugins --- main-actions/src/main/scala/sbt/compiler/Eval.scala | 6 ++++-- project/Dependencies.scala | 8 ++++---- project/plugins.sbt | 11 ++--------- .../sbt/internal/langserver/ClientCapabilities.scala | 2 +- .../sbt/protocol/CommandMessage.scala | 2 +- .../contraband-scala/sbt/protocol/EventMessage.scala | 2 +- .../sbt/protocol/SettingQueryResponse.scala | 2 +- .../sbt/protocol/testing/TestInitEvent.scala | 2 +- .../sbt/protocol/testing/TestMessage.scala | 2 +- 9 files changed, 16 insertions(+), 21 deletions(-) diff --git a/main-actions/src/main/scala/sbt/compiler/Eval.scala b/main-actions/src/main/scala/sbt/compiler/Eval.scala index f756d92b8..a075f61a8 100644 --- a/main-actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main-actions/src/main/scala/sbt/compiler/Eval.scala @@ -487,8 +487,10 @@ private[sbt] object Eval { def fileModifiedBytes(f: File): Array[Byte] = (if (f.isDirectory) filesModifiedBytes(f listFiles classDirFilter) else - bytes(try IO.getModifiedTime(f) catch { case _: java.io.FileNotFoundException => 0L })) ++ - bytes(f.getAbsolutePath) + bytes( + try IO.getModifiedTime(f) + catch { case _: java.io.FileNotFoundException => 0L })) ++ + bytes(f.getAbsolutePath) def fileExistsBytes(f: File): Array[Byte] = bytes(f.exists) ++ bytes(f.getAbsolutePath) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index a628f6aaf..f8e14429f 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -12,10 +12,10 @@ object Dependencies { val baseScalaVersion = scala212 // sbt modules - private val ioVersion = "1.1.1" - private val utilVersion = "1.1.0" - private val lmVersion = "1.1.0" - private val zincVersion = "1.1.0-RC1" + private val ioVersion = "1.1.2" + private val utilVersion = "1.1.1" + private val lmVersion = "1.1.1" + private val zincVersion = "1.1.0-RC3" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion diff --git a/project/plugins.sbt b/project/plugins.sbt index 4cfe0e774..66097a8b9 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,15 +1,8 @@ scalaVersion := "2.12.3" scalacOptions ++= Seq("-feature", "-language:postfixOps") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.17") -// addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.0") -// addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.9.2") -// addSbtPlugin("com.typesafe.sbt" % "sbt-javaversioncheck" % "0.1.0") -// addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.2.0") - -addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.1") -addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.1") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0-M1") +addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.4") +addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.2") addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.14") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0") diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/ClientCapabilities.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/ClientCapabilities.scala index 7bbfe152a..8e046ede9 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/ClientCapabilities.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/ClientCapabilities.scala @@ -9,7 +9,7 @@ final class ClientCapabilities private () extends Serializable { override def equals(o: Any): Boolean = o match { - case x: ClientCapabilities => true + case _: ClientCapabilities => true case _ => false } override def hashCode: Int = { diff --git a/protocol/src/main/contraband-scala/sbt/protocol/CommandMessage.scala b/protocol/src/main/contraband-scala/sbt/protocol/CommandMessage.scala index 9292dbd16..79b15d024 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/CommandMessage.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/CommandMessage.scala @@ -11,7 +11,7 @@ abstract class CommandMessage() extends Serializable { override def equals(o: Any): Boolean = o match { - case x: CommandMessage => true + case _: CommandMessage => true case _ => false } override def hashCode: Int = { diff --git a/protocol/src/main/contraband-scala/sbt/protocol/EventMessage.scala b/protocol/src/main/contraband-scala/sbt/protocol/EventMessage.scala index 6e934413e..9e4435096 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/EventMessage.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/EventMessage.scala @@ -11,7 +11,7 @@ abstract class EventMessage() extends Serializable { override def equals(o: Any): Boolean = o match { - case x: EventMessage => true + case _: EventMessage => true case _ => false } override def hashCode: Int = { diff --git a/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryResponse.scala b/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryResponse.scala index a63a99897..f8fe40388 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryResponse.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryResponse.scala @@ -10,7 +10,7 @@ abstract class SettingQueryResponse() extends sbt.protocol.EventMessage() with S override def equals(o: Any): Boolean = o match { - case x: SettingQueryResponse => true + case _: SettingQueryResponse => true case _ => false } override def hashCode: Int = { diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestInitEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestInitEvent.scala index 978524687..b6178c40f 100644 --- a/testing/src/main/contraband-scala/sbt/protocol/testing/TestInitEvent.scala +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestInitEvent.scala @@ -10,7 +10,7 @@ final class TestInitEvent private () extends sbt.protocol.testing.TestMessage() override def equals(o: Any): Boolean = o match { - case x: TestInitEvent => true + case _: TestInitEvent => true case _ => false } override def hashCode: Int = { diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestMessage.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestMessage.scala index 406807c6f..3ef0f0a76 100644 --- a/testing/src/main/contraband-scala/sbt/protocol/testing/TestMessage.scala +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestMessage.scala @@ -11,7 +11,7 @@ abstract class TestMessage() extends Serializable { override def equals(o: Any): Boolean = o match { - case x: TestMessage => true + case _: TestMessage => true case _ => false } override def hashCode: Int = { From 87bb0f48a6cae314f16b9b21506cbb62a03b4b48 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 16 Dec 2017 19:14:59 -0500 Subject: [PATCH 038/356] notes --- notes/1.1.0.markdown | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/notes/1.1.0.markdown b/notes/1.1.0.markdown index 8bbfa3805..07d8a4a4a 100644 --- a/notes/1.1.0.markdown +++ b/notes/1.1.0.markdown @@ -1,3 +1,13 @@ +### Changes since RC-1 + +- Fixes Java compilation causing `NullPointerException` by making PositionImpl thread-safe. [zinc#465](https://github.com/sbt/zinc/pull/465) by [@eed3si9n][@eed3si9n] +- Restores Scala 2.13.0-M1 support. #461 by @dwijnand +- Fixes `PollingWatchService` by preventing concurrent modification of `keysWithEvents` map. [io#90](https://github.com/sbt/io/pull/90) by [@mechkg][@mechkg], which fixes `~` related issues [#3687](https://github.com/sbt/sbt/issues/3687), [#3695](https://github.com/sbt/sbt/issues/3695), and [#3775](https://github.com/sbt/sbt/issues/3775). +- Fixed server spewing out debug level logs. [#3791](https://github.com/sbt/sbt/pull/3791) by [@eed3si9n][@eed3si9n] +- Fixes the encoding of Unix-like file path to use `file:///`. [#3805](https://github.com/sbt/sbt/pull/3805) by [@eed3si9n][@eed3si9n] +- Fixes Log4J2 initialization error during startup. [#3814](https://github.com/sbt/sbt/pull/3814) by [@dwijnand][@dwijnand] +- Provides workaround for `File#lastModified()` losing millisecond-precision by using native code when possible. [io#92](https://github.com/sbt/io/pull/92) by [@cunei][@cunei] + ### Features, fixes, changes with compatibility implications - sbt server feature is reworked in sbt 1.1.0. See below. @@ -112,7 +122,7 @@ Currently this extension is able to: - Run `compile` at the root project when `*.scala` files are saved. [#3524][3524] by [@eed3si9n][@eed3si9n] - Display compiler errors. - Display log messages. [#3740][3740] by [@laughedelic][@laughedelic] -- Jump to class definitions. [#3660][3660] +- Jump to class definitions. [#3660][3660] by [@wpopielarski][@wpopielarski] ### Filtering scripted tests using `project/build.properties` @@ -122,6 +132,7 @@ This allows you to define scripted tests that track the minimum supported sbt ve [@eed3si9n]: https://github.com/eed3si9n [@dwijnand]: http://github.com/dwijnand + [@cunei]: https://github.com/cunei [@jvican]: https://github.com/jvican [@Duhemm]: https://github.com/Duhemm [@jonas]: https://github.com/jonas @@ -136,6 +147,7 @@ This allows you to define scripted tests that track the minimum supported sbt ve [@romanowski]: https://github.com/romanowski [@raboof]: https://github.com/raboof [@jilen]: https://github.com/jilen + [@mechkg]: https://github.com/mechkg [vscode-sbt-scala]: https://marketplace.visualstudio.com/items?itemName=lightbend.vscode-sbt-scala [1812]: https://github.com/sbt/sbt/issues/1812 [3524]: https://github.com/sbt/sbt/pull/3524 From ec5bdc2381855311d4d0e1d384786a7ca62d2023 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 18 Dec 2017 23:07:25 -0500 Subject: [PATCH 039/356] Add Windows testing --- .appveyor.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 000000000..1cc9a9906 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,26 @@ +build: off + +init: + - git config --global core.autocrlf input + +install: + - cinst nodejs -params 'installdir=C:\\nodejs' + - SET PATH=C:\nodejs\bin;%PATH% + - cinst jdk8 -params 'installdir=C:\\jdk8' + - SET JAVA_HOME=C:\jdk8 + - SET PATH=C:\jdk8\bin;%PATH% + + - ps: | + Add-Type -AssemblyName System.IO.Compression.FileSystem + if (!(Test-Path -Path "C:\sbt" )) { + (new-object System.Net.WebClient).DownloadFile( + 'https://github.com/sbt/sbt/releases/download/v1.0.4/sbt-1.0.4.zip', + 'C:\sbt-bin.zip' + ) + [System.IO.Compression.ZipFile]::ExtractToDirectory("C:\sbt-bin.zip", "C:\sbt") + } + - SET PATH=C:\sbt\sbt\bin;%PATH% + - SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g -Dfile.encoding=UTF8 +test_script: + - sbt "scripted actions/*" + - sbt "scripted server/*" From c920919c2c13d66d36a7a257ea56b0eebd63b3d2 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 19 Dec 2017 00:17:46 -0500 Subject: [PATCH 040/356] Fix build so it works on Windows --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index ec5065942..368393d59 100644 --- a/build.sbt +++ b/build.sbt @@ -34,6 +34,7 @@ def buildLevelSettings: Seq[Setting[_]] = scmInfo := Some(ScmInfo(url("https://github.com/sbt/sbt"), "git@github.com:sbt/sbt.git")), resolvers += Resolver.mavenLocal, scalafmtOnCompile := true, + scalafmtOnCompile in Sbt := false, scalafmtVersion := "1.3.0", )) @@ -463,10 +464,9 @@ lazy val sbtIgnoredProblems = { } def runNpm(command: String, base: File, log: sbt.internal.util.ManagedLogger) = { - val npm = if (sbt.internal.util.Util.isWindows) "npm.cmd" else "npm" import scala.sys.process._ try { - val exitCode = Process(s"$npm $command", Option(base)) ! log + val exitCode = Process(s"npm $command", Option(base)) ! log if (exitCode != 0) throw new Exception("Process returned exit code: " + exitCode) } catch { case e: java.io.IOException => log.warn("failed to run npm " + e.getMessage) From 294110e2b8074e13c50aeb8e4e734fe77651433c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 19 Dec 2017 00:44:31 -0500 Subject: [PATCH 041/356] Remove JNA from sbt/sbt There's a conflict in JNA version difference between sbt and IO. Fixes #3821 --- build.sbt | 3 +-- project/Dependencies.scala | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 368393d59..dcedffd62 100644 --- a/build.sbt +++ b/build.sbt @@ -311,8 +311,7 @@ lazy val commandProj = (project in file("main-command")) .settings( testedBaseSettings, name := "Command", - libraryDependencies ++= Seq(launcherInterface, sjsonNewScalaJson.value, templateResolverApi, - jna, jnaPlatform), + libraryDependencies ++= Seq(launcherInterface, sjsonNewScalaJson.value, templateResolverApi), managedSourceDirectories in Compile += baseDirectory.value / "src" / "main" / "contraband-scala", sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f8e14429f..73405aea8 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -106,8 +106,6 @@ object Dependencies { val specs2 = "org.specs2" %% "specs2-junit" % "4.0.1" val junit = "junit" % "junit" % "4.11" val templateResolverApi = "org.scala-sbt" % "template-resolver" % "0.1" - val jna = "net.java.dev.jna" % "jna" % "4.1.0" - val jnaPlatform = "net.java.dev.jna" % "jna-platform" % "4.1.0" private def scala211Module(name: String, moduleVersion: String) = Def setting ( scalaBinaryVersion.value match { From 3507403a360cdac49950574394c157c53e53e4c1 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 19 Dec 2017 02:12:03 -0500 Subject: [PATCH 042/356] -Djna.nosys=true --- .appveyor.yml | 6 ++---- build.sbt | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 1cc9a9906..45e3cfd3f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -4,8 +4,6 @@ init: - git config --global core.autocrlf input install: - - cinst nodejs -params 'installdir=C:\\nodejs' - - SET PATH=C:\nodejs\bin;%PATH% - cinst jdk8 -params 'installdir=C:\\jdk8' - SET JAVA_HOME=C:\jdk8 - SET PATH=C:\jdk8\bin;%PATH% @@ -22,5 +20,5 @@ install: - SET PATH=C:\sbt\sbt\bin;%PATH% - SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g -Dfile.encoding=UTF8 test_script: - - sbt "scripted actions/*" - - sbt "scripted server/*" + - sbt "-Djna.nosys=true" "scripted actions/*" + - sbt "-Djna.nosys=true" "scripted server/*" diff --git a/build.sbt b/build.sbt index dcedffd62..8000bd603 100644 --- a/build.sbt +++ b/build.sbt @@ -572,7 +572,7 @@ def otherRootSettings = scriptedUnpublished := scriptedUnpublishedTask.evaluated, scriptedSource := (sourceDirectory in sbtProj).value / "sbt-test", // scriptedPrescripted := { addSbtAlternateResolver _ }, - scriptedLaunchOpts := List("-Xmx1500M", "-Xms512M", "-server"), + scriptedLaunchOpts := List("-Xmx1500M", "-Xms512M", "-server", "-Djna.nosys=true"), publishAll := { val _ = (publishLocal).all(ScopeFilter(inAnyProject)).value }, publishLocalBinAll := { val _ = (publishLocalBin).all(ScopeFilter(inAnyProject)).value }, aggregate in bintrayRelease := false From bc4d3d5031152b06162b7c2ad1a44991041dd967 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 19 Dec 2017 03:24:51 -0500 Subject: [PATCH 043/356] just scripted actions/* --- .appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 45e3cfd3f..0b15d875f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -21,4 +21,3 @@ install: - SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g -Dfile.encoding=UTF8 test_script: - sbt "-Djna.nosys=true" "scripted actions/*" - - sbt "-Djna.nosys=true" "scripted server/*" From ad2d71cae4cc5d6bbb9f9cf942eedb332500aca1 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 19 Dec 2017 13:25:58 +0000 Subject: [PATCH 044/356] Upgrade to sbt-houserules 0.3.5 --- project/plugins.sbt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 66097a8b9..7ea70f8ea 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,8 +1,7 @@ -scalaVersion := "2.12.3" +scalaVersion := "2.12.4" scalacOptions ++= Seq("-feature", "-language:postfixOps") -addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.4") +addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.5") addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.2") -addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.14") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0") From 29c15e4e2e98fb29f276f7c1ce258a4f69115eb3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 19 Dec 2017 15:19:12 +0000 Subject: [PATCH 045/356] Disable project/unique-settings-computation I've seen this fail Travis CI too many times. Here's two examples: * https://travis-ci.org/sbt/sbt/jobs/318632919 * https://travis-ci.org/sbt/sbt/jobs/313847550 --- .../project/unique-settings-computation/{pending => disabled} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename sbt/src/sbt-test/project/unique-settings-computation/{pending => disabled} (100%) diff --git a/sbt/src/sbt-test/project/unique-settings-computation/pending b/sbt/src/sbt-test/project/unique-settings-computation/disabled similarity index 100% rename from sbt/src/sbt-test/project/unique-settings-computation/pending rename to sbt/src/sbt-test/project/unique-settings-computation/disabled From 4c3b770b279a261a0ddbaa1df5de47d7faca5be7 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 19 Dec 2017 20:57:09 -0500 Subject: [PATCH 046/356] bump to sbt 1.0.4 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 94005e587..394cb75cf 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.0.0 +sbt.version=1.0.4 From 6e09c660fe54ce674e6df26f1b423f1b6ef84990 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 20 Dec 2017 11:50:02 +0000 Subject: [PATCH 047/356] Replace Throwable catching with DeserializationException catching --- .../protocol/codec/JsonRpcRequestMessageFormats.scala | 4 ++-- .../protocol/codec/JsonRpcResponseMessageFormats.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcRequestMessageFormats.scala b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcRequestMessageFormats.scala index dac0987e4..e0e047cc6 100644 --- a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcRequestMessageFormats.scala +++ b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcRequestMessageFormats.scala @@ -8,7 +8,7 @@ package sbt.internal.protocol.codec import sjsonnew.shaded.scalajson.ast.unsafe.JValue -import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +import sjsonnew.{ Builder, DeserializationException, JsonFormat, Unbuilder, deserializationError } trait JsonRpcRequestMessageFormats { self: sbt.internal.util.codec.JValueFormats with sjsonnew.BasicJsonProtocol => @@ -24,7 +24,7 @@ trait JsonRpcRequestMessageFormats { val id = try { unbuilder.readField[String]("id") } catch { - case _: Throwable => unbuilder.readField[Long]("id").toString + case _: DeserializationException => unbuilder.readField[Long]("id").toString } val method = unbuilder.readField[String]("method") val params = unbuilder.lookupField("params") map { diff --git a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala index d10164e67..56cec6321 100644 --- a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala +++ b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala @@ -7,7 +7,7 @@ package sbt.internal.protocol.codec -import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +import sjsonnew.{ Builder, DeserializationException, JsonFormat, Unbuilder, deserializationError } import sjsonnew.shaded.scalajson.ast.unsafe.JValue trait JsonRpcResponseMessageFormats { @@ -27,7 +27,7 @@ trait JsonRpcResponseMessageFormats { val id = try { unbuilder.readField[Option[String]]("id") } catch { - case _: Throwable => unbuilder.readField[Option[Long]]("id") map { _.toString } + case _: DeserializationException => unbuilder.readField[Option[Long]]("id") map { _.toString } } val result = unbuilder.lookupField("result") map { From 06ffb4f4406156e423ba7135178a45aa5f72b099 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 19 Dec 2017 21:01:02 -0500 Subject: [PATCH 048/356] warn about multiple instance once Fixes #3823 When you launch a second instance of sbt on a build, prior to this change it was displaying `java.io.IOException: sbt server is already running` on every command. This make it a bit less aggressive, and just display a warning once. ``` [warn] Is another instance of sbt is running on this build? [warn] Running multiple instances is unsupported ``` --- .../main/scala/sbt/internal/server/Server.scala | 4 +++- .../scala/sbt/internal/CommandExchange.scala | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/main-command/src/main/scala/sbt/internal/server/Server.scala b/main-command/src/main/scala/sbt/internal/server/Server.scala index 4fb8a7cb4..521c8d399 100644 --- a/main-command/src/main/scala/sbt/internal/server/Server.scala +++ b/main-command/src/main/scala/sbt/internal/server/Server.scala @@ -97,7 +97,7 @@ private[sbt] object Server { case Failure(e) => () case Success(socket) => socket.close() - throw new IOException("sbt server is already running.") + throw new AlreadyRunningException() } } else () } @@ -210,3 +210,5 @@ private[sbt] case class ServerConnection( } } } + +private[sbt] class AlreadyRunningException extends IOException("sbt server is already running.") diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 39259ee28..acbe78bb6 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -10,7 +10,7 @@ package internal import java.io.IOException import java.util.concurrent.ConcurrentLinkedQueue -import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic._ import scala.collection.mutable.ListBuffer import scala.annotation.tailrec import BasicKeys.{ @@ -48,6 +48,7 @@ private[sbt] final class CommandExchange { } getOrElse true private val lock = new AnyRef {} private var server: Option[ServerInstance] = None + private val firstInstance: AtomicBoolean = new AtomicBoolean(true) private var consoleChannel: Option[ConsoleChannel] = None private val commandQueue: ConcurrentLinkedQueue[Exec] = new ConcurrentLinkedQueue() private val channelBuffer: ListBuffer[CommandChannel] = new ListBuffer() @@ -90,7 +91,6 @@ private[sbt] final class CommandExchange { else s } - private def newChannelName: String = s"channel-${nextChannelId.incrementAndGet()}" private def newNetworkName: String = s"network-${nextChannelId.incrementAndGet()}" /** @@ -132,7 +132,8 @@ private[sbt] final class CommandExchange { subscribe(channel) } server match { - case Some(_) => // do nothing + case Some(_) => // do nothing + case None if !firstInstance.get => // there's another server case _ => val portfile = (new File(".")).getAbsoluteFile / "project" / "target" / "active.json" val h = Hash.halfHashString(IO.toURI(portfile).toString) @@ -154,6 +155,11 @@ private[sbt] final class CommandExchange { case Some(Success(_)) => // rememeber to shutdown only when the server comes up server = Some(x) + case Some(Failure(e: AlreadyRunningException)) => + s.log.warn("sbt server could not start because there's another instance of sbt running on this build.") + s.log.warn("Running multiple instances is unsupported") + server = None + firstInstance.set(false) case Some(Failure(e)) => s.log.error(e.toString) server = None @@ -191,6 +197,7 @@ private[sbt] final class CommandExchange { case xs => lock.synchronized { channelBuffer --= xs + () } } } @@ -247,6 +254,7 @@ private[sbt] final class CommandExchange { case xs => lock.synchronized { channelBuffer --= xs + () } } } @@ -288,6 +296,7 @@ private[sbt] final class CommandExchange { case xs => lock.synchronized { channelBuffer --= xs + () } } } @@ -339,6 +348,7 @@ private[sbt] final class CommandExchange { case xs => lock.synchronized { channelBuffer --= xs + () } } } From d8609ced7feb3ae8b878958d01632064806bc1d0 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 20 Dec 2017 22:36:07 -0500 Subject: [PATCH 049/356] formatting --- main/src/main/scala/sbt/internal/CommandExchange.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index acbe78bb6..99f1e1418 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -156,7 +156,8 @@ private[sbt] final class CommandExchange { // rememeber to shutdown only when the server comes up server = Some(x) case Some(Failure(e: AlreadyRunningException)) => - s.log.warn("sbt server could not start because there's another instance of sbt running on this build.") + s.log.warn( + "sbt server could not start because there's another instance of sbt running on this build.") s.log.warn("Running multiple instances is unsupported") server = None firstInstance.set(false) From 13e1862c2f12f633b13d4b68715d933fef8baf31 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 21 Dec 2017 00:08:56 -0500 Subject: [PATCH 050/356] set jna.nosys to true programmatically Previously I was seeing the error upon the first scripted test. I thought it was because Main was somehow not early enough. It might just be because scripted technically runs as part of the build. Ref sbt/io#110 --- .appveyor.yml | 2 +- build.sbt | 2 +- main/src/main/scala/sbt/Main.scala | 3 +++ project/Scripted.scala | 3 +++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 0b15d875f..b9a894ee2 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -20,4 +20,4 @@ install: - SET PATH=C:\sbt\sbt\bin;%PATH% - SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g -Dfile.encoding=UTF8 test_script: - - sbt "-Djna.nosys=true" "scripted actions/*" + - sbt "scripted actions/*" diff --git a/build.sbt b/build.sbt index 8000bd603..dcedffd62 100644 --- a/build.sbt +++ b/build.sbt @@ -572,7 +572,7 @@ def otherRootSettings = scriptedUnpublished := scriptedUnpublishedTask.evaluated, scriptedSource := (sourceDirectory in sbtProj).value / "sbt-test", // scriptedPrescripted := { addSbtAlternateResolver _ }, - scriptedLaunchOpts := List("-Xmx1500M", "-Xms512M", "-server", "-Djna.nosys=true"), + scriptedLaunchOpts := List("-Xmx1500M", "-Xms512M", "-server"), publishAll := { val _ = (publishLocal).all(ScopeFilter(inAnyProject)).value }, publishLocalBinAll := { val _ = (publishLocalBin).all(ScopeFilter(inAnyProject)).value }, aggregate in bintrayRelease := false diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 6c605056e..da44d0cb8 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -128,6 +128,9 @@ object StandardMain { def initialState(configuration: xsbti.AppConfiguration, initialDefinitions: Seq[Command], preCommands: Seq[String]): State = { + // This is to workaround https://github.com/sbt/io/issues/110 + sys.props.put("jna.nosys", "true") + import BasicCommandStrings.isEarlyCommand val userCommands = configuration.arguments.map(_.trim) val (earlyCommands, normalCommands) = (preCommands ++ userCommands).partition(isEarlyCommand) diff --git a/project/Scripted.scala b/project/Scripted.scala index 932d55a0f..788f1d60e 100644 --- a/project/Scripted.scala +++ b/project/Scripted.scala @@ -37,6 +37,9 @@ trait ScriptedKeys { } object Scripted { + // This is to workaround https://github.com/sbt/io/issues/110 + sys.props.put("jna.nosys", "true") + lazy val MavenResolverPluginTest = config("mavenResolverPluginTest") extend Compile lazy val RepoOverrideTest = config("repoOverrideTest") extend Compile From 8c7f93d7a58eb4e2a394b8b9ae188e3b28775043 Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Thu, 21 Dec 2017 15:25:24 +0100 Subject: [PATCH 051/356] Keep tests/fork-parallel disabled, but add an explanation See https://github.com/sbt/sbt/issues/3545#issuecomment-353247827 --- sbt/src/sbt-test/tests/fork-parallel/test | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/sbt/src/sbt-test/tests/fork-parallel/test b/sbt/src/sbt-test/tests/fork-parallel/test index 9d76b41e6..7cbc6d0a3 100644 --- a/sbt/src/sbt-test/tests/fork-parallel/test +++ b/sbt/src/sbt-test/tests/fork-parallel/test @@ -1,8 +1,23 @@ -# https://github.com/sbt/sbt/issues/3545 +# The tests/fork-parallel test will currently always +# report success when run on less than four cores, +# rather than failing in one of the two cases as expected. +# +# TODO: Adjust this scripted test so that it works as +# intended on less than four cores as well. +# +# To debug, it is possible to limit the number of cores +# reported to sbt, and run the test, by using: +# +# taskset 0x00000003 sbt 'scripted tests/fork-parallel' +# +# See: https://github.com/sbt/sbt/issues/3545 +# + +# This bit won't currently work when using less than four cores. # > test # -> check > clean > set testForkedParallel := true > test -> check \ No newline at end of file +> check From ebff7919e90730e03ff4c5e3a1bfd4dca980ac7a Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Mon, 18 Dec 2017 17:37:03 +0100 Subject: [PATCH 052/356] Revert *ModifiedTime() calls to *lastModified*() calls There are just too many instances in which sbt's code relies on the `lastModified`/`setLastModified` semantics, so instead of moving to `get`/`setModifiedTime`, we use new IO calls that offer the new timestamp precision, but retain the old semantics. --- main-actions/src/main/scala/sbt/compiler/Eval.scala | 9 ++++----- main/src/main/scala/sbt/Defaults.scala | 2 +- main/src/main/scala/sbt/internal/LibraryManagement.scala | 2 +- project/SiteMap.scala | 4 ++-- project/Util.scala | 4 ++-- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/main-actions/src/main/scala/sbt/compiler/Eval.scala b/main-actions/src/main/scala/sbt/compiler/Eval.scala index a075f61a8..78235cdb3 100644 --- a/main-actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main-actions/src/main/scala/sbt/compiler/Eval.scala @@ -485,12 +485,11 @@ private[sbt] object Eval { def filesModifiedBytes(fs: Array[File]): Array[Byte] = if (fs eq null) filesModifiedBytes(Array[File]()) else seqBytes(fs)(fileModifiedBytes) def fileModifiedBytes(f: File): Array[Byte] = - (if (f.isDirectory) filesModifiedBytes(f listFiles classDirFilter) + (if (f.isDirectory) + filesModifiedBytes(f listFiles classDirFilter) else - bytes( - try IO.getModifiedTime(f) - catch { case _: java.io.FileNotFoundException => 0L })) ++ - bytes(f.getAbsolutePath) + bytes(IO.lastModified(f)) + ) ++ bytes(f.getAbsolutePath) def fileExistsBytes(f: File): Array[Byte] = bytes(f.exists) ++ bytes(f.getAbsolutePath) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index b006c115d..5d9d2fb8b 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2315,7 +2315,7 @@ object Classpaths { case Some(period) => val fullUpdateOutput = cacheDirectory / "out" val now = System.currentTimeMillis - val diff = now - IO.getModifiedTime(fullUpdateOutput) + val diff = now - IO.lastModified(fullUpdateOutput) val elapsedDuration = new FiniteDuration(diff, TimeUnit.MILLISECONDS) fullUpdateOutput.exists() && elapsedDuration > period } diff --git a/main/src/main/scala/sbt/internal/LibraryManagement.scala b/main/src/main/scala/sbt/internal/LibraryManagement.scala index fc832ffff..b40956fad 100644 --- a/main/src/main/scala/sbt/internal/LibraryManagement.scala +++ b/main/src/main/scala/sbt/internal/LibraryManagement.scala @@ -127,7 +127,7 @@ private[sbt] object LibraryManagement { } private[this] def fileUptodate(file: File, stamps: Map[File, Long]): Boolean = - stamps.get(file).forall(_ == IO.getModifiedTime(file)) + stamps.get(file).forall(_ == IO.lastModified(file)) private[sbt] def transitiveScratch( lm: DependencyResolution, diff --git a/project/SiteMap.scala b/project/SiteMap.scala index 2cfb5d8ea..046031775 100644 --- a/project/SiteMap.scala +++ b/project/SiteMap.scala @@ -68,8 +68,8 @@ object SiteMap { // generates a string suitable for a sitemap file representing the last modified time of the given File private[this] def lastModifiedString(f: File): String = { val formatter = new java.text.SimpleDateFormat("yyyy-MM-dd") - // TODO: replace lastModified() with sbt.io.Milli.getModifiedTime(), once the build - // has been upgraded to a version of sbt that includes sbt.io.Milli. + // TODO: replace lastModified() with sbt.io.IO.lastModified(), once the build + // has been upgraded to a version of sbt that includes that call. formatter.format(new java.util.Date(f.lastModified)) } // writes the provided XML node to `output` and then gzips it to `gzipped` if `gzip` is true diff --git a/project/Util.scala b/project/Util.scala index 0b87b02db..c04734285 100644 --- a/project/Util.scala +++ b/project/Util.scala @@ -105,8 +105,8 @@ object Util { val timestamp = formatter.format(new Date) val content = versionLine(version) + "\ntimestamp=" + timestamp val f = dir / "xsbt.version.properties" - // TODO: replace lastModified() with sbt.io.Milli.getModifiedTime(), once the build - // has been upgraded to a version of sbt that includes sbt.io.Milli. + // TODO: replace lastModified() with sbt.io.IO.lastModified(), once the build + // has been upgraded to a version of sbt that includes that call. if (!f.exists || f.lastModified < lastCompilationTime(analysis) || !containsVersion(f, version)) { s.log.info("Writing version information to " + f + " :\n" + content) IO.write(f, content) From 94e36a14c80027a4e316505574d4c49fdcb14948 Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Fri, 22 Dec 2017 01:49:59 +0100 Subject: [PATCH 053/356] Change modifiedTime definitions --- main-actions/src/main/scala/sbt/compiler/Eval.scala | 3 +-- main/src/main/scala/sbt/Defaults.scala | 2 +- main/src/main/scala/sbt/internal/LibraryManagement.scala | 2 +- project/SiteMap.scala | 2 +- project/Util.scala | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/main-actions/src/main/scala/sbt/compiler/Eval.scala b/main-actions/src/main/scala/sbt/compiler/Eval.scala index 78235cdb3..a9d97246a 100644 --- a/main-actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main-actions/src/main/scala/sbt/compiler/Eval.scala @@ -488,8 +488,7 @@ private[sbt] object Eval { (if (f.isDirectory) filesModifiedBytes(f listFiles classDirFilter) else - bytes(IO.lastModified(f)) - ) ++ bytes(f.getAbsolutePath) + bytes(IO.getModifiedTimeOrZero(f))) ++ bytes(f.getAbsolutePath) def fileExistsBytes(f: File): Array[Byte] = bytes(f.exists) ++ bytes(f.getAbsolutePath) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 5d9d2fb8b..4fd8d1585 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2315,7 +2315,7 @@ object Classpaths { case Some(period) => val fullUpdateOutput = cacheDirectory / "out" val now = System.currentTimeMillis - val diff = now - IO.lastModified(fullUpdateOutput) + val diff = now - IO.getModifiedTimeOrZero(fullUpdateOutput) val elapsedDuration = new FiniteDuration(diff, TimeUnit.MILLISECONDS) fullUpdateOutput.exists() && elapsedDuration > period } diff --git a/main/src/main/scala/sbt/internal/LibraryManagement.scala b/main/src/main/scala/sbt/internal/LibraryManagement.scala index b40956fad..d247a9d99 100644 --- a/main/src/main/scala/sbt/internal/LibraryManagement.scala +++ b/main/src/main/scala/sbt/internal/LibraryManagement.scala @@ -127,7 +127,7 @@ private[sbt] object LibraryManagement { } private[this] def fileUptodate(file: File, stamps: Map[File, Long]): Boolean = - stamps.get(file).forall(_ == IO.lastModified(file)) + stamps.get(file).forall(_ == IO.getModifiedTimeOrZero(file)) private[sbt] def transitiveScratch( lm: DependencyResolution, diff --git a/project/SiteMap.scala b/project/SiteMap.scala index 046031775..95e038eab 100644 --- a/project/SiteMap.scala +++ b/project/SiteMap.scala @@ -68,7 +68,7 @@ object SiteMap { // generates a string suitable for a sitemap file representing the last modified time of the given File private[this] def lastModifiedString(f: File): String = { val formatter = new java.text.SimpleDateFormat("yyyy-MM-dd") - // TODO: replace lastModified() with sbt.io.IO.lastModified(), once the build + // TODO: replace lastModified() with sbt.io.IO.getModifiedTimeOrZero(), once the build // has been upgraded to a version of sbt that includes that call. formatter.format(new java.util.Date(f.lastModified)) } diff --git a/project/Util.scala b/project/Util.scala index c04734285..8c68f4ced 100644 --- a/project/Util.scala +++ b/project/Util.scala @@ -105,7 +105,7 @@ object Util { val timestamp = formatter.format(new Date) val content = versionLine(version) + "\ntimestamp=" + timestamp val f = dir / "xsbt.version.properties" - // TODO: replace lastModified() with sbt.io.IO.lastModified(), once the build + // TODO: replace lastModified() with sbt.io.IO.getModifiedTimeOrZero(), once the build // has been upgraded to a version of sbt that includes that call. if (!f.exists || f.lastModified < lastCompilationTime(analysis) || !containsVersion(f, version)) { s.log.info("Writing version information to " + f + " :\n" + content) From 7f0ff7c90aa3c3935337229d79a8f7f496d9ea66 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 22 Dec 2017 11:45:31 -0500 Subject: [PATCH 054/356] IO 1.1.3, Zinc 1.1.0-RC4 --- project/Dependencies.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 73405aea8..21de56080 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -12,10 +12,10 @@ object Dependencies { val baseScalaVersion = scala212 // sbt modules - private val ioVersion = "1.1.2" - private val utilVersion = "1.1.1" - private val lmVersion = "1.1.1" - private val zincVersion = "1.1.0-RC3" + private val ioVersion = "1.1.3" + private val utilVersion = "1.1.2" + private val lmVersion = "1.1.2" + private val zincVersion = "1.1.0-RC4" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion From 0a3900f53dfbd4db14e849960d853a8f05513886 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 22 Dec 2017 15:38:17 -0500 Subject: [PATCH 055/356] Add Library interface Fixes #3821 Initially I missed why #3821 was failing. Looking at it again, the error message reads: ``` Caused by: java.lang.IllegalArgumentException: Interface (NGWin32NamedPipeLibrary) of library=kernel32 does not extend Library at com.sun.jna.Native.loadLibrary(Native.java:566) at sbt.internal.NGWin32NamedPipeLibrary.(NGWin32NamedPipeLibrary.java:38) ... 7 more ``` Inside `Native.loadLibrary`, it requires the "library" interface to extend `com.sun.jna.Library`, which this adds. --- .appveyor.yml | 2 +- .../src/main/java/sbt/internal/NGWin32NamedPipeLibrary.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index b9a894ee2..a0d3292f1 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -20,4 +20,4 @@ install: - SET PATH=C:\sbt\sbt\bin;%PATH% - SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g -Dfile.encoding=UTF8 test_script: - - sbt "scripted actions/*" + - sbt "scripted actions/* server/*" diff --git a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeLibrary.java b/main-command/src/main/java/sbt/internal/NGWin32NamedPipeLibrary.java index ba535691f..dd4d8f15a 100644 --- a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeLibrary.java +++ b/main-command/src/main/java/sbt/internal/NGWin32NamedPipeLibrary.java @@ -29,7 +29,7 @@ import com.sun.jna.ptr.IntByReference; import com.sun.jna.win32.W32APIOptions; -public interface NGWin32NamedPipeLibrary extends WinNT { +public interface NGWin32NamedPipeLibrary extends Library, WinNT { int PIPE_ACCESS_DUPLEX = 3; int PIPE_UNLIMITED_INSTANCES = 255; int FILE_FLAG_FIRST_PIPE_INSTANCE = 524288; From 0aebb92ef5a8004da26c86e2c597bbb80442dc6b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 22 Dec 2017 16:14:05 -0500 Subject: [PATCH 056/356] don't block the build when server can't get up --- .../main/scala/sbt/internal/CommandExchange.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 99f1e1418..d7b52280d 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -26,7 +26,7 @@ import sjsonnew.JsonFormat import sjsonnew.shaded.scalajson.ast.unsafe._ import scala.concurrent.Await import scala.concurrent.duration.Duration -import scala.util.{ Success, Failure } +import scala.util.{ Success, Failure, Try } import sbt.io.syntax._ import sbt.io.{ Hash, IO } import sbt.internal.server._ @@ -150,7 +150,10 @@ private[sbt] final class CommandExchange { socketfile, pipeName) val x = Server.start(connection, onIncomingSocket, s.log) - Await.ready(x.ready, Duration("10s")) + + // don't throw exception when it times out + val d = "10s" + Try(Await.ready(x.ready, Duration(d))) x.ready.value match { case Some(Success(_)) => // rememeber to shutdown only when the server comes up @@ -164,7 +167,10 @@ private[sbt] final class CommandExchange { case Some(Failure(e)) => s.log.error(e.toString) server = None - case None => // this won't happen because we awaited + case None => + s.log.warn(s"sbt server could not start in $d") + server = None + firstInstance.set(false) } } s From eb0bf307120e7e0d6c590a4ae8e06f71d99f7942 Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Sat, 23 Dec 2017 05:00:38 +0100 Subject: [PATCH 057/356] Adjustments to test file. Scripted can't cope with empty comments Apparently an empty comment line, meaning a single '#' on a line, cannot be fed to scripted, which will complain with an odd error message. This commit tweaks the comments in the tests/fork-parallel file so that there are no empty comment lines. --- sbt/src/sbt-test/tests/fork-parallel/test | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sbt/src/sbt-test/tests/fork-parallel/test b/sbt/src/sbt-test/tests/fork-parallel/test index 7cbc6d0a3..662328f42 100644 --- a/sbt/src/sbt-test/tests/fork-parallel/test +++ b/sbt/src/sbt-test/tests/fork-parallel/test @@ -1,17 +1,13 @@ # The tests/fork-parallel test will currently always # report success when run on less than four cores, # rather than failing in one of the two cases as expected. -# # TODO: Adjust this scripted test so that it works as # intended on less than four cores as well. -# + # To debug, it is possible to limit the number of cores # reported to sbt, and run the test, by using: -# # taskset 0x00000003 sbt 'scripted tests/fork-parallel' -# # See: https://github.com/sbt/sbt/issues/3545 -# # This bit won't currently work when using less than four cores. # > test From 4752084f91909d67ced6e649400c553f4d1cd654 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 21 Dec 2017 16:11:17 +0000 Subject: [PATCH 058/356] Introduce projectToLocalProject to replace projectToRef Fixes #3757 --- main/src/main/scala/sbt/Project.scala | 4 +++- notes/1.2.0/introduce-projectToLocalProject.md | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 notes/1.2.0/introduce-projectToLocalProject.md diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index c83c7a217..e2d99dac7 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -754,7 +754,9 @@ object Project extends ProjectExtra { EvaluateTask(extracted.structure, taskKey, state, extracted.currentRef, config) } - implicit def projectToRef(p: Project): ProjectReference = LocalProject(p.id) + def projectToRef(p: Project): ProjectReference = LocalProject(p.id) + + implicit def projectToLocalProject(p: Project): LocalProject = LocalProject(p.id) final class RichTaskSessionVar[S](i: Def.Initialize[Task[S]]) { import SessionVar.{ persistAndSet, resolveContext, set, transform => tx } diff --git a/notes/1.2.0/introduce-projectToLocalProject.md b/notes/1.2.0/introduce-projectToLocalProject.md new file mode 100644 index 000000000..8f856d3e8 --- /dev/null +++ b/notes/1.2.0/introduce-projectToLocalProject.md @@ -0,0 +1,12 @@ +[@dwijnand]: https://github.com/dwijnand + +[#3757]: https://github.com/sbt/sbt/issues/3757 +[#3836]: https://github.com/sbt/sbt/pull/3836 + +### Fixes with compatibility implications + +### Improvements + +- Introduces `projectToLocalProject` to replace `projectToRef`. [#3757][]/[#3836][] by [@dwijnand][] + +### Bug fixes From bd5cbc4fb31fdafe93bffe6a098e2ba78835228a Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 5 Jan 2018 14:06:30 -0500 Subject: [PATCH 059/356] notes --- notes/1.1.0.markdown | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/notes/1.1.0.markdown b/notes/1.1.0.markdown index 07d8a4a4a..29ed283f7 100644 --- a/notes/1.1.0.markdown +++ b/notes/1.1.0.markdown @@ -1,12 +1,19 @@ +### Changes since RC-2 + +- Provides workaround for `File#lastModified()` losing millisecond-precision by using native code when possible. [io#92](https://github.com/sbt/io/pull/92)/[io#106](https://github.com/sbt/io/pull/106) by [@cunei][@cunei] +- Fixes `IO.relativize` not working with relative path. [io#108](https://github.com/sbt/io/pull/108) by [@dwijnand][@dwijnand] +- Fixes `ClasspathFilter` that was causing `Class.forName` to not work in `run`. [zinc#473](https://github.com/sbt/zinc/pull/473) / [#3736](https://github.com/sbt/sbt/issues/3736) / [#3733](https://github.com/sbt/sbt/issues/3733) / [#3647](https://github.com/sbt/sbt/issues/3647) / [#3608](https://github.com/sbt/sbt/issues/3608) by [@ravwojdyla][@ravwojdyla] +- Fixes JNA version mixup. [#3837](https://github.com/sbt/sbt/pull/3837) by [@eed3si9n][@eed3si9n] +- Fixes warning message when multiple instances are detected. [#3828](https://github.com/sbt/sbt/pull/3828) by [@eed3si9n][@eed3si9n] + ### Changes since RC-1 - Fixes Java compilation causing `NullPointerException` by making PositionImpl thread-safe. [zinc#465](https://github.com/sbt/zinc/pull/465) by [@eed3si9n][@eed3si9n] -- Restores Scala 2.13.0-M1 support. #461 by @dwijnand +- Restores Scala 2.13.0-M1 support. #461 by [@dwijnand][@dwijnand] - Fixes `PollingWatchService` by preventing concurrent modification of `keysWithEvents` map. [io#90](https://github.com/sbt/io/pull/90) by [@mechkg][@mechkg], which fixes `~` related issues [#3687](https://github.com/sbt/sbt/issues/3687), [#3695](https://github.com/sbt/sbt/issues/3695), and [#3775](https://github.com/sbt/sbt/issues/3775). - Fixed server spewing out debug level logs. [#3791](https://github.com/sbt/sbt/pull/3791) by [@eed3si9n][@eed3si9n] - Fixes the encoding of Unix-like file path to use `file:///`. [#3805](https://github.com/sbt/sbt/pull/3805) by [@eed3si9n][@eed3si9n] - Fixes Log4J2 initialization error during startup. [#3814](https://github.com/sbt/sbt/pull/3814) by [@dwijnand][@dwijnand] -- Provides workaround for `File#lastModified()` losing millisecond-precision by using native code when possible. [io#92](https://github.com/sbt/io/pull/92) by [@cunei][@cunei] ### Features, fixes, changes with compatibility implications @@ -148,6 +155,7 @@ This allows you to define scripted tests that track the minimum supported sbt ve [@raboof]: https://github.com/raboof [@jilen]: https://github.com/jilen [@mechkg]: https://github.com/mechkg + [@ravwojdyla]: https://github.com/ravwojdyla [vscode-sbt-scala]: https://marketplace.visualstudio.com/items?itemName=lightbend.vscode-sbt-scala [1812]: https://github.com/sbt/sbt/issues/1812 [3524]: https://github.com/sbt/sbt/pull/3524 From 47d66eef0f4fd92a9718e842e298a67054853ba5 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 5 Jan 2018 14:07:04 -0500 Subject: [PATCH 060/356] Zinc 1.1.0 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 21de56080..f8d470ccd 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -15,7 +15,7 @@ object Dependencies { private val ioVersion = "1.1.3" private val utilVersion = "1.1.2" private val lmVersion = "1.1.2" - private val zincVersion = "1.1.0-RC4" + private val zincVersion = "1.1.0" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion From 5a695b9761092c449c59903883355be5d71445e7 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 5 Jan 2018 22:47:58 -0500 Subject: [PATCH 061/356] launchconfig --- src/main/conscript/scalas/launchconfig | 2 +- src/main/conscript/screpl/launchconfig | 2 +- src/main/conscript/xsbt/launchconfig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/conscript/scalas/launchconfig b/src/main/conscript/scalas/launchconfig index 497187233..98febc38c 100644 --- a/src/main/conscript/scalas/launchconfig +++ b/src/main/conscript/scalas/launchconfig @@ -4,7 +4,7 @@ [app] org: ${sbt.organization-org.scala-sbt} name: sbt - version: ${sbt.version-read(sbt.version)[1.0.0]} + version: ${sbt.version-read(sbt.version)[1.1.0]} class: sbt.ScriptMain components: xsbti,extra cross-versioned: ${sbt.cross.versioned-false} diff --git a/src/main/conscript/screpl/launchconfig b/src/main/conscript/screpl/launchconfig index 4701c697e..17a32efb0 100644 --- a/src/main/conscript/screpl/launchconfig +++ b/src/main/conscript/screpl/launchconfig @@ -4,7 +4,7 @@ [app] org: ${sbt.organization-org.scala-sbt} name: sbt - version: ${sbt.version-read(sbt.version)[1.0.0]} + version: ${sbt.version-read(sbt.version)[1.1.0]} class: sbt.ConsoleMain components: xsbti,extra cross-versioned: ${sbt.cross.versioned-false} diff --git a/src/main/conscript/xsbt/launchconfig b/src/main/conscript/xsbt/launchconfig index e47a59083..79ddd5ef7 100644 --- a/src/main/conscript/xsbt/launchconfig +++ b/src/main/conscript/xsbt/launchconfig @@ -4,7 +4,7 @@ [app] org: ${sbt.organization-org.scala-sbt} name: sbt - version: ${sbt.version-read(sbt.version)[1.0.0]} + version: ${sbt.version-read(sbt.version)[1.1.0]} class: sbt.xMain components: xsbti,extra cross-versioned: ${sbt.cross.versioned-false} From 7ff88a3e51cd19af845094a8fa0c613fe52bc75f Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Tue, 26 Dec 2017 16:32:29 +0900 Subject: [PATCH 062/356] delete buildinfo.BuildInfo from sbt main sbt-buildinfo plugin have `buildInfoScopedSettings(Compile)` in default. I think it is unnecessary. or we should set "buildinfoPackage in Compile" and "buildinfoObject in Compile" https://github.com/sbt/sbt-buildinfo/blob/v0.7.0/src/main/scala/sbtbuildinfo/BuildInfoPlugin.scala#L11 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f9fe5937b..18413ff99 100644 --- a/build.sbt +++ b/build.sbt @@ -448,7 +448,6 @@ lazy val mainProj = (project in file("main")) // with the sole purpose of providing certain identifiers without qualification (with a package object) lazy val sbtProj = (project in file("sbt")) .dependsOn(mainProj, scriptedSbtProj % "test->test") - .enablePlugins(BuildInfoPlugin) .settings( baseSettings, name := "sbt", @@ -459,6 +458,7 @@ lazy val sbtProj = (project in file("sbt")) mimaSettings, mimaBinaryIssueFilters ++= sbtIgnoredProblems, addBuildInfoToConfig(Test), + BuildInfoPlugin.buildInfoDefaultSettings, buildInfoObject in Test := "TestBuildInfo", buildInfoKeys in Test := Seq[BuildInfoKey](fullClasspath in Compile), connectInput in run in Test := true, From afd214d4b0cfcd6b14c89857eed49e0c337ecca6 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Wed, 10 Jan 2018 12:48:07 +0900 Subject: [PATCH 063/356] update mimaPreviousArtifacts. add sbt 1.1.0 --- build.sbt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index acee0ff52..1c8ffd801 100644 --- a/build.sbt +++ b/build.sbt @@ -76,9 +76,11 @@ def testedBaseSettings: Seq[Setting[_]] = baseSettings ++ testDependencies val mimaSettings = Def settings ( - mimaPreviousArtifacts := (0 to 4).map { v => - organization.value % moduleName.value % s"1.0.$v" cross (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled) - }.toSet + mimaPreviousArtifacts := { + ((0 to 4).map(v => s"1.0.$v") ++ (0 to 0).map(v => s"1.1.$v")).map{ v => + organization.value % moduleName.value % v cross (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled) + }.toSet + } ) lazy val sbtRoot: Project = (project in file(".")) @@ -336,6 +338,7 @@ lazy val commandProj = (project in file("main-command")) contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats, mimaSettings, mimaBinaryIssueFilters ++= Vector( + exclude[DirectMissingMethodProblem]("sbt.BasicCommands.rebootOptionParser"), // Changed the signature of Server method. nacho cheese. exclude[DirectMissingMethodProblem]("sbt.internal.server.Server.*"), // Added method to ServerInstance. This is also internal. @@ -397,6 +400,9 @@ lazy val mainSettingsProj = (project in file("main-settings")) name := "Main Settings", resourceGenerators in Compile += generateToolboxClasspath.taskValue, mimaSettings, + mimaBinaryIssueFilters ++= Seq( + exclude[DirectMissingMethodProblem]("sbt.Scope.display012StyleMasked"), + ), ) .configure( addSbtIO, @@ -467,6 +473,9 @@ lazy val sbtProj = (project in file("sbt")) lazy val sbtIgnoredProblems = { Vector( + exclude[MissingClassProblem]("buildinfo.BuildInfo"), + exclude[MissingClassProblem]("buildinfo.BuildInfo$"), + // Added more items to Import trait. exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$WatchSource_="), exclude[ReversedMissingMethodProblem]("sbt.Import.WatchSource"), From 57caee6152a8aba14b6746b6b8c1cba132c42cb8 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Wed, 10 Jan 2018 12:48:07 +0900 Subject: [PATCH 064/356] update mimaPreviousArtifacts. add sbt 1.1.0 --- build.sbt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index dcedffd62..c9d250357 100644 --- a/build.sbt +++ b/build.sbt @@ -76,9 +76,11 @@ def testedBaseSettings: Seq[Setting[_]] = baseSettings ++ testDependencies val mimaSettings = Def settings ( - mimaPreviousArtifacts := (0 to 4).map { v => - organization.value % moduleName.value % s"1.0.$v" cross (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled) - }.toSet + mimaPreviousArtifacts := { + ((0 to 4).map(v => s"1.0.$v") ++ (0 to 0).map(v => s"1.1.$v")).map{ v => + organization.value % moduleName.value % v cross (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled) + }.toSet + } ) lazy val sbtRoot: Project = (project in file(".")) @@ -318,6 +320,7 @@ lazy val commandProj = (project in file("main-command")) contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats, mimaSettings, mimaBinaryIssueFilters ++= Vector( + exclude[DirectMissingMethodProblem]("sbt.BasicCommands.rebootOptionParser"), // Changed the signature of Server method. nacho cheese. exclude[DirectMissingMethodProblem]("sbt.internal.server.Server.*"), // Added method to ServerInstance. This is also internal. @@ -379,6 +382,9 @@ lazy val mainSettingsProj = (project in file("main-settings")) name := "Main Settings", resourceGenerators in Compile += generateToolboxClasspath.taskValue, mimaSettings, + mimaBinaryIssueFilters ++= Seq( + exclude[DirectMissingMethodProblem]("sbt.Scope.display012StyleMasked"), + ), ) .configure( addSbtIO, @@ -449,6 +455,9 @@ lazy val sbtProj = (project in file("sbt")) lazy val sbtIgnoredProblems = { Vector( + exclude[MissingClassProblem]("buildinfo.BuildInfo"), + exclude[MissingClassProblem]("buildinfo.BuildInfo$"), + // Added more items to Import trait. exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$WatchSource_="), exclude[ReversedMissingMethodProblem]("sbt.Import.WatchSource"), From cb2042c2833477d7295266a7ff53b568b54a0740 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 10 Jan 2018 16:08:13 +0000 Subject: [PATCH 065/356] Upgrade to sbt 1.1.0 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 394cb75cf..8b697bbb9 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.0.4 +sbt.version=1.1.0 From cdba3e6beab32d67617b0999dfcc7b495f19d878 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 11 Jan 2018 14:13:21 +0000 Subject: [PATCH 066/356] Add, configure & enforce sbt-whitesource --- .travis.yml | 8 +++++--- build.sbt | 9 +++++++++ project/plugins.sbt | 9 +++++---- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4d33b9c23..a8a539237 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,8 +16,10 @@ matrix: fast_finish: true env: + global: + - secure: d3bu2KNwsVHwfhbGgO+gmRfDKBJhfICdCJFGWKf2w3Gv86AJZX9nuTYRxz0KtdvEHO5Xw8WTBZLPb2thSJqhw9OCm4J8TBAVqCP0ruUj4+aqBUFy4bVexQ6WKE6nWHs4JPzPk8c6uC1LG3hMuzlC8RGETXtL/n81Ef1u7NjyXjs= matrix: - - SBT_CMD=";mimaReportBinaryIssues ;scalafmt::test ;test:scalafmt::test ;sbt:scalafmt::test ;headerCheck ;test:headerCheck ;test:compile ;mainSettingsProj/test ;safeUnitTests ;otherUnitTests" + - SBT_CMD=";mimaReportBinaryIssues ;scalafmt::test ;test:scalafmt::test ;sbt:scalafmt::test ;headerCheck ;test:headerCheck ;whitesourceCheckPolicies ;test:compile ;mainSettingsProj/test ;safeUnitTests ;otherUnitTests" - SBT_CMD="scripted actions/*" - SBT_CMD="scripted apiinfo/* compiler-project/* ivy-deps-management/*" - SBT_CMD="scripted dependency-management/*1of4" @@ -46,5 +48,5 @@ script: - sbt -J-XX:ReservedCodeCacheSize=128m -J-Xmx800M -J-Xms800M -J-server "$SBT_CMD" before_cache: - - find $HOME/.ivy2 -name "ivydata-*.properties" -print -delete - - find $HOME/.sbt -name "*.lock" -print -delete + - find $HOME/.ivy2 -name "ivydata-*.properties" -delete + - find $HOME/.sbt -name "*.lock" -delete diff --git a/build.sbt b/build.sbt index 1c8ffd801..2f4c132f8 100644 --- a/build.sbt +++ b/build.sbt @@ -707,3 +707,12 @@ def customCommands: Seq[Setting[_]] = Seq( state } ) + +inThisBuild(Seq( + whitesourceProduct := "Lightbend Reactive Platform", + whitesourceAggregateProjectName := "sbt-master", + whitesourceAggregateProjectToken := "e7a1e55518c0489a98e9c7430c8b2ccd53d9f97c12ed46148b592ebe4c8bf128", + whitesourceIgnoredScopes ++= Seq("plugin", "scalafmt", "sxr"), + whitesourceFailOnError := sys.env.contains("WHITESOURCE_PASSWORD"), // fail if pwd is present + whitesourceForceCheckAllDependencies := true, +)) diff --git a/project/plugins.sbt b/project/plugins.sbt index 7ea70f8ea..dfc4564bf 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,8 @@ scalaVersion := "2.12.4" scalacOptions ++= Seq("-feature", "-language:postfixOps") -addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.5") -addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.2") -addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") -addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0") +addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.5") +addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.2") +addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0") +addSbtPlugin("com.lightbend" % "sbt-whitesource" % "0.1.9") From e58b30329706b5119ef9cdba716eb005f0b2b454 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 13 Jan 2018 03:27:11 -0500 Subject: [PATCH 067/356] 1.1.1-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index c9d250357..a4e810d3b 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def buildLevelSettings: Seq[Setting[_]] = inThisBuild( Seq( organization := "org.scala-sbt", - version := "1.1.0-SNAPSHOT", + version := "1.1.1-SNAPSHOT", description := "sbt is an interactive build tool", bintrayOrganization := Some("sbt"), bintrayRepository := { From 0885233281151877ae5613ab87e095dbff1575e2 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 10 Jan 2018 10:44:45 +0000 Subject: [PATCH 068/356] Deprecates Extracted#append for appendWithSession. .. and appendWithoutSession. --- main/src/main/scala/sbt/Extracted.scala | 23 +++++++++++++++++++++-- notes/1.1.1/fix-Extracted.append.md | 11 +++++++++++ notes/{1.0.2 => 1.1.1}/sample.md | 0 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 notes/1.1.1/fix-Extracted.append.md rename notes/{1.0.2 => 1.1.1}/sample.md (100%) diff --git a/main/src/main/scala/sbt/Extracted.scala b/main/src/main/scala/sbt/Extracted.scala index 80243ada1..f507fa2c7 100644 --- a/main/src/main/scala/sbt/Extracted.scala +++ b/main/src/main/scala/sbt/Extracted.scala @@ -120,10 +120,29 @@ final case class Extracted(structure: BuildStructure, structure.data.get(scope, key) getOrElse sys.error( display.show(ScopedKey(scope, key)) + " is undefined.") - def append(settings: Seq[Setting[_]], state: State): State = { + @deprecated("This discards session settings. Migrate to appendWithSession or appendWithoutSession.", "1.2.0") + def append(settings: Seq[Setting[_]], state: State): State = + appendWithoutSession(settings, state) + + /** Appends the given settings to all the build state settings, including session settings. */ + def appendWithSession(settings: Seq[Setting[_]], state: State): State = + appendImpl(settings, state, session.mergeSettings) + + /** + * Appends the given settings to the original build state settings, discarding any settings + * appended to the session in the process. + */ + def appendWithoutSession(settings: Seq[Setting[_]], state: State): State = + appendImpl(settings, state, session.original) + + private[this] def appendImpl( + settings: Seq[Setting[_]], + state: State, + sessionSettings: Seq[Setting[_]], + ): State = { val appendSettings = Load.transformSettings(Load.projectScope(currentRef), currentRef.build, rootProject, settings) - val newStructure = Load.reapply(session.original ++ appendSettings, structure) + val newStructure = Load.reapply(sessionSettings ++ appendSettings, structure) Project.setProject(session, newStructure, state) } } diff --git a/notes/1.1.1/fix-Extracted.append.md b/notes/1.1.1/fix-Extracted.append.md new file mode 100644 index 000000000..56e450391 --- /dev/null +++ b/notes/1.1.1/fix-Extracted.append.md @@ -0,0 +1,11 @@ +[@dwijnand]: https://github.com/dwijnand + +[#3865]: https://github.com/sbt/sbt/pull/3865 + +### Fixes with compatibility implications + +### Improvements + +- Deprecates `Extracted#append` in favour of `appendWithSession` or `appendWithoutSession`. [#3865][] by [@dwijnand][] + +### Bug fixes diff --git a/notes/1.0.2/sample.md b/notes/1.1.1/sample.md similarity index 100% rename from notes/1.0.2/sample.md rename to notes/1.1.1/sample.md From 113656aed18bd159bd62a5083f6d61c4f799ac98 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 16 Jan 2018 10:02:35 +0000 Subject: [PATCH 069/356] Remove compile warnings --- main-actions/src/main/scala/sbt/Package.scala | 4 +- main-actions/src/main/scala/sbt/Sync.scala | 10 +- .../sbt/internal/client/NetworkClient.scala | 1 + .../scala/sbt/internal/server/Server.scala | 2 +- .../main/scala/sbt/std/TaskLinterDSL.scala | 1 + main/src/main/scala/sbt/Defaults.scala | 4 +- main/src/main/scala/sbt/MainLoop.scala | 1 + main/src/main/scala/sbt/Resolvers.scala | 2 +- main/src/main/scala/sbt/TemplateCommand.scala | 4 +- .../scala/sbt/internal/CommandExchange.scala | 4 +- .../scala/sbt/internal/ConsoleProject.scala | 2 +- .../main/scala/sbt/internal/TaskTimings.scala | 2 +- .../sbt/internal/server/Definition.scala | 250 +++++++++--------- .../server/LanguageServerProtocol.scala | 3 + .../sbt/internal/server/NetworkChannel.scala | 1 + .../sbt/internal/server/DefinitionTest.scala | 25 +- .../codec/JsonRpcResponseMessageFormats.scala | 3 +- run/src/main/scala/sbt/Run.scala | 2 +- run/src/main/scala/sbt/TrapExit.scala | 4 +- .../src/main/scala/sbt/test/SbtHandler.scala | 1 + .../main/scala/sbt/CompletionService.scala | 4 +- .../scala/sbt/ConcurrentRestrictions.scala | 4 +- .../scala/sbt/JUnitXmlTestsListener.scala | 2 +- .../main/scala/sbt/TestStatusReporter.scala | 2 +- 24 files changed, 176 insertions(+), 162 deletions(-) diff --git a/main-actions/src/main/scala/sbt/Package.scala b/main-actions/src/main/scala/sbt/Package.scala index 3297d3b4a..080667e6a 100644 --- a/main-actions/src/main/scala/sbt/Package.scala +++ b/main-actions/src/main/scala/sbt/Package.scala @@ -85,8 +85,10 @@ object Package { } def setVersion(main: Attributes): Unit = { val version = Attributes.Name.MANIFEST_VERSION - if (main.getValue(version) eq null) + if (main.getValue(version) eq null) { main.put(version, "1.0") + () + } } def addSpecManifestAttributes(name: String, version: String, orgName: String): PackageOption = { import Attributes.Name._ diff --git a/main-actions/src/main/scala/sbt/Sync.scala b/main-actions/src/main/scala/sbt/Sync.scala index b5024f2ae..deec0ac34 100644 --- a/main-actions/src/main/scala/sbt/Sync.scala +++ b/main-actions/src/main/scala/sbt/Sync.scala @@ -71,11 +71,11 @@ object Sync { def copy(source: File, target: File): Unit = if (source.isFile) IO.copyFile(source, target, true) - else if (!target.exists) // we don't want to update the last modified time of an existing directory - { - IO.createDirectory(target) - IO.copyLastModified(source, target) - } + else if (!target.exists) { // we don't want to update the last modified time of an existing directory + IO.createDirectory(target) + IO.copyLastModified(source, target) + () + } def noDuplicateTargets(relation: Relation[File, File]): Unit = { val dups = relation.reverseMap 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 ff5c5661b..d3cd2dc0e 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -126,6 +126,7 @@ object NetworkClient { def run(arguments: List[String]): Unit = try { new NetworkClient(arguments) + () } catch { case NonFatal(e) => println(e.getMessage) } diff --git a/main-command/src/main/scala/sbt/internal/server/Server.scala b/main-command/src/main/scala/sbt/internal/server/Server.scala index 521c8d399..8104a1736 100644 --- a/main-command/src/main/scala/sbt/internal/server/Server.scala +++ b/main-command/src/main/scala/sbt/internal/server/Server.scala @@ -94,7 +94,7 @@ private[sbt] object Server { def tryClient(f: => Socket): Unit = { if (portfile.exists) { Try { f } match { - case Failure(e) => () + case Failure(_) => () case Success(socket) => socket.close() throw new AlreadyRunningException() diff --git a/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala b/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala index 8ae98e8de..a328cee70 100644 --- a/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala +++ b/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala @@ -48,6 +48,7 @@ abstract class BaseTaskLinterDSL extends LinterDSL { case _ => exprAtUseSite } uncheckedWrappers.add(removedSbtWrapper) + () } case _ => } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 0b82ce8ea..5c030829f 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -500,7 +500,7 @@ object Defaults extends BuildCommon { }, compileIncSetup := compileIncSetupTask.value, console := consoleTask.value, - collectAnalyses := Definition.collectAnalysesTask.value, + collectAnalyses := Definition.collectAnalysesTask.map(_ => ()).value, consoleQuick := consoleQuickTask.value, discoveredMainClasses := (compile map discoverMainClasses storeAs discoveredMainClasses xtriggeredBy compile).value, discoveredSbtPlugins := discoverSbtPluginNames.value, @@ -1373,7 +1373,7 @@ object Defaults extends BuildCommon { private[this] def exported(w: PrintWriter, command: String): Seq[String] => Unit = args => w.println((command +: args).mkString(" ")) - private[this] def exported(s: TaskStreams, command: String): Seq[String] => Unit = args => { + private[this] def exported(s: TaskStreams, command: String): Seq[String] => Unit = { val w = s.text(ExportStream) try exported(w, command) finally w.close() // workaround for #937 diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index 83b38b4c8..110a78fa4 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -34,6 +34,7 @@ object MainLoop { runLoggedLoop(state, state.globalLogging.backing) } finally { Runtime.getRuntime.removeShutdownHook(shutdownHook) + () } } diff --git a/main/src/main/scala/sbt/Resolvers.scala b/main/src/main/scala/sbt/Resolvers.scala index c574c55aa..36afc0217 100644 --- a/main/src/main/scala/sbt/Resolvers.scala +++ b/main/src/main/scala/sbt/Resolvers.scala @@ -40,7 +40,7 @@ object Resolvers { val to = uniqueSubdirectoryFor(info.uri, in = info.staging) Some { () => - creates(to) { IO.unzipURL(url, to) } + creates(to) { IO.unzipURL(url, to); () } } } diff --git a/main/src/main/scala/sbt/TemplateCommand.scala b/main/src/main/scala/sbt/TemplateCommand.scala index e02b1a6a2..51f67bb17 100644 --- a/main/src/main/scala/sbt/TemplateCommand.scala +++ b/main/src/main/scala/sbt/TemplateCommand.scala @@ -75,8 +75,10 @@ private[sbt] object TemplateCommandUtil { private def runTemplate(info: TemplateResolverInfo, arguments: List[String], - loader: ClassLoader): Unit = + loader: ClassLoader): Unit = { call(info.implementationClass, "run", loader)(classOf[Array[String]])(arguments.toArray) + () + } private def infoLoader( info: TemplateResolverInfo, diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 33fbfa986..2ce2819fa 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -156,9 +156,9 @@ private[sbt] final class CommandExchange { Try(Await.ready(x.ready, Duration(d))) x.ready.value match { case Some(Success(_)) => - // rememeber to shutdown only when the server comes up + // remember to shutdown only when the server comes up server = Some(x) - case Some(Failure(e: AlreadyRunningException)) => + case Some(Failure(_: AlreadyRunningException)) => s.log.warn( "sbt server could not start because there's another instance of sbt running on this build.") s.log.warn("Running multiple instances is unsupported") diff --git a/main/src/main/scala/sbt/internal/ConsoleProject.scala b/main/src/main/scala/sbt/internal/ConsoleProject.scala index bc3756460..866d4bd5e 100644 --- a/main/src/main/scala/sbt/internal/ConsoleProject.scala +++ b/main/src/main/scala/sbt/internal/ConsoleProject.scala @@ -48,7 +48,7 @@ object ConsoleProject { options, initCommands, cleanupCommands - )(Some(unit.loader), bindings) + )(Some(unit.loader), bindings).get } /** Conveniences for consoleProject that shouldn't normally be used for builds. */ diff --git a/main/src/main/scala/sbt/internal/TaskTimings.scala b/main/src/main/scala/sbt/internal/TaskTimings.scala index 51ccfcaa1..3586eb4b1 100644 --- a/main/src/main/scala/sbt/internal/TaskTimings.scala +++ b/main/src/main/scala/sbt/internal/TaskTimings.scala @@ -61,7 +61,7 @@ private[sbt] final class TaskTimings(shutdown: Boolean) extends ExecuteProgress[ } } def ready(state: Unit, task: Task[_]) = () - def workStarting(task: Task[_]) = timings.put(task, System.nanoTime) + def workStarting(task: Task[_]) = { timings.put(task, System.nanoTime); () } def workFinished[T](task: Task[T], result: Either[Task[T], Result[T]]) = { timings.put(task, System.nanoTime - timings.get(task)) result.left.foreach { t => diff --git a/main/src/main/scala/sbt/internal/server/Definition.scala b/main/src/main/scala/sbt/internal/server/Definition.scala index 01881b614..bc6a0a220 100644 --- a/main/src/main/scala/sbt/internal/server/Definition.scala +++ b/main/src/main/scala/sbt/internal/server/Definition.scala @@ -9,38 +9,47 @@ package sbt package internal package server -import sbt.io.IO -import sbt.internal.inc.MixedAnalyzingCompiler -import sbt.internal.langserver.ErrorCodes -import sbt.util.Logger +import java.io.File +import java.net.URI +import java.nio.file._ + import scala.annotation.tailrec +import scala.collection.JavaConverters._ import scala.concurrent.{ ExecutionContext, Future } -import scala.concurrent.duration.Duration.Inf -import scala.util.matching.Regex.MatchIterator -import java.nio.file.{ Files, Paths } -import sbt.StandardMain +import scala.concurrent.duration.Duration +import scala.reflect.NameTransformer +import scala.tools.reflect.{ ToolBox, ToolBoxError } +import scala.util.matching.Regex + +import sjsonnew.JsonFormat +import sjsonnew.shaded.scalajson.ast.unsafe.JValue +import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter } + +import scalacache._ + +import sbt.io.IO +import sbt.internal.inc.{ Analysis, MixedAnalyzingCompiler } +import sbt.internal.inc.JavaInterfaceUtil._ +import sbt.internal.protocol.JsonRpcResponseError +import sbt.internal.protocol.codec.JsonRPCProtocol +import sbt.internal.langserver +import sbt.internal.langserver.{ ErrorCodes, Location, Position, Range, TextDocumentPositionParams } +import sbt.util.Logger +import sbt.Keys._ private[sbt] object Definition { - import java.net.URI - import Keys._ - import sbt.internal.inc.Analysis - import sbt.internal.inc.JavaInterfaceUtil._ - val AnalysesKey = "lsp.definition.analyses.key" - - import sjsonnew.JsonFormat def send[A: JsonFormat](source: CommandSource, execId: String)(params: A): Unit = { for { channel <- StandardMain.exchange.channels.collectFirst { case c if c.name == source.channelName => c } - } yield { + } { channel.publishEvent(params, Option(execId)) } } object textProcessor { private val isIdentifier = { - import scala.tools.reflect.{ ToolBox, ToolBoxError } lazy val tb = scala.reflect.runtime.universe .runtimeMirror(this.getClass.getClassLoader) @@ -58,23 +67,14 @@ private[sbt] object Definition { private def findInBackticks(line: String, point: Int): Option[String] = { val (even, odd) = line.zipWithIndex - .collect { - case (char, backtickIndex) if char == '`' => - backtickIndex - } + .collect { case (char, backtickIndex) if char == '`' => backtickIndex } .zipWithIndex - .partition { bs => - val (_, index) = bs - index % 2 == 0 - } + .partition { case (_, index) => index % 2 == 0 } + even - .collect { - case (backtickIndex, _) => backtickIndex - } + .collect { case (backtickIndex, _) => backtickIndex } .zip { - odd.collect { - case (backtickIndex, _) => backtickIndex + 1 - } + odd.collect { case (backtickIndex, _) => backtickIndex + 1 } } .collectFirst { case (from, to) if from <= point && point < to => line.slice(from, to) @@ -83,43 +83,43 @@ private[sbt] object Definition { def identifier(line: String, point: Int): Option[String] = findInBackticks(line, point).orElse { val whiteSpaceReg = "(\\s|\\.)+".r + val (zero, end) = fold(Seq.empty)(whiteSpaceReg.findAllIn(line)) .collect { case (white, ind) => (ind, ind + white.length) } - .fold((0, line.length)) { (z, e) => - val (from, to) = e - val (left, right) = z - (if (to > left && to <= point) to else left, - if (from < right && from >= point) from else right) + .fold((0, line.length)) { + case ((left, right), (from, to)) => + val zero = if (to > left && to <= point) to else left + val end = if (from < right && from >= point) from else right + (zero, end) } + val ranges = for { from <- zero to point to <- point to end } yield (from -> to) + ranges - .sortBy { - case (from, to) => -(to - from) - } - .foldLeft(Seq.empty[String]) { (z, e) => - val (from, to) = e - val fragment = line.slice(from, to).trim - z match { - case Nil if fragment.nonEmpty && isIdentifier(fragment) => fragment +: z - case h +: _ if h.length < fragment.length && isIdentifier(fragment) => - Seq(fragment) - case h +: _ if h.length == fragment.length && isIdentifier(fragment) => - fragment +: z - case z => z - } + .sortBy { case (from, to) => -(to - from) } + .foldLeft(List.empty[String]) { + case (z, (from, to)) => + val fragment = line.slice(from, to).trim + if (isIdentifier(fragment)) + z match { + case Nil if fragment.nonEmpty => fragment :: z + case h :: _ if h.length < fragment.length => fragment :: Nil + case h :: _ if h.length == fragment.length => fragment :: z + case _ => z + } else z } .headOption } private def asClassObjectIdentifier(sym: String) = Seq(s".$sym", s".$sym$$", s"$$$sym", s"$$$sym$$") + def potentialClsOrTraitOrObj(sym: String): PartialFunction[String, String] = { - import scala.reflect.NameTransformer val encodedSym = NameTransformer.encode(sym.toSeq match { case '`' +: body :+ '`' => body.mkString case noBackticked => noBackticked.mkString @@ -135,17 +135,17 @@ private[sbt] object Definition { } @tailrec - private def fold(z: Seq[(String, Int)])(it: MatchIterator): Seq[(String, Int)] = { + private def fold(z: Seq[(String, Int)])(it: Regex.MatchIterator): Seq[(String, Int)] = { if (!it.hasNext) z else fold(z :+ (it.next() -> it.start))(it) } def classTraitObjectInLine(sym: String)(line: String): Seq[(String, Int)] = { - import scala.util.matching.Regex.quote - val potentials = - Seq(s"object\\s+${quote(sym)}".r, - s"trait\\s+${quote(sym)} *\\[?".r, - s"class\\s+${quote(sym)} *\\[?".r) + val potentials = Seq( + s"object\\s+${Regex quote sym}".r, + s"trait\\s+${Regex quote sym} *\\[?".r, + s"class\\s+${Regex quote sym} *\\[?".r, + ) potentials .flatMap { reg => fold(Seq.empty)(reg.findAllIn(line)) @@ -156,10 +156,7 @@ private[sbt] object Definition { } } - import java.io.File def markPosition(file: File, sym: String): Seq[(File, Long, Long, Long)] = { - import java.nio.file._ - import scala.collection.JavaConverters._ val findInLine = classTraitObjectInLine(sym)(_) Files .lines(file.toPath) @@ -179,43 +176,49 @@ private[sbt] object Definition { } } - import sbt.internal.langserver.TextDocumentPositionParams - import sjsonnew.shaded.scalajson.ast.unsafe.JValue private def getDefinition(jsonDefinition: JValue): Option[TextDocumentPositionParams] = { - import sbt.internal.langserver.codec.JsonProtocol._ - import sjsonnew.support.scalajson.unsafe.Converter + import langserver.codec.JsonProtocol._ Converter.fromJson[TextDocumentPositionParams](jsonDefinition).toOption } - import java.io.File + object AnalysesAccess { + private[this] val AnalysesKey = "lsp.definition.analyses.key" + + private[server] type Analyses = Set[((String, Boolean), Option[Analysis])] + + private[server] def getFrom[F[_]]( + cache: Cache[Any] + )(implicit mode: Mode[F], flags: Flags): F[Option[Analyses]] = + mode.M.map(cache.get(AnalysesKey))(_ map (_.asInstanceOf[Analyses])) + + private[server] def putIn[F[_]]( + cache: Cache[Any], + value: Analyses, + ttl: Option[Duration], + )(implicit mode: Mode[F], flags: Flags): F[Any] = + cache.put(AnalysesKey)(value, ttl) + } + private def storeAnalysis(cacheFile: File, useBinary: Boolean): Option[Analysis] = MixedAnalyzingCompiler .staticCachedStore(cacheFile, !useBinary) .get .toOption - .collect { - case contents => - contents.getAnalysis - } - .collect { - case a: Analysis => a - } + .map { _.getAnalysis } + .collect { case a: Analysis => a } - import scalacache._ private[sbt] def updateCache[F[_]](cache: Cache[Any])(cacheFile: String, useBinary: Boolean)( implicit mode: Mode[F], flags: Flags): F[Any] = { - mode.M.flatMap(cache.get(AnalysesKey)) { + mode.M.flatMap(AnalysesAccess.getFrom(cache)) { case None => - cache.put(AnalysesKey)(Set(cacheFile -> useBinary -> None), Option(Inf)) + AnalysesAccess.putIn(cache, Set(cacheFile -> useBinary -> None), Option(Duration.Inf)) case Some(set) => - cache.put(AnalysesKey)( - set.asInstanceOf[Set[((String, Boolean), Option[Analysis])]].filterNot { - case ((file, _), _) => file == cacheFile - } + (cacheFile -> useBinary -> None), - Option(Inf)) - case _ => mode.M.pure(()) + val newSet = set + .filterNot { case ((file, _), _) => file == cacheFile } + .+(cacheFile -> useBinary -> None) + AnalysesAccess.putIn(cache, newSet, Option(Duration.Inf)) } } @@ -231,14 +234,13 @@ private[sbt] object Definition { private[sbt] def getAnalyses: Future[Seq[Analysis]] = { import scalacache.modes.scalaFuture._ import scala.concurrent.ExecutionContext.Implicits.global - StandardMain.cache - .get(AnalysesKey) - .collect { - case Some(a) => a.asInstanceOf[Set[((String, Boolean), Option[Analysis])]] - } + AnalysesAccess + .getFrom(StandardMain.cache) + .collect { case Some(a) => a } .map { caches => - val (working, uninitialized) = caches.partition { cacheAnalysis => - cacheAnalysis._2 != None + val (working, uninitialized) = caches.partition { + case (_, Some(_)) => true + case (_, None) => false } val addToCache = uninitialized.collect { case (title @ (file, useBinary), _) if Files.exists(Paths.get(file)) => @@ -246,7 +248,7 @@ private[sbt] object Definition { } val validCaches = working ++ addToCache if (addToCache.nonEmpty) - StandardMain.cache.put(AnalysesKey)(validCaches, Option(Inf)) + AnalysesAccess.putIn(StandardMain.cache, validCaches, Option(Duration.Inf)) validCaches.toSeq.collect { case (_, Some(analysis)) => analysis @@ -254,19 +256,19 @@ private[sbt] object Definition { } } - def lspDefinition(jsonDefinition: JValue, - requestId: String, - commandSource: CommandSource, - log: Logger)(implicit ec: ExecutionContext): Future[Unit] = Future { + def lspDefinition( + jsonDefinition: JValue, + requestId: String, + commandSource: CommandSource, + log: Logger, + )(implicit ec: ExecutionContext): Future[Unit] = Future { val LspDefinitionLogHead = "lsp-definition" - import sjsonnew.support.scalajson.unsafe.CompactPrinter - log.debug(s"$LspDefinitionLogHead json request: ${CompactPrinter(jsonDefinition)}") + val jsonDefinitionString = CompactPrinter(jsonDefinition) + log.debug(s"$LspDefinitionLogHead json request: $jsonDefinitionString") lazy val analyses = getAnalyses - val definition = getDefinition(jsonDefinition) - definition + getDefinition(jsonDefinition) .flatMap { definition => val uri = URI.create(definition.textDocument.uri) - import java.nio.file._ Files .lines(Paths.get(uri)) .skip(definition.position.line) @@ -274,11 +276,10 @@ private[sbt] object Definition { .toOption .flatMap { line => log.debug(s"$LspDefinitionLogHead found line: $line") - textProcessor - .identifier(line, definition.position.character.toInt) + textProcessor.identifier(line, definition.position.character.toInt) } - } - .map { sym => + } match { + case Some(sym) => log.debug(s"symbol $sym") analyses .map { analyses => @@ -291,40 +292,39 @@ private[sbt] object Definition { log.debug(s"$LspDefinitionLogHead potentials: $classes") classes .flatMap { className => - analysis.relations.definesClass(className) ++ analysis.relations - .libraryDefinesClass(className) + analysis.relations.definesClass(className) ++ + analysis.relations.libraryDefinesClass(className) } .flatMap { classFile => textProcessor.markPosition(classFile, sym).collect { case (file, line, from, to) => - import sbt.internal.langserver.{ Location, Position, Range } - Location(IO.toURI(file).toString, - Range(Position(line, from), Position(line, to))) + Location( + IO.toURI(file).toString, + Range(Position(line, from), Position(line, to)), + ) } } }.seq - log.debug(s"$LspDefinitionLogHead locations ${locations}") - import sbt.internal.langserver.codec.JsonProtocol._ + log.debug(s"$LspDefinitionLogHead locations $locations") + import langserver.codec.JsonProtocol._ send(commandSource, requestId)(locations.toArray) } .recover { - case anyException @ _ => - log.warn( - s"Problem with processing analyses $anyException for ${CompactPrinter(jsonDefinition)}") - import sbt.internal.protocol.JsonRpcResponseError - import sbt.internal.protocol.codec.JsonRPCProtocol._ - send(commandSource, requestId)( - JsonRpcResponseError(ErrorCodes.InternalError, - "Problem with processing analyses.", - None)) + case t => + log.warn(s"Problem with processing analyses $t for $jsonDefinitionString") + val rsp = JsonRpcResponseError( + ErrorCodes.InternalError, + "Problem with processing analyses.", + None, + ) + import JsonRPCProtocol._ + send(commandSource, requestId)(rsp) } - } - .orElse { - log.info(s"Symbol not found in definition request ${CompactPrinter(jsonDefinition)}") - import sbt.internal.langserver.Location - import sbt.internal.langserver.codec.JsonProtocol._ + () + case None => + log.info(s"Symbol not found in definition request $jsonDefinitionString") + import langserver.codec.JsonProtocol._ send(commandSource, requestId)(Array.empty[Location]) - None - } + } } } diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index ce1131d0f..637bd97d0 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -39,6 +39,7 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { notification.method match { case "textDocument/didSave" => append(Exec(";compile; collectAnalyses", None, Some(CommandSource(name)))) + () case u => log.debug(s"Unhandled notification received: $u") } } @@ -69,9 +70,11 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { case "textDocument/definition" => import scala.concurrent.ExecutionContext.Implicits.global Definition.lspDefinition(json, request.id, CommandSource(name), log) + () case "sbt/exec" => val param = Converter.fromJson[SbtExecParams](json).get append(Exec(param.commandLine, Some(request.id), Some(CommandSource(name)))) + () case "sbt/setting" => { import sbt.protocol.codec.JsonProtocol._ val param = Converter.fromJson[Q](json).get diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 7c27063de..3ad7f2fc5 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -331,6 +331,7 @@ final class NetworkChannel(val name: String, if (initialized) { append( Exec(cmd.commandLine, cmd.execId orElse Some(Exec.newExecId), Some(CommandSource(name)))) + () } else { log.warn(s"ignoring command $cmd before initialization") } diff --git a/main/src/test/scala/sbt/internal/server/DefinitionTest.scala b/main/src/test/scala/sbt/internal/server/DefinitionTest.scala index 534d77083..5cbff9163 100644 --- a/main/src/test/scala/sbt/internal/server/DefinitionTest.scala +++ b/main/src/test/scala/sbt/internal/server/DefinitionTest.scala @@ -9,8 +9,6 @@ package sbt package internal package server -import sbt.internal.inc.Analysis - class DefinitionTest extends org.specs2.mutable.Specification { import Definition.textProcessor @@ -126,9 +124,12 @@ class DefinitionTest extends org.specs2.mutable.Specification { textProcessor.classTraitObjectInLine("B")("trait A ") must be empty } } + "definition" should { + import scalacache.caffeine._ import scalacache.modes.sync._ + "cache data in cache" in { val cache = CaffeineCache[Any] val cacheFile = "Test.scala" @@ -136,12 +137,11 @@ class DefinitionTest extends org.specs2.mutable.Specification { Definition.updateCache(cache)(cacheFile, useBinary) - val actual = cache.get(Definition.AnalysesKey) + val actual = Definition.AnalysesAccess.getFrom(cache) - actual.collect { - case s => s.asInstanceOf[Set[((String, Boolean), Option[Analysis])]] - }.get should contain("Test.scala" -> true -> None) + actual.get should contain("Test.scala" -> true -> None) } + "replace cache data in cache" in { val cache = CaffeineCache[Any] val cacheFile = "Test.scala" @@ -151,12 +151,11 @@ class DefinitionTest extends org.specs2.mutable.Specification { Definition.updateCache(cache)(cacheFile, falseUseBinary) Definition.updateCache(cache)(cacheFile, useBinary) - val actual = cache.get(Definition.AnalysesKey) + val actual = Definition.AnalysesAccess.getFrom(cache) - actual.collect { - case s => s.asInstanceOf[Set[((String, Boolean), Option[Analysis])]] - }.get should contain("Test.scala" -> true -> None) + actual.get should contain("Test.scala" -> true -> None) } + "cache more data in cache" in { val cache = CaffeineCache[Any] val cacheFile = "Test.scala" @@ -167,11 +166,9 @@ class DefinitionTest extends org.specs2.mutable.Specification { Definition.updateCache(cache)(otherCacheFile, otherUseBinary) Definition.updateCache(cache)(cacheFile, useBinary) - val actual = cache.get(Definition.AnalysesKey) + val actual = Definition.AnalysesAccess.getFrom(cache) - actual.collect { - case s => s.asInstanceOf[Set[((String, Boolean), Option[Analysis])]] - }.get should contain("Test.scala" -> true -> None, "OtherTest.scala" -> false -> None) + actual.get should contain("Test.scala" -> true -> None, "OtherTest.scala" -> false -> None) } } } diff --git a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala index 56cec6321..8367ea906 100644 --- a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala +++ b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala @@ -27,7 +27,8 @@ trait JsonRpcResponseMessageFormats { val id = try { unbuilder.readField[Option[String]]("id") } catch { - case _: DeserializationException => unbuilder.readField[Option[Long]]("id") map { _.toString } + case _: DeserializationException => + unbuilder.readField[Option[Long]]("id") map { _.toString } } val result = unbuilder.lookupField("result") map { diff --git a/run/src/main/scala/sbt/Run.scala b/run/src/main/scala/sbt/Run.scala index ff5426b6d..15469f86e 100644 --- a/run/src/main/scala/sbt/Run.scala +++ b/run/src/main/scala/sbt/Run.scala @@ -90,7 +90,7 @@ class Run(instance: ScalaInstance, trapExit: Boolean, nativeTmp: File) extends S val currentThread = Thread.currentThread val oldLoader = Thread.currentThread.getContextClassLoader currentThread.setContextClassLoader(loader) - try { main.invoke(null, options.toArray[String]) } finally { + try { main.invoke(null, options.toArray[String]); () } finally { currentThread.setContextClassLoader(oldLoader) } } diff --git a/run/src/main/scala/sbt/TrapExit.scala b/run/src/main/scala/sbt/TrapExit.scala index d1a16ef52..54cf83760 100644 --- a/run/src/main/scala/sbt/TrapExit.scala +++ b/run/src/main/scala/sbt/TrapExit.scala @@ -152,7 +152,7 @@ private final class TrapExit(delegateManager: SecurityManager) extends SecurityM def runManaged(f: Supplier[Unit], xlog: xsbti.Logger): Int = { val _ = running.incrementAndGet() try runManaged0(f, xlog) - finally running.decrementAndGet() + finally { running.decrementAndGet(); () } } private[this] def runManaged0(f: Supplier[Unit], xlog: xsbti.Logger): Int = { val log: Logger = xlog @@ -264,6 +264,7 @@ private final class TrapExit(delegateManager: SecurityManager) extends SecurityM val old = groups.putIfAbsent(groupID, new WeakReference(g)) if (old.isEmpty) { // wasn't registered threadToApp.put(groupID, this) + () } } @@ -299,6 +300,7 @@ private final class TrapExit(delegateManager: SecurityManager) extends SecurityM threadToApp.remove(id) threads.remove(id) groups.remove(id) + () } /** Final cleanup for this application after it has terminated. */ diff --git a/scripted/sbt/src/main/scala/sbt/test/SbtHandler.scala b/scripted/sbt/src/main/scala/sbt/test/SbtHandler.scala index 04b85e572..12598df78 100644 --- a/scripted/sbt/src/main/scala/sbt/test/SbtHandler.scala +++ b/scripted/sbt/src/main/scala/sbt/test/SbtHandler.scala @@ -65,6 +65,7 @@ final class SbtHandler(directory: File, try { send("exit", server) process.exitValue() + () } catch { case _: IOException => process.destroy() } diff --git a/tasks/src/main/scala/sbt/CompletionService.scala b/tasks/src/main/scala/sbt/CompletionService.scala index f6fc3a957..f88f6885a 100644 --- a/tasks/src/main/scala/sbt/CompletionService.scala +++ b/tasks/src/main/scala/sbt/CompletionService.scala @@ -23,13 +23,13 @@ import java.util.concurrent.{ object CompletionService { def apply[A, T](poolSize: Int): (CompletionService[A, T], () => Unit) = { val pool = Executors.newFixedThreadPool(poolSize) - (apply[A, T](pool), () => pool.shutdownNow()) + (apply[A, T](pool), () => { pool.shutdownNow(); () }) } def apply[A, T](x: Executor): CompletionService[A, T] = apply(new ExecutorCompletionService[T](x)) def apply[A, T](completion: JCompletionService[T]): CompletionService[A, T] = new CompletionService[A, T] { - def submit(node: A, work: () => T) = CompletionService.submit(work, completion) + def submit(node: A, work: () => T) = { CompletionService.submit(work, completion); () } def take() = completion.take().get() } def submit[T](work: () => T, completion: JCompletionService[T]): () => T = { diff --git a/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala b/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala index 40a15d4fd..a827c139a 100644 --- a/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala +++ b/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala @@ -128,7 +128,7 @@ object ConcurrentRestrictions { def completionService[A, R](tags: ConcurrentRestrictions[A], warn: String => Unit): (CompletionService[A, R], () => Unit) = { val pool = Executors.newCachedThreadPool() - (completionService[A, R](pool, tags, warn), () => pool.shutdownNow()) + (completionService[A, R](pool, tags, warn), () => { pool.shutdownNow(); () }) } /** @@ -167,6 +167,7 @@ object ConcurrentRestrictions { if (running == 0) errorAddingToIdle() pending.add(new Enqueue(node, work)) } + () } private[this] def submitValid(node: A, work: () => R) = { running += 1 @@ -192,6 +193,7 @@ object ConcurrentRestrictions { if (!tried.isEmpty) { if (running == 0) errorAddingToIdle() pending.addAll(tried) + () } } else { val next = pending.remove() diff --git a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala index 3e62dcea2..ad547c511 100644 --- a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala +++ b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala @@ -127,7 +127,7 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { val testSuite = new DynamicVariable(null: TestSuite) /**Creates the output Dir*/ - override def doInit() = { targetDir.mkdirs() } + override def doInit() = { targetDir.mkdirs(); () } /** * Starts a new, initially empty Suite with the given name. diff --git a/testing/src/main/scala/sbt/TestStatusReporter.scala b/testing/src/main/scala/sbt/TestStatusReporter.scala index 732e3257c..ab248741f 100644 --- a/testing/src/main/scala/sbt/TestStatusReporter.scala +++ b/testing/src/main/scala/sbt/TestStatusReporter.scala @@ -18,7 +18,7 @@ private[sbt] class TestStatusReporter(f: File) extends TestsListener { private lazy val succeeded = TestStatus.read(f) def doInit = () - def startGroup(name: String): Unit = { succeeded remove name } + def startGroup(name: String): Unit = { succeeded remove name; () } def testEvent(event: TestEvent): Unit = () def endGroup(name: String, t: Throwable): Unit = () def endGroup(name: String, result: TestResult): Unit = { From c00739ebcdec7a6f469940e885285b5944ab05aa Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 16 Jan 2018 14:38:09 +0000 Subject: [PATCH 070/356] Fix sbtOn's prompt & echo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is what it looks like now! 🎉 > sbtOn /s/t-sbtOn [...] [info] Running (fork) sbt.RunFromSourceMain /s/t-sbtOn Listening for transport dt_socket at address: 5005 [warn] sbt version mismatch, current: 1.0.3, in build.properties: "1.1.0", use 'reboot' to use the new value. [info] Loading settings from idea.sbt,global-plugins.sbt ... [info] Loading global plugins from /Users/dnw/.dotfiles/.sbt/1.0/plugins [info] Updating ProjectRef(uri("file:/Users/dnw/.sbt/1.0/plugins/"), "global-plugins")... [info] Done updating. [info] Loading settings from plugins.sbt ... [info] Loading project definition from /s/t-sbtOn/project [info] Updating ProjectRef(uri("file:/s/t-sbtOn/project/"), "t-sbton-build")... [info] Done updating. [info] Loading settings from build.sbt ... [info] Set current project to t (in build file:/s/t-sbtOn/) [info] sbt server started at local:///Users/dnw/.sbt/1.0/server/2c27eaf4c750902a3a41/sock > show baseDirectory [info] /s/t-sbtOn > exit [info] shutting down server [success] Total time: 34 s, completed 16-Jan-2018 14:37:32 > Exception in thread "Thread-17" java.io.IOException: Stream closed at java.lang.ProcessBuilder$NullOutputStream.write(ProcessBuilder.java:433) at java.io.OutputStream.write(OutputStream.java:116) at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82) at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140) at java.io.FilterOutputStream.close(FilterOutputStream.java:158) at scala.sys.process.BasicIO$.$anonfun$input$1(BasicIO.scala:200) at scala.sys.process.BasicIO$.$anonfun$input$1$adapted(BasicIO.scala:198) at scala.sys.process.ProcessBuilderImpl$Simple.$anonfun$run$2(ProcessBuilderImpl.scala:75) at scala.sys.process.ProcessImpl$Spawn$$anon$1.run(ProcessImpl.scala:23) > show {.}/baseDirectory [...] [info] ThisBuild / baseDirectory [info] /d/sbt --- build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sbt b/build.sbt index 2f4c132f8..b9d11c0d6 100644 --- a/build.sbt +++ b/build.sbt @@ -468,6 +468,7 @@ lazy val sbtProj = (project in file("sbt")) buildInfoObject in Test := "TestBuildInfo", buildInfoKeys in Test := Seq[BuildInfoKey](fullClasspath in Compile), connectInput in run in Test := true, + outputStrategy in run in Test := Some(StdoutOutput), ) .configure(addSbtCompilerBridge) From b2290658ba366203d9b2aa6e0845690e00f6d1fd Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 16 Jan 2018 17:04:53 +0000 Subject: [PATCH 071/356] Tweak RunFromSourceMain so compile/run/test work Tested pos/neg compilation of a simple hello world file, running said file & a simple uTest test suite. Uses things already downloaded in ~/.ivy2/cache, & shares ~/.sbt/boot for the compiler-bridge component. --- .../test/scala/sbt/RunFromSourceMain.scala | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala index be79cc54a..bd2f142f7 100644 --- a/sbt/src/test/scala/sbt/RunFromSourceMain.scala +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -12,7 +12,7 @@ import scala.annotation.tailrec import xsbti._ object RunFromSourceMain { - private val sbtVersion = "1.0.3" // "dev" + private val sbtVersion = "1.1.0" // "dev" private val scalaVersion = "2.12.4" def main(args: Array[String]): Unit = args match { @@ -51,6 +51,8 @@ object RunFromSourceMain { def baseDirectory = baseDir def arguments = args.toArray def provider = new AppProvider { appProvider => + def bootDirectory: File = file(sys.props("user.home")) / ".sbt" / "boot" + def scalaHome: File = bootDirectory / s"scala-$scalaVersion" def scalaProvider = new ScalaProvider { scalaProvider => def scalaOrg = "org.scala-lang" def launcher = new Launcher { @@ -60,15 +62,15 @@ object RunFromSourceMain { def app(id: xsbti.ApplicationID, version: String) = appProvider def topLoader = new java.net.URLClassLoader(Array(), null) def globalLock = noGlobalLock - def bootDirectory = file(sys.props("user.home")) / ".sbt" / "boot" - def ivyRepositories = Array() - def appRepositories = Array() - def isOverrideRepositories = false + def bootDirectory = appProvider.bootDirectory def ivyHome = file(sys.props("user.home")) / ".ivy2" + def ivyRepositories = Array(new PredefinedRepository { def id() = Predefined.Local }) + def appRepositories = Array(new PredefinedRepository { def id() = Predefined.Local }) + def isOverrideRepositories = false def checksums = Array("sha1", "md5") } def version = scalaVersion - def libDir: File = launcher.bootDirectory / s"scala-$version" / "lib" + def libDir: File = scalaHome / "lib" def jar(name: String): File = libDir / s"$name.jar" def libraryJar = jar("scala-library") def compilerJar = jar("scala-compiler") @@ -86,6 +88,7 @@ object RunFromSourceMain { CrossValue.Disabled, Nil ) + def appHome: File = scalaHome / id.groupID / id.name / id.version def mainClasspath = buildinfo.TestBuildInfo.fullClasspath.iterator @@ -98,11 +101,11 @@ object RunFromSourceMain { def newMain = new xMain def components = new ComponentProvider { - def componentLocation(id: String) = ??? - def component(componentID: String) = ??? - def defineComponent(componentID: String, components: Array[File]) = ??? - def addToComponent(componentID: String, components: Array[File]) = ??? - def lockFile = ??? + def componentLocation(id: String) = appHome / id + def component(id: String) = IO.listFiles(componentLocation(id), _.isFile) + def defineComponent(id: String, components: Array[File]) = () + def addToComponent(id: String, components: Array[File]) = false + def lockFile = null } } } From ca712acf41c2c5e37e6ce7ac8756ac5a6c6f3cb6 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 13 Jan 2018 03:37:29 -0500 Subject: [PATCH 072/356] Fix tab completion running `sbt console` Fixes #3841 This fixes console task that internally uses JLine. When `console` is started from batch mode, the tab is printed as is. This is because JLine is not initialized yet. Calling `usingTerminal` initializes and restores the terminal afterwards. --- main/src/main/scala/sbt/Defaults.scala | 13 +++++++------ .../scala/sbt/internal/ConsoleProject.scala | 19 ++++++++++++------- notes/1.1.1/console_jline.md | 13 +++++++++++++ notes/{1.0.2 => 1.1.1}/sample.md | 0 4 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 notes/1.1.1/console_jline.md rename notes/{1.0.2 => 1.1.1}/sample.md (100%) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 4fd8d1585..721e08914 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1366,12 +1366,13 @@ object Defaults extends BuildCommon { (compilers in task).value.scalac match { case ac: AnalyzingCompiler => ac.onArgs(exported(s, "scala")) } - (new Console(compiler))(cpFiles, - (scalacOptions in task).value, - loader, - (initialCommands in task).value, - (cleanupCommands in task).value)()(s.log).get - println() + val sc = (scalacOptions in task).value + val ic = (initialCommands in task).value + val cc = (cleanupCommands in task).value + JLine.usingTerminal { _ => + (new Console(compiler))(cpFiles, sc, loader, ic, cc)()(s.log).get + println() + } } private[this] def exported(w: PrintWriter, command: String): Seq[String] => Unit = diff --git a/main/src/main/scala/sbt/internal/ConsoleProject.scala b/main/src/main/scala/sbt/internal/ConsoleProject.scala index bc3756460..c2e2edef2 100644 --- a/main/src/main/scala/sbt/internal/ConsoleProject.scala +++ b/main/src/main/scala/sbt/internal/ConsoleProject.scala @@ -9,6 +9,7 @@ package sbt package internal import sbt.util.Logger +import sbt.internal.util.JLine import sbt.internal.inc.{ ScalaInstance, ZincUtil } import xsbti.compile.ClasspathOptionsUtil @@ -42,13 +43,17 @@ object ConsoleProject { val imports = BuildUtil.getImports(unit.unit) ++ BuildUtil.importAll(bindings.map(_._1)) val importString = imports.mkString("", ";\n", ";\n\n") val initCommands = importString + extra - // TODO - Hook up dsl classpath correctly... - (new Console(compiler))( - unit.classpath, - options, - initCommands, - cleanupCommands - )(Some(unit.loader), bindings) + + JLine.usingTerminal { _ => + // TODO - Hook up dsl classpath correctly... + (new Console(compiler))( + unit.classpath, + options, + initCommands, + cleanupCommands + )(Some(unit.loader), bindings) + } + () } /** Conveniences for consoleProject that shouldn't normally be used for builds. */ diff --git a/notes/1.1.1/console_jline.md b/notes/1.1.1/console_jline.md new file mode 100644 index 000000000..fd851ce16 --- /dev/null +++ b/notes/1.1.1/console_jline.md @@ -0,0 +1,13 @@ + [@eed3si9n]: https://github.com/eed3si9n + + [3841]: https://github.com/sbt/sbt/issues/3841 + [3876]: https://github.com/sbt/sbt/pull/3876 + +### Fixes with compatibility implications + +### Improvements + + +### Bug fixes + +- Fixes tab completion in `console` while running in batch mode as `sbt console`. [#3841][3841]/[#3876][3876] by [@eed3si9n][@eed3si9n] diff --git a/notes/1.0.2/sample.md b/notes/1.1.1/sample.md similarity index 100% rename from notes/1.0.2/sample.md rename to notes/1.1.1/sample.md From 12d2ecfa6294053de525597bf249f83c6e60eaa5 Mon Sep 17 00:00:00 2001 From: jvican Date: Fri, 20 Oct 2017 23:37:55 +0200 Subject: [PATCH 073/356] Enable parallel execution of scripted in the plugin The change to enable batched and parallel execution for scripted was done only for the scripted-sbt project. This pull request enables it for scripted-plugin, so that all sbt plugins in 1.x. can benefit from it. By default, it configures a number of parallel instances of 1 and batch execution is disabled. Users can change the number of parallel sbt hosts running scripted tests via the `scriptedParallelInstances` setting. In some plugins scripted tests', batch execution can cause issues because the first time `>` commands are executed they assume sbt starts up. This error can be fixed by doing `reload` before running the `>` command. Note that the current scripted plugin does not allow parallel execution in non-batched mode. --- .../src/main/scala/sbt/ScriptedPlugin.scala | 49 ++++++++++++------- .../main/scala/sbt/test/ScriptedTests.scala | 19 ++++++- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala b/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala index 4788e0186..a7bfdc907 100644 --- a/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala +++ b/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala @@ -26,6 +26,11 @@ object ScriptedPlugin extends AutoPlugin { val scriptedBufferLog = SettingKey[Boolean]("scripted-buffer-log") val scriptedClasspath = TaskKey[PathFinder]("scripted-classpath") val scriptedTests = TaskKey[AnyRef]("scripted-tests") + val scriptedBatchExecution = + settingKey[Boolean]("Enables or disables batch execution for scripted.") + val scriptedParallelInstances = + settingKey[Int]( + "Configures the number of scripted instances for parallel testing, only used in batch mode.") val scriptedRun = TaskKey[Method]("scripted-run") val scriptedLaunchOpts = SettingKey[Seq[String]]( "scripted-launch-opts", @@ -56,6 +61,8 @@ object ScriptedPlugin extends AutoPlugin { scriptedBufferLog := true, scriptedClasspath := getJars(ScriptedConf).value, scriptedTests := scriptedTestsTask.value, + scriptedParallelInstances := 1, + scriptedBatchExecution := false, scriptedRun := scriptedRunTask.value, scriptedDependencies := { def use[A](@deprecated("unused", "") x: A*): Unit = () // avoid unused warnings @@ -73,15 +80,18 @@ object ScriptedPlugin extends AutoPlugin { ModuleUtilities.getObject("sbt.test.ScriptedTests", loader) } - def scriptedRunTask: Initialize[Task[Method]] = Def.task( - scriptedTests.value.getClass.getMethod("run", - classOf[File], - classOf[Boolean], - classOf[Array[String]], - classOf[File], - classOf[Array[String]], - classOf[java.util.List[File]]) - ) + private[this] val fCls = classOf[File] + private[this] val bCls = classOf[Boolean] + private[this] val asCls = classOf[Array[String]] + private[this] val lfCls = classOf[java.util.List[File]] + private[this] val iCls = classOf[Integer] + + def scriptedRunTask: Initialize[Task[Method]] = Def.taskDyn { + val clazz = scriptedTests.value.getClass + if (scriptedBatchExecution.value) + Def.task(clazz.getMethod("runInParallel", fCls, bCls, asCls, fCls, asCls, lfCls, iCls)) + else Def.task(clazz.getMethod("run", fCls, bCls, asCls, fCls, asCls, lfCls)) + } import DefaultParsers._ case class ScriptedTestPage(page: Int, total: Int) @@ -134,15 +144,18 @@ object ScriptedPlugin extends AutoPlugin { val args = scriptedParser(sbtTestDirectory.value).parsed scriptedDependencies.value try { - scriptedRun.value.invoke( - scriptedTests.value, - sbtTestDirectory.value, - scriptedBufferLog.value: java.lang.Boolean, - args.toArray, - sbtLauncher.value, - scriptedLaunchOpts.value.toArray, - new java.util.ArrayList() - ) + val method = scriptedRun.value + val scriptedInstance = scriptedTests.value + val dir = sbtTestDirectory.value + val log: java.lang.Boolean = scriptedBufferLog.value + val launcher = sbtLauncher.value + val opts = scriptedLaunchOpts.value.toArray + val empty = new java.util.ArrayList() + val instances: Integer = scriptedParallelInstances.value + + if (scriptedBatchExecution.value) + method.invoke(scriptedInstance, dir, log, args.toArray, launcher, opts, empty, instances) + else method.invoke(scriptedInstance, dir, log, args.toArray, launcher, opts, empty) } catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause } } diff --git a/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala index 3e2798300..7af336a26 100644 --- a/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala +++ b/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala @@ -324,8 +324,8 @@ class ScriptedRunner { prescripted.add(f); () }) //new FullLogger(Logger.xlog2Log(log))) } - // This is called by sbt-scripted 0.13.x (the sbt host) when cross-compiling to sbt 0.13.x and 1.0.x - // See https://github.com/sbt/sbt/issues/3245 + + // This is called by sbt-scripted 0.13.x and 1.x (see https://github.com/sbt/sbt/issues/3245) def run(resourceBaseDirectory: File, bufferLog: Boolean, tests: Array[String], @@ -374,6 +374,21 @@ class ScriptedRunner { 1) } + // This is used by sbt-scripted sbt 1.x + def runInParallel( + baseDir: File, + bufferLog: Boolean, + tests: Array[String], + bootProps: File, + launchOpts: Array[String], + prescripted: java.util.List[File], + instances: Integer + ): Unit = { + val logger = ConsoleLogger() + val addTestFile = (f: File) => { prescripted.add(f); () } + runInParallel(baseDir, bufferLog, tests, logger, bootProps, launchOpts, addTestFile, instances) + } + def runInParallel( resourceBaseDirectory: File, bufferLog: Boolean, From 2aed011bc990d53c17aafb0f4b9ac04532b2b54a Mon Sep 17 00:00:00 2001 From: Allan Renucci Date: Wed, 17 Jan 2018 14:54:21 +0100 Subject: [PATCH 074/356] Cleanup --- .../src/main/scala/sbt/ScriptedPlugin.scala | 26 +++++++++++-------- .../main/scala/sbt/test/ScriptedTests.scala | 2 +- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala b/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala index a7bfdc907..a7bf73543 100644 --- a/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala +++ b/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala @@ -80,17 +80,21 @@ object ScriptedPlugin extends AutoPlugin { ModuleUtilities.getObject("sbt.test.ScriptedTests", loader) } - private[this] val fCls = classOf[File] - private[this] val bCls = classOf[Boolean] - private[this] val asCls = classOf[Array[String]] - private[this] val lfCls = classOf[java.util.List[File]] - private[this] val iCls = classOf[Integer] - def scriptedRunTask: Initialize[Task[Method]] = Def.taskDyn { + val fCls = classOf[File] + val bCls = classOf[Boolean] + val asCls = classOf[Array[String]] + val lfCls = classOf[java.util.List[File]] + val iCls = classOf[Int] + val clazz = scriptedTests.value.getClass - if (scriptedBatchExecution.value) - Def.task(clazz.getMethod("runInParallel", fCls, bCls, asCls, fCls, asCls, lfCls, iCls)) - else Def.task(clazz.getMethod("run", fCls, bCls, asCls, fCls, asCls, lfCls)) + val method = + if (scriptedBatchExecution.value) + clazz.getMethod("runInParallel", fCls, bCls, asCls, fCls, asCls, lfCls, iCls) + else + clazz.getMethod("run", fCls, bCls, asCls, fCls, asCls, lfCls) + + Def.task(method) } import DefaultParsers._ @@ -150,8 +154,8 @@ object ScriptedPlugin extends AutoPlugin { val log: java.lang.Boolean = scriptedBufferLog.value val launcher = sbtLauncher.value val opts = scriptedLaunchOpts.value.toArray - val empty = new java.util.ArrayList() - val instances: Integer = scriptedParallelInstances.value + val empty = new java.util.ArrayList[File]() + val instances: java.lang.Integer = scriptedParallelInstances.value if (scriptedBatchExecution.value) method.invoke(scriptedInstance, dir, log, args.toArray, launcher, opts, empty, instances) diff --git a/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala index 7af336a26..f516d0beb 100644 --- a/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala +++ b/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala @@ -382,7 +382,7 @@ class ScriptedRunner { bootProps: File, launchOpts: Array[String], prescripted: java.util.List[File], - instances: Integer + instances: Int ): Unit = { val logger = ConsoleLogger() val addTestFile = (f: File) => { prescripted.add(f); () } From 655c2ac5d159b5e510b2a0f2cc41868befb1d531 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 13 Jan 2018 00:35:11 -0500 Subject: [PATCH 075/356] Make ScriptedPlugin not a triggered plugin Fixes #3514 --- scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala b/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala index a7bf73543..9ba17d09c 100644 --- a/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala +++ b/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala @@ -16,7 +16,7 @@ import java.lang.reflect.Method object ScriptedPlugin extends AutoPlugin { override def requires = plugins.JvmPlugin - override def trigger = allRequirements + object autoImport { val ScriptedConf = Configurations.config("scripted-sbt") hide val ScriptedLaunchConf = Configurations.config("scripted-sbt-launch") hide From 08eaba9107bdeb47a4273fef882702d1257de3ac Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 13 Jan 2018 14:28:06 -0500 Subject: [PATCH 076/356] Add SbtPlugin Fixes #3538 This brings in `sbt.ScriptedPlugin` as `sbt.plugins.ScriptedPlugin` into sbt mothership. In addition, `sbt.plugins.SbtPlugin` is added that enables the scripted plugin and `sbtPlugin := true`. This allows plugin authors to bring in scripted plugin by writing: ```scala lazy val root = (project in file(".")) .enablePlugins(SbtPlugin) ``` --- build.sbt | 11 +++++++++-- .../scala/sbt/internal/PluginDiscovery.scala | 2 ++ .../main/scala/sbt/plugins/SbtPlugin.scala | 19 +++++++++++++++++++ .../scala/sbt/plugins}/ScriptedPlugin.scala | 14 +++++++++++--- .../project/scripted-plugin/build.sbt | 2 ++ 5 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 main/src/main/scala/sbt/plugins/SbtPlugin.scala rename {scripted/plugin/src/main/scala/sbt => main/src/main/scala/sbt/plugins}/ScriptedPlugin.scala (96%) create mode 100644 sbt/src/sbt-test/project/scripted-plugin/build.sbt diff --git a/build.sbt b/build.sbt index b9d11c0d6..4473012ac 100644 --- a/build.sbt +++ b/build.sbt @@ -272,11 +272,18 @@ lazy val scriptedSbtProj = (project in scriptedPath / "sbt") .configure(addSbtIO, addSbtUtilLogging, addSbtCompilerInterface, addSbtUtilScripted, addSbtLmCore) lazy val scriptedPluginProj = (project in scriptedPath / "plugin") - .dependsOn(sbtProj) + .dependsOn(mainProj) .settings( baseSettings, name := "Scripted Plugin", mimaSettings, + mimaBinaryIssueFilters ++= Seq( + // scripted plugin has moved into sbt mothership as sbt.plugins.ScriptedPlugin. + // sbt.ScriptedPlugin is still here for bincomat. + exclude[DirectMissingMethodProblem]("sbt.ScriptedPlugin#autoImport*"), + exclude[IncompatibleResultTypeProblem]("sbt.ScriptedPlugin.requires"), + exclude[DirectMissingMethodProblem]("sbt.ScriptedPlugin.scriptedParser"), + ), ) .configure(addSbtCompilerClasspath) @@ -417,7 +424,7 @@ lazy val mainSettingsProj = (project in file("main-settings")) // The main integration project for sbt. It brings all of the projects together, configures them, and provides for overriding conventions. lazy val mainProj = (project in file("main")) .enablePlugins(ContrabandPlugin) - .dependsOn(logicProj, actionsProj, mainSettingsProj, runProj, commandProj, collectionProj) + .dependsOn(logicProj, actionsProj, mainSettingsProj, runProj, commandProj, collectionProj, scriptedSbtProj) .settings( testedBaseSettings, name := "Main", diff --git a/main/src/main/scala/sbt/internal/PluginDiscovery.scala b/main/src/main/scala/sbt/internal/PluginDiscovery.scala index af2097298..50e60663b 100644 --- a/main/src/main/scala/sbt/internal/PluginDiscovery.scala +++ b/main/src/main/scala/sbt/internal/PluginDiscovery.scala @@ -47,6 +47,8 @@ object PluginDiscovery { "sbt.plugins.IvyPlugin" -> sbt.plugins.IvyPlugin, "sbt.plugins.JvmPlugin" -> sbt.plugins.JvmPlugin, "sbt.plugins.CorePlugin" -> sbt.plugins.CorePlugin, + "sbt.plugins.ScriptedPlugin" -> sbt.plugins.ScriptedPlugin, + "sbt.plugins.SbtPlugin" -> sbt.plugins.SbtPlugin, "sbt.plugins.JUnitXmlReportPlugin" -> sbt.plugins.JUnitXmlReportPlugin, "sbt.plugins.Giter8TemplatePlugin" -> sbt.plugins.Giter8TemplatePlugin ) diff --git a/main/src/main/scala/sbt/plugins/SbtPlugin.scala b/main/src/main/scala/sbt/plugins/SbtPlugin.scala new file mode 100644 index 000000000..a8a52413c --- /dev/null +++ b/main/src/main/scala/sbt/plugins/SbtPlugin.scala @@ -0,0 +1,19 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt +package plugins + +import Keys._ + +object SbtPlugin extends AutoPlugin { + override def requires = ScriptedPlugin + + override lazy val projectSettings = Seq( + sbtPlugin := true + ) +} diff --git a/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala b/main/src/main/scala/sbt/plugins/ScriptedPlugin.scala similarity index 96% rename from scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala rename to main/src/main/scala/sbt/plugins/ScriptedPlugin.scala index 9ba17d09c..cf5355ec7 100644 --- a/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala +++ b/main/src/main/scala/sbt/plugins/ScriptedPlugin.scala @@ -6,13 +6,21 @@ */ package sbt +package plugins +import java.io.File import Def.Initialize import Keys._ import sbt.internal.util.complete.{ Parser, DefaultParsers } import sbt.internal.inc.classpath.ClasspathUtilities import sbt.internal.inc.ModuleUtilities import java.lang.reflect.Method +import sbt.librarymanagement._ +import sbt.librarymanagement.syntax._ +import sbt.io._ +import sbt.io.syntax._ +import Project._ +import Def._ object ScriptedPlugin extends AutoPlugin { override def requires = plugins.JvmPlugin @@ -38,6 +46,7 @@ object ScriptedPlugin extends AutoPlugin { val scriptedDependencies = TaskKey[Unit]("scripted-dependencies") val scripted = InputKey[Unit]("scripted") } + import autoImport._ override lazy val projectSettings = Seq( ivyConfigurations ++= Seq(ScriptedConf, ScriptedLaunchConf), @@ -66,7 +75,7 @@ object ScriptedPlugin extends AutoPlugin { scriptedRun := scriptedRunTask.value, scriptedDependencies := { def use[A](@deprecated("unused", "") x: A*): Unit = () // avoid unused warnings - val analysis = (compile in Test).value + val analysis = (Keys.compile in Test).value val pub = (publishLocal).value use(analysis, pub) }, @@ -143,7 +152,6 @@ object ScriptedPlugin extends AutoPlugin { //(token(Space) ~> matched(testID)).* (token(Space) ~> (PagedIds | testIdAsGroup)).* map (_.flatten) } - def scriptedTask: Initialize[InputTask[Unit]] = Def.inputTask { val args = scriptedParser(sbtTestDirectory.value).parsed scriptedDependencies.value @@ -164,6 +172,6 @@ object ScriptedPlugin extends AutoPlugin { } private[this] def getJars(config: Configuration): Initialize[Task[PathFinder]] = Def.task { - PathFinder(Classpaths.managedJars(config, classpathTypes.value, update.value).map(_.data)) + PathFinder(Classpaths.managedJars(config, classpathTypes.value, Keys.update.value).map(_.data)) } } diff --git a/sbt/src/sbt-test/project/scripted-plugin/build.sbt b/sbt/src/sbt-test/project/scripted-plugin/build.sbt new file mode 100644 index 000000000..6c91c26b3 --- /dev/null +++ b/sbt/src/sbt-test/project/scripted-plugin/build.sbt @@ -0,0 +1,2 @@ +lazy val root = (project in file(".")) + .enablePlugins(SbtPlugin) From b05802f63b67bc9df04ab48046973b3771980a16 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 13 Jan 2018 02:47:53 -0500 Subject: [PATCH 077/356] move some scripted defaults settings to global Fixes #3656 --- .../main/scala/sbt/plugins/ScriptedPlugin.scala | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/main/src/main/scala/sbt/plugins/ScriptedPlugin.scala b/main/src/main/scala/sbt/plugins/ScriptedPlugin.scala index cf5355ec7..97f332986 100644 --- a/main/src/main/scala/sbt/plugins/ScriptedPlugin.scala +++ b/main/src/main/scala/sbt/plugins/ScriptedPlugin.scala @@ -48,6 +48,12 @@ object ScriptedPlugin extends AutoPlugin { } import autoImport._ + + override lazy val globalSettings = Seq( + scriptedBufferLog := true, + scriptedLaunchOpts := Seq(), + ) + override lazy val projectSettings = Seq( ivyConfigurations ++= Seq(ScriptedConf, ScriptedLaunchConf), scriptedSbt := (sbtVersion in pluginCrossBuild).value, @@ -67,7 +73,6 @@ object ScriptedPlugin extends AutoPlugin { case Some((x, y)) => sys error s"Unknown sbt version ${scriptedSbt.value} ($x.$y)" case None => sys error s"Unknown sbt version ${scriptedSbt.value}" }), - scriptedBufferLog := true, scriptedClasspath := getJars(ScriptedConf).value, scriptedTests := scriptedTestsTask.value, scriptedParallelInstances := 1, @@ -79,17 +84,16 @@ object ScriptedPlugin extends AutoPlugin { val pub = (publishLocal).value use(analysis, pub) }, - scriptedLaunchOpts := Seq(), scripted := scriptedTask.evaluated ) - def scriptedTestsTask: Initialize[Task[AnyRef]] = + private[sbt] def scriptedTestsTask: Initialize[Task[AnyRef]] = Def.task { val loader = ClasspathUtilities.toLoader(scriptedClasspath.value, scalaInstance.value.loader) ModuleUtilities.getObject("sbt.test.ScriptedTests", loader) } - def scriptedRunTask: Initialize[Task[Method]] = Def.taskDyn { + private[sbt] def scriptedRunTask: Initialize[Task[Method]] = Def.taskDyn { val fCls = classOf[File] val bCls = classOf[Boolean] val asCls = classOf[Array[String]] @@ -107,7 +111,7 @@ object ScriptedPlugin extends AutoPlugin { } import DefaultParsers._ - case class ScriptedTestPage(page: Int, total: Int) + private[sbt] case class ScriptedTestPage(page: Int, total: Int) private[sbt] def scriptedParser(scriptedBase: File): Parser[Seq[String]] = { @@ -152,7 +156,8 @@ object ScriptedPlugin extends AutoPlugin { //(token(Space) ~> matched(testID)).* (token(Space) ~> (PagedIds | testIdAsGroup)).* map (_.flatten) } - def scriptedTask: Initialize[InputTask[Unit]] = Def.inputTask { + + private[sbt] def scriptedTask: Initialize[InputTask[Unit]] = Def.inputTask { val args = scriptedParser(sbtTestDirectory.value).parsed scriptedDependencies.value try { From c20029ce160cab04e8fea1adba4fd0e321131204 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 13 Jan 2018 17:08:48 -0500 Subject: [PATCH 078/356] Work around package name confusion This works around the name conflict between sbt.test package and sbt.Keys.test. 1. sbt.test package is renamed to sbt.scriptedtest. This allows 1.0 plugins and builds to use `test` to mean `Keys.test`. 2. To keep binary compatibility for sbt 0.13 scripted, I am adding `sbt.test.ScriptedRunner` and `sbt.test.ScriptedTests` in `scripted-plugin` artifact. 3. Another affected user is Giter8 plugin that uses ScriptedPlugin. Since the intereactions are limited to `sbt.ScriptedPlugin.*`, we should be fine here. - https://github.com/foundweekends/giter8/blob/v0.11.0-M2/plugin/src/main/scala-sbt-1.0/giter8/SBTCompat.scala --- build.sbt | 11 ++++--- .../sbt/{plugins => }/ScriptedPlugin.scala | 8 +++-- .../scala/sbt/internal/PluginDiscovery.scala | 2 +- project/Scripted.scala | 2 +- .../src/main/resources/sbt/sbt.autoplugins | 1 - .../src/main/scala/sbt/ScriptedPlugin.scala | 10 ++++++ .../main/scala/sbt/test/ScriptedTests.scala | 32 +++++++++++++++++++ .../BatchScriptRunner.scala | 4 +-- .../{test => scriptedtest}/SbtHandler.scala | 2 +- .../ScriptedTests.scala | 10 +++--- 10 files changed, 64 insertions(+), 18 deletions(-) rename main/src/main/scala/sbt/{plugins => }/ScriptedPlugin.scala (96%) delete mode 100644 scripted/plugin/src/main/resources/sbt/sbt.autoplugins create mode 100644 scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala create mode 100644 scripted/plugin/src/main/scala/sbt/test/ScriptedTests.scala rename scripted/sbt/src/main/scala/sbt/{test => scriptedtest}/BatchScriptRunner.scala (96%) rename scripted/sbt/src/main/scala/sbt/{test => scriptedtest}/SbtHandler.scala (99%) rename scripted/sbt/src/main/scala/sbt/{test => scriptedtest}/ScriptedTests.scala (98%) diff --git a/build.sbt b/build.sbt index 4473012ac..092830ad7 100644 --- a/build.sbt +++ b/build.sbt @@ -268,6 +268,10 @@ lazy val scriptedSbtProj = (project in scriptedPath / "sbt") name := "Scripted sbt", libraryDependencies ++= Seq(launcherInterface % "provided"), mimaSettings, + mimaBinaryIssueFilters ++= Seq( + // sbt.test package is renamed to sbt.scriptedtest. + exclude[MissingClassProblem]("sbt.test.*"), + ), ) .configure(addSbtIO, addSbtUtilLogging, addSbtCompilerInterface, addSbtUtilScripted, addSbtLmCore) @@ -278,11 +282,8 @@ lazy val scriptedPluginProj = (project in scriptedPath / "plugin") name := "Scripted Plugin", mimaSettings, mimaBinaryIssueFilters ++= Seq( - // scripted plugin has moved into sbt mothership as sbt.plugins.ScriptedPlugin. - // sbt.ScriptedPlugin is still here for bincomat. - exclude[DirectMissingMethodProblem]("sbt.ScriptedPlugin#autoImport*"), - exclude[IncompatibleResultTypeProblem]("sbt.ScriptedPlugin.requires"), - exclude[DirectMissingMethodProblem]("sbt.ScriptedPlugin.scriptedParser"), + // scripted plugin has moved into sbt mothership. + exclude[MissingClassProblem]("sbt.ScriptedPlugin*") ), ) .configure(addSbtCompilerClasspath) diff --git a/main/src/main/scala/sbt/plugins/ScriptedPlugin.scala b/main/src/main/scala/sbt/ScriptedPlugin.scala similarity index 96% rename from main/src/main/scala/sbt/plugins/ScriptedPlugin.scala rename to main/src/main/scala/sbt/ScriptedPlugin.scala index 97f332986..538a851ef 100644 --- a/main/src/main/scala/sbt/plugins/ScriptedPlugin.scala +++ b/main/src/main/scala/sbt/ScriptedPlugin.scala @@ -6,7 +6,6 @@ */ package sbt -package plugins import java.io.File import Def.Initialize @@ -90,7 +89,12 @@ object ScriptedPlugin extends AutoPlugin { private[sbt] def scriptedTestsTask: Initialize[Task[AnyRef]] = Def.task { val loader = ClasspathUtilities.toLoader(scriptedClasspath.value, scalaInstance.value.loader) - ModuleUtilities.getObject("sbt.test.ScriptedTests", loader) + try { + ModuleUtilities.getObject("sbt.scriptedtest.ScriptedTests", loader) + } catch { + case _: ClassNotFoundException => + ModuleUtilities.getObject("sbt.test.ScriptedTests", loader) + } } private[sbt] def scriptedRunTask: Initialize[Task[Method]] = Def.taskDyn { diff --git a/main/src/main/scala/sbt/internal/PluginDiscovery.scala b/main/src/main/scala/sbt/internal/PluginDiscovery.scala index 50e60663b..cc99453e3 100644 --- a/main/src/main/scala/sbt/internal/PluginDiscovery.scala +++ b/main/src/main/scala/sbt/internal/PluginDiscovery.scala @@ -47,7 +47,7 @@ object PluginDiscovery { "sbt.plugins.IvyPlugin" -> sbt.plugins.IvyPlugin, "sbt.plugins.JvmPlugin" -> sbt.plugins.JvmPlugin, "sbt.plugins.CorePlugin" -> sbt.plugins.CorePlugin, - "sbt.plugins.ScriptedPlugin" -> sbt.plugins.ScriptedPlugin, + "sbt.ScriptedPlugin" -> sbt.ScriptedPlugin, "sbt.plugins.SbtPlugin" -> sbt.plugins.SbtPlugin, "sbt.plugins.JUnitXmlReportPlugin" -> sbt.plugins.JUnitXmlReportPlugin, "sbt.plugins.Giter8TemplatePlugin" -> sbt.plugins.Giter8TemplatePlugin diff --git a/project/Scripted.scala b/project/Scripted.scala index 788f1d60e..0723d4d84 100644 --- a/project/Scripted.scala +++ b/project/Scripted.scala @@ -115,7 +115,7 @@ object Scripted { sys.props(org.apache.logging.log4j.util.LoaderUtil.IGNORE_TCCL_PROPERTY) = "true" val noJLine = new classpath.FilteredLoader(scriptedSbtInstance.loader, "jline." :: Nil) val loader = classpath.ClasspathUtilities.toLoader(scriptedSbtClasspath.files, noJLine) - val bridgeClass = Class.forName("sbt.test.ScriptedRunner", true, loader) + val bridgeClass = Class.forName("sbt.scriptedtest.ScriptedRunner", true, loader) val bridge = bridgeClass.getDeclaredConstructor().newInstance().asInstanceOf[SbtScriptedRunner] try { // Using java.util.List to encode File => Unit. diff --git a/scripted/plugin/src/main/resources/sbt/sbt.autoplugins b/scripted/plugin/src/main/resources/sbt/sbt.autoplugins deleted file mode 100644 index 0077b7635..000000000 --- a/scripted/plugin/src/main/resources/sbt/sbt.autoplugins +++ /dev/null @@ -1 +0,0 @@ -sbt.ScriptedPlugin \ No newline at end of file diff --git a/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala b/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala new file mode 100644 index 000000000..93c2d93f4 --- /dev/null +++ b/scripted/plugin/src/main/scala/sbt/ScriptedPlugin.scala @@ -0,0 +1,10 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt + +// ScriptedPlugin has moved to main. diff --git a/scripted/plugin/src/main/scala/sbt/test/ScriptedTests.scala b/scripted/plugin/src/main/scala/sbt/test/ScriptedTests.scala new file mode 100644 index 000000000..727c4bd6c --- /dev/null +++ b/scripted/plugin/src/main/scala/sbt/test/ScriptedTests.scala @@ -0,0 +1,32 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt.test + +import java.io.File + +/** + * This is a bincompat place holder sbt.test package that we are now trying to hide + * because of the name conflict with Keys.test. + */ +@deprecated("Use sbt.scriptedtest.ScriptedRunner.", "1.2.0") +private[sbt] class ScriptedRunner extends sbt.scriptedtest.ScriptedRunner + +/** + * This is a bincompat place holder for sbt.test package that we are now trying to hide + * because of the name conflict with Keys.test. + */ +@deprecated("Use sbt.scriptedtest.ScriptedTests.", "1.2.0") +private[sbt] object ScriptedTests extends ScriptedRunner { + + /** Represents the function that runs the scripted tests, both in single or batch mode. */ + type TestRunner = () => Seq[Option[String]] + + val emptyCallback: File => Unit = _ => () + def main(args: Array[String]): Unit = + sbt.scriptedtest.ScriptedTests.main(args) +} diff --git a/scripted/sbt/src/main/scala/sbt/test/BatchScriptRunner.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala similarity index 96% rename from scripted/sbt/src/main/scala/sbt/test/BatchScriptRunner.scala rename to scripted/sbt/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala index ccf9d5148..fe9d35712 100644 --- a/scripted/sbt/src/main/scala/sbt/test/BatchScriptRunner.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala @@ -6,10 +6,10 @@ */ package sbt -package test +package scriptedtest import sbt.internal.scripted._ -import sbt.test.BatchScriptRunner.States +import sbt.scriptedtest.BatchScriptRunner.States /** Defines an alternative script runner that allows batch execution. */ private[sbt] class BatchScriptRunner extends ScriptRunner { diff --git a/scripted/sbt/src/main/scala/sbt/test/SbtHandler.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/SbtHandler.scala similarity index 99% rename from scripted/sbt/src/main/scala/sbt/test/SbtHandler.scala rename to scripted/sbt/src/main/scala/sbt/scriptedtest/SbtHandler.scala index 12598df78..3ddecbab8 100644 --- a/scripted/sbt/src/main/scala/sbt/test/SbtHandler.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/SbtHandler.scala @@ -6,7 +6,7 @@ */ package sbt -package test +package scriptedtest import java.io.{ File, IOException } import xsbt.IPC diff --git a/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala similarity index 98% rename from scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala rename to scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index f516d0beb..4d552994c 100644 --- a/scripted/sbt/src/main/scala/sbt/test/ScriptedTests.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -6,7 +6,7 @@ */ package sbt -package test +package scriptedtest import java.io.File import java.util.Properties @@ -466,13 +466,13 @@ class ScriptedRunner { final case class ScriptedTest(group: String, name: String) { override def toString = group + "/" + name } -private[test] object ListTests { +private[sbt] object ListTests { def list(directory: File, filter: java.io.FileFilter) = wrapNull(directory.listFiles(filter)) } import ListTests._ -private[test] final class ListTests(baseDirectory: File, - accept: ScriptedTest => Boolean, - log: Logger) { +private[sbt] final class ListTests(baseDirectory: File, + accept: ScriptedTest => Boolean, + log: Logger) { def filter = DirectoryFilter -- HiddenFileFilter def listTests: Seq[ScriptedTest] = { list(baseDirectory, filter) flatMap { group => From 0d83b2fc3f38ccc3e4071a132aa27d8fbecf0418 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 17 Jan 2018 14:58:46 -0500 Subject: [PATCH 079/356] notes --- notes/1.2.0/scripted-change.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 notes/1.2.0/scripted-change.md diff --git a/notes/1.2.0/scripted-change.md b/notes/1.2.0/scripted-change.md new file mode 100644 index 000000000..63715ecab --- /dev/null +++ b/notes/1.2.0/scripted-change.md @@ -0,0 +1,27 @@ + +### Fixes with compatibility implications + +- In sbt 1.2, `ScriptedPlugin` is no longer triggered automatically. This allows easier use of the plugin in a multi-project build. We recommend migration to `SbtPlugin`. [#3514][3514]/[#3875][3875] by [@eed3si9n][@eed3si9n] +- `scriptedBufferLog` and `scriptedLaunchOpts` settings are changed so they are scoped globally. + +### Features + +- Adds `SbtPlugin`. See below. + +### Bug fixes + + +### SbtPlugin + +`SbtPlugin` is a new plugin that represents sbt plugin projects. + + lazy val fooPlugin = (project in file("plugin")) + .enablePlugins(SbtPlugin) + +This sets `sbtPlugin` setting to `true`, and brings in the new non-triggered `ScriptedPlugin`. + +[#3875][3875] by [@eed3si9n][@eed3si9n] + + [@eed3si9n]: https://github.com/eed3si9n + [3514]: https://github.com/sbt/sbt/issues/3514 + [3875]: https://github.com/sbt/sbt/pull/3875 From 58ee24b427dea3b53429d4c19f3cc377a2e04915 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 22 Jan 2018 13:45:58 -0500 Subject: [PATCH 080/356] Migrate to the new way of enabling scripted --- sbt/src/sbt-test/project/scripted-skip-incompatible/build.sbt | 2 ++ .../project/scripted-skip-incompatible/project/plugins.sbt | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 sbt/src/sbt-test/project/scripted-skip-incompatible/build.sbt delete mode 100644 sbt/src/sbt-test/project/scripted-skip-incompatible/project/plugins.sbt diff --git a/sbt/src/sbt-test/project/scripted-skip-incompatible/build.sbt b/sbt/src/sbt-test/project/scripted-skip-incompatible/build.sbt new file mode 100644 index 000000000..6c91c26b3 --- /dev/null +++ b/sbt/src/sbt-test/project/scripted-skip-incompatible/build.sbt @@ -0,0 +1,2 @@ +lazy val root = (project in file(".")) + .enablePlugins(SbtPlugin) diff --git a/sbt/src/sbt-test/project/scripted-skip-incompatible/project/plugins.sbt b/sbt/src/sbt-test/project/scripted-skip-incompatible/project/plugins.sbt deleted file mode 100644 index 529e7d656..000000000 --- a/sbt/src/sbt-test/project/scripted-skip-incompatible/project/plugins.sbt +++ /dev/null @@ -1,3 +0,0 @@ -libraryDependencies += { - "org.scala-sbt" %% "scripted-plugin" % sbtVersion.value -} From 92ed849eaa0c57f5e6bcc2a54a2a080ac44441be Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 22 Dec 2017 10:29:11 +0000 Subject: [PATCH 081/356] Remove import-excluding Predef.conforms As of Scala 2.11 Predef.conforms isn't implicit anymore (and it's deprecated), so import-excluding doesn't avoid the implicit (the implicit version is now Predef.$conforms). Also in Scala 2.13 Predef.conforms has been removed. --- main-actions/src/main/scala/sbt/Package.scala | 1 - main-actions/src/main/scala/sbt/RawCompileLike.scala | 1 - 2 files changed, 2 deletions(-) diff --git a/main-actions/src/main/scala/sbt/Package.scala b/main-actions/src/main/scala/sbt/Package.scala index 080667e6a..82ab1072f 100644 --- a/main-actions/src/main/scala/sbt/Package.scala +++ b/main-actions/src/main/scala/sbt/Package.scala @@ -7,7 +7,6 @@ package sbt -import scala.Predef.{ conforms => _, _ } import java.io.File import java.util.jar.{ Attributes, Manifest } import scala.collection.JavaConverters._ diff --git a/main-actions/src/main/scala/sbt/RawCompileLike.scala b/main-actions/src/main/scala/sbt/RawCompileLike.scala index f8b85e25c..56fc1b551 100644 --- a/main-actions/src/main/scala/sbt/RawCompileLike.scala +++ b/main-actions/src/main/scala/sbt/RawCompileLike.scala @@ -11,7 +11,6 @@ import scala.annotation.tailrec import java.io.File import sbt.internal.inc.{ RawCompiler, ScalaInstance } -import Predef.{ conforms => _, _ } import sbt.io.syntax._ import sbt.io.IO From 5daf10d6c765580c7c859f5409730b908840825d Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 22 Dec 2017 10:54:56 +0000 Subject: [PATCH 082/356] Tweak the description of KList --- .../src/main/scala/sbt/internal/util/KList.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/KList.scala b/internal/util-collection/src/main/scala/sbt/internal/util/KList.scala index 51babcae3..7b7aaf404 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/KList.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/KList.scala @@ -10,7 +10,7 @@ package sbt.internal.util import Types._ import Classes.Applicative -/** Heterogeneous list with each element having type M[T] for some type T.*/ +/** A higher-kinded heterogeneous list of elements that share the same type constructor `M[_]`. */ sealed trait KList[+M[_]] { type Transform[N[_]] <: KList[N] From 3e11b3f0009fb391ec716a078a89eadd5e84b21e Mon Sep 17 00:00:00 2001 From: Colin Dean Date: Wed, 24 Jan 2018 23:13:00 -0500 Subject: [PATCH 083/356] Fixes link to documentation for deprecated 0.10/0.12 DSL syntax Fixes sbt/website#558 --- main-settings/src/main/scala/sbt/Structure.scala | 2 +- main-settings/src/main/scala/sbt/std/TaskMacro.scala | 6 +++--- notes/1.2.0/fix-sbt012x-link.md | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 notes/1.2.0/fix-sbt012x-link.md diff --git a/main-settings/src/main/scala/sbt/Structure.scala b/main-settings/src/main/scala/sbt/Structure.scala index abe27d015..72cc84b63 100644 --- a/main-settings/src/main/scala/sbt/Structure.scala +++ b/main-settings/src/main/scala/sbt/Structure.scala @@ -570,7 +570,7 @@ object Scoped { /** The sbt 0.10 style DSL was deprecated in 0.13.13, favouring the use of the '.value' macro. * - * See http://www.scala-sbt.org/0.13/docs/Migrating-from-sbt-012x.html for how to migrate. + * See http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html#Migrating+from+sbt+0.12+style for how to migrate. */ trait TupleSyntax { import Scoped._ diff --git a/main-settings/src/main/scala/sbt/std/TaskMacro.scala b/main-settings/src/main/scala/sbt/std/TaskMacro.scala index 85e19c3f6..562a685f5 100644 --- a/main-settings/src/main/scala/sbt/std/TaskMacro.scala +++ b/main-settings/src/main/scala/sbt/std/TaskMacro.scala @@ -89,12 +89,12 @@ object TaskMacro { final val InputTaskCreateDynName = "createDyn" final val InputTaskCreateFreeName = "createFree" final val append1Migration = - "`<+=` operator is removed. Try `lhs += { x.value }`\n or see http://www.scala-sbt.org/1.0/docs/Migrating-from-sbt-012x.html." + "`<+=` operator is removed. Try `lhs += { x.value }`\n or see http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html#Migrating+from+sbt+0.12+style." final val appendNMigration = - "`<++=` operator is removed. Try `lhs ++= { x.value }`\n or see http://www.scala-sbt.org/1.0/docs/Migrating-from-sbt-012x.html." + "`<++=` operator is removed. Try `lhs ++= { x.value }`\n or see http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html#Migrating+from+sbt+0.12+style." final val assignMigration = """`<<=` operator is removed. Use `key := { x.value }` or `key ~= (old => { newValue })`. - |See http://www.scala-sbt.org/1.0/docs/Migrating-from-sbt-012x.html""".stripMargin + |See http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html#Migrating+from+sbt+0.12+style""".stripMargin import LinterDSL.{ Empty => EmptyLinter } diff --git a/notes/1.2.0/fix-sbt012x-link.md b/notes/1.2.0/fix-sbt012x-link.md new file mode 100644 index 000000000..3e368f8cb --- /dev/null +++ b/notes/1.2.0/fix-sbt012x-link.md @@ -0,0 +1,4 @@ +### Bug fixes + +* Fixes link to SBT upgrade migration page + From 286758e2ba42f924ff7e1790287435f00ae3c650 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 23 Jan 2018 16:52:51 +0000 Subject: [PATCH 084/356] Minor cleanups --- .../scala/sbt/internal/util/LineReader.scala | 2 +- .../scala/sbt/internal/CommandChannel.scala | 3 +- main/src/main/scala/sbt/BuildSyntax.scala | 2 +- main/src/main/scala/sbt/MainLoop.scala | 24 ++- .../scala/sbt/internal/CommandExchange.scala | 139 ++++++++---------- 5 files changed, 72 insertions(+), 98 deletions(-) diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala index b5e9d53d2..406ee5e97 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala @@ -132,7 +132,7 @@ private[sbt] object JLine { def createReader(): ConsoleReader = createReader(None, JLine.makeInputStream(true)) def createReader(historyPath: Option[File], in: InputStream): ConsoleReader = - usingTerminal { t => + usingTerminal { _ => val cr = new ConsoleReader(in, System.out) cr.setExpandEvents(false) // https://issues.scala-lang.org/browse/SI-7650 cr.setBellEnabled(false) diff --git a/main-command/src/main/scala/sbt/internal/CommandChannel.scala b/main-command/src/main/scala/sbt/internal/CommandChannel.scala index 4fbb9a299..6b1b8e391 100644 --- a/main-command/src/main/scala/sbt/internal/CommandChannel.scala +++ b/main-command/src/main/scala/sbt/internal/CommandChannel.scala @@ -19,8 +19,7 @@ import sjsonnew.JsonFormat */ abstract class CommandChannel { private val commandQueue: ConcurrentLinkedQueue[Exec] = new ConcurrentLinkedQueue() - def append(exec: Exec): Boolean = - commandQueue.add(exec) + def append(exec: Exec): Boolean = commandQueue.add(exec) def poll: Option[Exec] = Option(commandQueue.poll) def publishEvent[A: JsonFormat](event: A, execId: Option[String]): Unit diff --git a/main/src/main/scala/sbt/BuildSyntax.scala b/main/src/main/scala/sbt/BuildSyntax.scala index a2ef2c2cd..527481e87 100644 --- a/main/src/main/scala/sbt/BuildSyntax.scala +++ b/main/src/main/scala/sbt/BuildSyntax.scala @@ -11,7 +11,7 @@ import sbt.internal.DslEntry import sbt.librarymanagement.Configuration private[sbt] trait BuildSyntax { - import language.experimental.macros + import scala.language.experimental.macros def settingKey[T](description: String): SettingKey[T] = macro std.KeyMacro.settingKeyImpl[T] def taskKey[T](description: String): TaskKey[T] = macro std.KeyMacro.taskKeyImpl[T] def inputKey[T](description: String): InputKey[T] = macro std.KeyMacro.inputKeyImpl[T] diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index 110a78fa4..cbcbcb2cf 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -7,14 +7,17 @@ package sbt +import java.io.PrintWriter import java.util.Properties + +import jline.TerminalFactory + import scala.annotation.tailrec import scala.util.control.NonFatal -import jline.TerminalFactory import sbt.io.{ IO, Using } import sbt.internal.util.{ ErrorHandling, GlobalLogBacking } -import sbt.internal.util.complete.DefaultParsers +import sbt.internal.util.complete.Parser import sbt.util.Logger import sbt.protocol._ @@ -25,9 +28,7 @@ object MainLoop { // We've disabled jline shutdown hooks to prevent classloader leaks, and have been careful to always restore // the jline terminal in finally blocks, but hitting ctrl+c prevents finally blocks from being executed, in that // case the only way to restore the terminal is in a shutdown hook. - val shutdownHook = new Thread(new Runnable { - def run(): Unit = TerminalFactory.get().restore() - }) + val shutdownHook = new Thread(() => TerminalFactory.get().restore()) try { Runtime.getRuntime.addShutdownHook(shutdownHook) @@ -100,7 +101,7 @@ object MainLoop { /** Runs the next sequence of commands with global logging in place. */ def runWithNewLog(state: State, logBacking: GlobalLogBacking): RunNext = Using.fileWriter(append = true)(logBacking.file) { writer => - val out = new java.io.PrintWriter(writer) + val out = new PrintWriter(writer) val full = state.globalLogging.full val newLogging = state.globalLogging.newAppender(full, out, logBacking) // transferLevels(state, newLogging) @@ -124,7 +125,7 @@ object MainLoop { final class KeepGlobalLog(val state: State) extends RunNext final class Return(val result: xsbti.MainResult) extends RunNext - /** Runs the next sequence of commands that doesn't require global logging changes.*/ + /** Runs the next sequence of commands that doesn't require global logging changes. */ @tailrec def run(state: State): RunNext = state.next match { case State.Continue => run(next(state)) @@ -143,14 +144,11 @@ object MainLoop { /** This is the main function State transfer function of the sbt command processing. */ def processCommand(exec: Exec, state: State): State = { - import DefaultParsers._ val channelName = exec.source map (_.channelName) - StandardMain.exchange publishEventMessage ExecStatusEvent("Processing", - channelName, - exec.execId, - Vector()) + StandardMain.exchange publishEventMessage + ExecStatusEvent("Processing", channelName, exec.execId, Vector()) val parser = Command combine state.definedCommands - val newState = parse(exec.commandLine, parser(state)) match { + val newState = Parser.parse(exec.commandLine, parser(state)) match { case Right(s) => s() // apply command. command side effects happen here case Left(errMsg) => state.log error errMsg diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 2ce2819fa..2ece2e12a 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -46,46 +46,41 @@ private[sbt] final class CommandExchange { private val autoStartServer = sys.props get "sbt.server.autostart" forall (_.toLowerCase == "true") - private val lock = new AnyRef {} private var server: Option[ServerInstance] = None private val firstInstance: AtomicBoolean = new AtomicBoolean(true) private var consoleChannel: Option[ConsoleChannel] = None private val commandQueue: ConcurrentLinkedQueue[Exec] = new ConcurrentLinkedQueue() private val channelBuffer: ListBuffer[CommandChannel] = new ListBuffer() + private val channelBufferLock = new AnyRef {} private val nextChannelId: AtomicInteger = new AtomicInteger(0) private lazy val jsonFormat = new sjsonnew.BasicJsonProtocol with JValueFormats {} def channels: List[CommandChannel] = channelBuffer.toList - def subscribe(c: CommandChannel): Unit = - lock.synchronized { - channelBuffer.append(c) - } + def subscribe(c: CommandChannel): Unit = channelBufferLock.synchronized(channelBuffer.append(c)) // periodically move all messages from all the channels @tailrec def blockUntilNextExec: Exec = { @tailrec def slurpMessages(): Unit = - (((None: Option[Exec]) /: channels) { _ orElse _.poll }) match { + channels.foldLeft(Option.empty[Exec]) { _ orElse _.poll } match { + case None => () case Some(x) => commandQueue.add(x) slurpMessages - case _ => () } slurpMessages() Option(commandQueue.poll) match { case Some(x) => x - case _ => + case None => Thread.sleep(50) blockUntilNextExec } } def run(s: State): State = { - consoleChannel match { - case Some(_) => // do nothing - case _ => - val x = new ConsoleChannel("console0") - consoleChannel = Some(x) - subscribe(x) + if (consoleChannel.isEmpty) { + val console0 = new ConsoleChannel("console0") + consoleChannel = Some(console0) + subscribe(console0) } if (autoStartServer) runServer(s) else s @@ -97,25 +92,12 @@ private[sbt] final class CommandExchange { * Check if a server instance is running already, and start one if it isn't. */ private[sbt] def runServer(s: State): State = { - lazy val port = (s get serverPort) match { - case Some(x) => x - case None => 5001 - } - lazy val host = (s get serverHost) match { - case Some(x) => x - case None => "127.0.0.1" - } - lazy val auth: Set[ServerAuthentication] = (s get serverAuthentication) match { - case Some(xs) => xs - case None => Set(ServerAuthentication.Token) - } - lazy val connectionType = (s get serverConnectionType) match { - case Some(x) => x - case None => ConnectionType.Tcp - } - lazy val level: Level.Value = (s get serverLogLevel) - .orElse(s get logLevel) - .getOrElse(Level.Warn) + lazy val port = s.get(serverPort).getOrElse(5001) + lazy val host = s.get(serverHost).getOrElse("127.0.0.1") + lazy val auth: Set[ServerAuthentication] = + s.get(serverAuthentication).getOrElse(Set(ServerAuthentication.Token)) + lazy val connectionType = s.get(serverConnectionType).getOrElse(ConnectionType.Tcp) + lazy val level = s.get(serverLogLevel).orElse(s.get(logLevel)).getOrElse(Level.Warn) def onIncomingSocket(socket: Socket, instance: ServerInstance): Unit = { val name = newNetworkName @@ -131,55 +113,50 @@ private[sbt] final class CommandExchange { new NetworkChannel(name, socket, Project structure s, auth, instance, logger) subscribe(channel) } - server match { - case Some(_) => // do nothing - case None if !firstInstance.get => // there's another server - case _ => - val portfile = (new File(".")).getAbsoluteFile / "project" / "target" / "active.json" - val h = Hash.halfHashString(IO.toURI(portfile).toString) - val tokenfile = BuildPaths.getGlobalBase(s) / "server" / h / "token.json" - val socketfile = BuildPaths.getGlobalBase(s) / "server" / h / "sock" - val pipeName = "sbt-server-" + h - val connection = - ServerConnection(connectionType, - host, - port, - auth, - portfile, - tokenfile, - socketfile, - pipeName) - val x = Server.start(connection, onIncomingSocket, s.log) - - // don't throw exception when it times out - val d = "10s" - Try(Await.ready(x.ready, Duration(d))) - x.ready.value match { - case Some(Success(_)) => - // remember to shutdown only when the server comes up - server = Some(x) - case Some(Failure(_: AlreadyRunningException)) => - s.log.warn( - "sbt server could not start because there's another instance of sbt running on this build.") - s.log.warn("Running multiple instances is unsupported") - server = None - firstInstance.set(false) - case Some(Failure(e)) => - s.log.error(e.toString) - server = None - case None => - s.log.warn(s"sbt server could not start in $d") - server = None - firstInstance.set(false) - } + if (server.isEmpty && firstInstance.get) { + val portfile = (new File(".")).getAbsoluteFile / "project" / "target" / "active.json" + val h = Hash.halfHashString(IO.toURI(portfile).toString) + val tokenfile = BuildPaths.getGlobalBase(s) / "server" / h / "token.json" + val socketfile = BuildPaths.getGlobalBase(s) / "server" / h / "sock" + val pipeName = "sbt-server-" + h + val connection = ServerConnection( + connectionType, + host, + port, + auth, + portfile, + tokenfile, + socketfile, + pipeName, + ) + val serverInstance = Server.start(connection, onIncomingSocket, s.log) + // don't throw exception when it times out + val d = "10s" + Try(Await.ready(serverInstance.ready, Duration(d))) + serverInstance.ready.value match { + case Some(Success(())) => + // remember to shutdown only when the server comes up + server = Some(serverInstance) + case Some(Failure(_: AlreadyRunningException)) => + s.log.warn( + "sbt server could not start because there's another instance of sbt running on this build.") + s.log.warn("Running multiple instances is unsupported") + server = None + firstInstance.set(false) + case Some(Failure(e)) => + s.log.error(e.toString) + server = None + case None => + s.log.warn(s"sbt server could not start in $d") + server = None + firstInstance.set(false) + } } s } def shutdown(): Unit = { - channels foreach { c => - c.shutdown() - } + channels foreach (_.shutdown()) // interrupt and kill the thread server.foreach(_.shutdown()) server = None @@ -202,7 +179,7 @@ private[sbt] final class CommandExchange { toDel.toList match { case Nil => // do nothing case xs => - lock.synchronized { + channelBufferLock.synchronized { channelBuffer --= xs () } @@ -241,7 +218,7 @@ private[sbt] final class CommandExchange { toDel.toList match { case Nil => // do nothing case xs => - lock.synchronized { + channelBufferLock.synchronized { channelBuffer --= xs () } @@ -283,7 +260,7 @@ private[sbt] final class CommandExchange { toDel.toList match { case Nil => // do nothing case xs => - lock.synchronized { + channelBufferLock.synchronized { channelBuffer --= xs () } @@ -325,7 +302,7 @@ private[sbt] final class CommandExchange { toDel.toList match { case Nil => // do nothing case xs => - lock.synchronized { + channelBufferLock.synchronized { channelBuffer --= xs () } From 2f48425511d5b4729857b6931fdc40101b89a0d1 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 17 Jan 2018 15:17:56 +0000 Subject: [PATCH 085/356] Remove old zinc addSbtAlternateResolver stuff --- build.sbt | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/build.sbt b/build.sbt index b9d11c0d6..2c860b440 100644 --- a/build.sbt +++ b/build.sbt @@ -537,11 +537,6 @@ def scriptedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask { val result = scriptedSource(dir => (s: State) => Scripted.scriptedParser(dir)).parsed // publishLocalBinAll.value // TODO: Restore scripted needing only binary jars. publishAll.value - // These two projects need to be visible in a repo even if the default - // local repository is hidden, so we publish them to an alternate location and add - // that alternate repo to the running scripted test (in Scripted.scriptedpreScripted). - // (altLocalPublish in interfaceProj).value - // (altLocalPublish in compileInterfaceProj).value Scripted.doScripted( (sbtLaunchJar in bundledLauncherProj).value, (fullClasspath in scriptedSbtProj in Test).value, @@ -599,7 +594,6 @@ def otherRootSettings = scripted := scriptedTask.evaluated, scriptedUnpublished := scriptedUnpublishedTask.evaluated, scriptedSource := (sourceDirectory in sbtProj).value / "sbt-test", - // scriptedPrescripted := { addSbtAlternateResolver _ }, scriptedLaunchOpts := List("-Xmx1500M", "-Xms512M", "-server"), publishAll := { val _ = (publishLocal).all(ScopeFilter(inAnyProject)).value }, publishLocalBinAll := { val _ = (publishLocalBin).all(ScopeFilter(inAnyProject)).value }, @@ -619,23 +613,6 @@ def otherRootSettings = scriptedSource := (sourceDirectory in sbtProj).value / "repo-override-test" )) -// def addSbtAlternateResolver(scriptedRoot: File) = { -// val resolver = scriptedRoot / "project" / "AddResolverPlugin.scala" -// if (!resolver.exists) { -// IO.write(resolver, s"""import sbt._ -// |import Keys._ -// | -// |object AddResolverPlugin extends AutoPlugin { -// | override def requires = sbt.plugins.JvmPlugin -// | override def trigger = allRequirements -// | -// | override lazy val projectSettings = Seq(resolvers += alternativeLocalResolver) -// | lazy val alternativeLocalResolver = Resolver.file("$altLocalRepoName", file("$altLocalRepoPath"))(Resolver.ivyStylePatterns) -// |} -// |""".stripMargin) -// } -// } - lazy val docProjects: ScopeFilter = ScopeFilter( inAnyProject -- inProjects(sbtRoot, sbtProj, scriptedSbtProj, scriptedPluginProj), inConfigurations(Compile) From bee69ebb92906637b6ca9723004bc0ad044816c7 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 17 Jan 2018 15:19:21 +0000 Subject: [PATCH 086/356] Get rid of old MavenResolverPluginTest --- project/Scripted.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/project/Scripted.scala b/project/Scripted.scala index 788f1d60e..bd64b0cfb 100644 --- a/project/Scripted.scala +++ b/project/Scripted.scala @@ -40,8 +40,7 @@ object Scripted { // This is to workaround https://github.com/sbt/io/issues/110 sys.props.put("jna.nosys", "true") - lazy val MavenResolverPluginTest = config("mavenResolverPluginTest") extend Compile - lazy val RepoOverrideTest = config("repoOverrideTest") extend Compile + val RepoOverrideTest = config("repoOverrideTest") extend Compile import sbt.complete._ import DefaultParsers._ From c310ade9f8d389bcd82ae2159340170ade79ad77 Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Tue, 30 Jan 2018 23:59:37 +0100 Subject: [PATCH 087/356] Allow the full tests/fork-parallel to pass on less than four cores The test will be meaningless on less than four cores, but the scripted test will pass. On four or more cores, the test wil test testForkedParallel as expected. Closes #3545 --- sbt/src/sbt-test/tests/fork-parallel/build.sbt | 2 ++ sbt/src/sbt-test/tests/fork-parallel/test | 17 +++-------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/sbt/src/sbt-test/tests/fork-parallel/build.sbt b/sbt/src/sbt-test/tests/fork-parallel/build.sbt index e28535d49..0647f55e9 100644 --- a/sbt/src/sbt-test/tests/fork-parallel/build.sbt +++ b/sbt/src/sbt-test/tests/fork-parallel/build.sbt @@ -13,6 +13,8 @@ lazy val root = (project in file(".")). val log = streams.value.log if( nbProc < 4 ) { log.warn("With fewer than 4 processors this test is meaningless") + // mimic behavior expected by scripted + if (!testForkedParallel.value) sys.error("Exiting with error (note: test not performed)") } else { // we've got at least 4 processors, we'll check the upper end but also 3 and 4 as the upper might not // be reached if the system is under heavy load. diff --git a/sbt/src/sbt-test/tests/fork-parallel/test b/sbt/src/sbt-test/tests/fork-parallel/test index 662328f42..70e542b98 100644 --- a/sbt/src/sbt-test/tests/fork-parallel/test +++ b/sbt/src/sbt-test/tests/fork-parallel/test @@ -1,18 +1,7 @@ -# The tests/fork-parallel test will currently always -# report success when run on less than four cores, -# rather than failing in one of the two cases as expected. -# TODO: Adjust this scripted test so that it works as -# intended on less than four cores as well. - -# To debug, it is possible to limit the number of cores -# reported to sbt, and run the test, by using: -# taskset 0x00000003 sbt 'scripted tests/fork-parallel' -# See: https://github.com/sbt/sbt/issues/3545 - -# This bit won't currently work when using less than four cores. -# > test -# -> check +# Note: this test is meaningless on less than four cores +> test +-> check > clean > set testForkedParallel := true > test From 50f3dd2e20901ca4ee1d2e50ca02362a54884944 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 30 Jan 2018 00:43:30 -0500 Subject: [PATCH 088/356] Use ipcsocket --- build.sbt | 5 +- .../internal/NGUnixDomainServerSocket.java | 178 ---------------- .../java/sbt/internal/NGUnixDomainSocket.java | 192 ------------------ .../internal/NGUnixDomainSocketLibrary.java | 142 ------------- .../sbt/internal/NGWin32NamedPipeLibrary.java | 90 -------- .../NGWin32NamedPipeServerSocket.java | 173 ---------------- .../sbt/internal/NGWin32NamedPipeSocket.java | 172 ---------------- .../ReferenceCountedFileDescriptor.java | 82 -------- .../scala/sbt/internal/server/Server.scala | 7 +- project/Dependencies.scala | 1 + 10 files changed, 9 insertions(+), 1033 deletions(-) delete mode 100644 main-command/src/main/java/sbt/internal/NGUnixDomainServerSocket.java delete mode 100644 main-command/src/main/java/sbt/internal/NGUnixDomainSocket.java delete mode 100644 main-command/src/main/java/sbt/internal/NGUnixDomainSocketLibrary.java delete mode 100644 main-command/src/main/java/sbt/internal/NGWin32NamedPipeLibrary.java delete mode 100644 main-command/src/main/java/sbt/internal/NGWin32NamedPipeServerSocket.java delete mode 100644 main-command/src/main/java/sbt/internal/NGWin32NamedPipeSocket.java delete mode 100644 main-command/src/main/java/sbt/internal/ReferenceCountedFileDescriptor.java diff --git a/build.sbt b/build.sbt index d832e23f4..3eb98a312 100644 --- a/build.sbt +++ b/build.sbt @@ -323,7 +323,7 @@ lazy val protocolProj = (project in file("protocol")) scalacOptions -= "-Ywarn-unused", scalacOptions += "-Xlint:-unused", name := "Protocol", - libraryDependencies ++= Seq(sjsonNewScalaJson.value), + libraryDependencies ++= Seq(sjsonNewScalaJson.value, ipcSocket), managedSourceDirectories in Compile += baseDirectory.value / "src" / "main" / "contraband-scala", sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", @@ -355,6 +355,9 @@ lazy val commandProj = (project in file("main-command")) exclude[ReversedMissingMethodProblem]("sbt.internal.CommandChannel.*"), // Added an overload to reboot. The overload is private[sbt]. exclude[ReversedMissingMethodProblem]("sbt.StateOps.reboot"), + // Replace nailgun socket stuff + exclude[MissingClassProblem]("sbt.internal.NG*"), + exclude[MissingClassProblem]("sbt.internal.ReferenceCountedFileDescriptor"), ), unmanagedSources in (Compile, headerCreate) := { val old = (unmanagedSources in (Compile, headerCreate)).value diff --git a/main-command/src/main/java/sbt/internal/NGUnixDomainServerSocket.java b/main-command/src/main/java/sbt/internal/NGUnixDomainServerSocket.java deleted file mode 100644 index 89d3bcf43..000000000 --- a/main-command/src/main/java/sbt/internal/NGUnixDomainServerSocket.java +++ /dev/null @@ -1,178 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGUnixDomainServerSocket.java - -/* - - Copyright 2004-2015, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketAddress; -import java.util.concurrent.atomic.AtomicInteger; - -import com.sun.jna.LastErrorException; -import com.sun.jna.ptr.IntByReference; - -/** - * Implements a {@link ServerSocket} which binds to a local Unix domain socket - * and returns instances of {@link NGUnixDomainSocket} from - * {@link #accept()}. - */ -public class NGUnixDomainServerSocket extends ServerSocket { - private static final int DEFAULT_BACKLOG = 50; - - // We use an AtomicInteger to prevent a race in this situation which - // could happen if fd were just an int: - // - // Thread 1 -> NGUnixDomainServerSocket.accept() - // -> lock this - // -> check isBound and isClosed - // -> unlock this - // -> descheduled while still in method - // Thread 2 -> NGUnixDomainServerSocket.close() - // -> lock this - // -> check isClosed - // -> NGUnixDomainSocketLibrary.close(fd) - // -> now fd is invalid - // -> unlock this - // Thread 1 -> re-scheduled while still in method - // -> NGUnixDomainSocketLibrary.accept(fd, which is invalid and maybe re-used) - // - // By using an AtomicInteger, we'll set this to -1 after it's closed, which - // will cause the accept() call above to cleanly fail instead of possibly - // being called on an unrelated fd (which may or may not fail). - private final AtomicInteger fd; - - private final int backlog; - private boolean isBound; - private boolean isClosed; - - public static class NGUnixDomainServerSocketAddress extends SocketAddress { - private final String path; - - public NGUnixDomainServerSocketAddress(String path) { - this.path = path; - } - - public String getPath() { - return path; - } - } - - /** - * Constructs an unbound Unix domain server socket. - */ - public NGUnixDomainServerSocket() throws IOException { - this(DEFAULT_BACKLOG, null); - } - - /** - * Constructs an unbound Unix domain server socket with the specified listen backlog. - */ - public NGUnixDomainServerSocket(int backlog) throws IOException { - this(backlog, null); - } - - /** - * Constructs and binds a Unix domain server socket to the specified path. - */ - public NGUnixDomainServerSocket(String path) throws IOException { - this(DEFAULT_BACKLOG, path); - } - - /** - * Constructs and binds a Unix domain server socket to the specified path - * with the specified listen backlog. - */ - public NGUnixDomainServerSocket(int backlog, String path) throws IOException { - try { - fd = new AtomicInteger( - NGUnixDomainSocketLibrary.socket( - NGUnixDomainSocketLibrary.PF_LOCAL, - NGUnixDomainSocketLibrary.SOCK_STREAM, - 0)); - this.backlog = backlog; - if (path != null) { - bind(new NGUnixDomainServerSocketAddress(path)); - } - } catch (LastErrorException e) { - throw new IOException(e); - } - } - - public synchronized void bind(SocketAddress endpoint) throws IOException { - if (!(endpoint instanceof NGUnixDomainServerSocketAddress)) { - throw new IllegalArgumentException( - "endpoint must be an instance of NGUnixDomainServerSocketAddress"); - } - if (isBound) { - throw new IllegalStateException("Socket is already bound"); - } - if (isClosed) { - throw new IllegalStateException("Socket is already closed"); - } - NGUnixDomainServerSocketAddress unEndpoint = (NGUnixDomainServerSocketAddress) endpoint; - NGUnixDomainSocketLibrary.SockaddrUn address = - new NGUnixDomainSocketLibrary.SockaddrUn(unEndpoint.getPath()); - try { - int socketFd = fd.get(); - NGUnixDomainSocketLibrary.bind(socketFd, address, address.size()); - NGUnixDomainSocketLibrary.listen(socketFd, backlog); - isBound = true; - } catch (LastErrorException e) { - throw new IOException(e); - } - } - - public Socket accept() throws IOException { - // We explicitly do not make this method synchronized, since the - // call to NGUnixDomainSocketLibrary.accept() will block - // indefinitely, causing another thread's call to close() to deadlock. - synchronized (this) { - if (!isBound) { - throw new IllegalStateException("Socket is not bound"); - } - if (isClosed) { - throw new IllegalStateException("Socket is already closed"); - } - } - try { - NGUnixDomainSocketLibrary.SockaddrUn sockaddrUn = - new NGUnixDomainSocketLibrary.SockaddrUn(); - IntByReference addressLen = new IntByReference(); - addressLen.setValue(sockaddrUn.size()); - int clientFd = NGUnixDomainSocketLibrary.accept(fd.get(), sockaddrUn, addressLen); - return new NGUnixDomainSocket(clientFd); - } catch (LastErrorException e) { - throw new IOException(e); - } - } - - public synchronized void close() throws IOException { - if (isClosed) { - throw new IllegalStateException("Socket is already closed"); - } - try { - // Ensure any pending call to accept() fails. - NGUnixDomainSocketLibrary.close(fd.getAndSet(-1)); - isClosed = true; - } catch (LastErrorException e) { - throw new IOException(e); - } - } -} diff --git a/main-command/src/main/java/sbt/internal/NGUnixDomainSocket.java b/main-command/src/main/java/sbt/internal/NGUnixDomainSocket.java deleted file mode 100644 index b70bef611..000000000 --- a/main-command/src/main/java/sbt/internal/NGUnixDomainSocket.java +++ /dev/null @@ -1,192 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGUnixDomainSocket.java - -/* - - Copyright 2004-2015, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import com.sun.jna.LastErrorException; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import java.nio.ByteBuffer; - -import java.net.Socket; - -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Implements a {@link Socket} backed by a native Unix domain socket. - * - * Instances of this class always return {@code null} for - * {@link Socket#getInetAddress()}, {@link Socket#getLocalAddress()}, - * {@link Socket#getLocalSocketAddress()}, {@link Socket#getRemoteSocketAddress()}. - */ -public class NGUnixDomainSocket extends Socket { - private final ReferenceCountedFileDescriptor fd; - private final InputStream is; - private final OutputStream os; - - public NGUnixDomainSocket(String path) throws IOException { - try { - AtomicInteger fd = new AtomicInteger( - NGUnixDomainSocketLibrary.socket( - NGUnixDomainSocketLibrary.PF_LOCAL, - NGUnixDomainSocketLibrary.SOCK_STREAM, - 0)); - NGUnixDomainSocketLibrary.SockaddrUn address = - new NGUnixDomainSocketLibrary.SockaddrUn(path); - int socketFd = fd.get(); - NGUnixDomainSocketLibrary.connect(socketFd, address, address.size()); - this.fd = new ReferenceCountedFileDescriptor(socketFd); - this.is = new NGUnixDomainSocketInputStream(); - this.os = new NGUnixDomainSocketOutputStream(); - } catch (LastErrorException e) { - throw new IOException(e); - } - } - - /** - * Creates a Unix domain socket backed by a native file descriptor. - */ - public NGUnixDomainSocket(int fd) { - this.fd = new ReferenceCountedFileDescriptor(fd); - this.is = new NGUnixDomainSocketInputStream(); - this.os = new NGUnixDomainSocketOutputStream(); - } - - public InputStream getInputStream() { - return is; - } - - public OutputStream getOutputStream() { - return os; - } - - public void shutdownInput() throws IOException { - doShutdown(NGUnixDomainSocketLibrary.SHUT_RD); - } - - public void shutdownOutput() throws IOException { - doShutdown(NGUnixDomainSocketLibrary.SHUT_WR); - } - - private void doShutdown(int how) throws IOException { - try { - int socketFd = fd.acquire(); - if (socketFd != -1) { - NGUnixDomainSocketLibrary.shutdown(socketFd, how); - } - } catch (LastErrorException e) { - throw new IOException(e); - } finally { - fd.release(); - } - } - - public void close() throws IOException { - super.close(); - try { - // This might not close the FD right away. In case we are about - // to read or write on another thread, it will delay the close - // until the read or write completes, to prevent the FD from - // being re-used for a different purpose and the other thread - // reading from a different FD. - fd.close(); - } catch (LastErrorException e) { - throw new IOException(e); - } - } - - private class NGUnixDomainSocketInputStream extends InputStream { - public int read() throws IOException { - ByteBuffer buf = ByteBuffer.allocate(1); - int result; - if (doRead(buf) == 0) { - result = -1; - } else { - // Make sure to & with 0xFF to avoid sign extension - result = 0xFF & buf.get(); - } - return result; - } - - public int read(byte[] b, int off, int len) throws IOException { - if (len == 0) { - return 0; - } - ByteBuffer buf = ByteBuffer.wrap(b, off, len); - int result = doRead(buf); - if (result == 0) { - result = -1; - } - return result; - } - - private int doRead(ByteBuffer buf) throws IOException { - try { - int fdToRead = fd.acquire(); - if (fdToRead == -1) { - return -1; - } - return NGUnixDomainSocketLibrary.read(fdToRead, buf, buf.remaining()); - } catch (LastErrorException e) { - throw new IOException(e); - } finally { - fd.release(); - } - } - } - - private class NGUnixDomainSocketOutputStream extends OutputStream { - - public void write(int b) throws IOException { - ByteBuffer buf = ByteBuffer.allocate(1); - buf.put(0, (byte) (0xFF & b)); - doWrite(buf); - } - - public void write(byte[] b, int off, int len) throws IOException { - if (len == 0) { - return; - } - ByteBuffer buf = ByteBuffer.wrap(b, off, len); - doWrite(buf); - } - - private void doWrite(ByteBuffer buf) throws IOException { - try { - int fdToWrite = fd.acquire(); - if (fdToWrite == -1) { - return; - } - int ret = NGUnixDomainSocketLibrary.write(fdToWrite, buf, buf.remaining()); - if (ret != buf.remaining()) { - // This shouldn't happen with standard blocking Unix domain sockets. - throw new IOException("Could not write " + buf.remaining() + " bytes as requested " + - "(wrote " + ret + " bytes instead)"); - } - } catch (LastErrorException e) { - throw new IOException(e); - } finally { - fd.release(); - } - } - } -} diff --git a/main-command/src/main/java/sbt/internal/NGUnixDomainSocketLibrary.java b/main-command/src/main/java/sbt/internal/NGUnixDomainSocketLibrary.java deleted file mode 100644 index 4d781b6b6..000000000 --- a/main-command/src/main/java/sbt/internal/NGUnixDomainSocketLibrary.java +++ /dev/null @@ -1,142 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGUnixDomainSocketLibrary.java - -/* - - Copyright 2004-2015, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import com.sun.jna.LastErrorException; -import com.sun.jna.Native; -import com.sun.jna.Platform; -import com.sun.jna.Structure; -import com.sun.jna.Union; -import com.sun.jna.ptr.IntByReference; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.List; - -/** - * Utility class to bridge native Unix domain socket calls to Java using JNA. - */ -public class NGUnixDomainSocketLibrary { - public static final int PF_LOCAL = 1; - public static final int AF_LOCAL = 1; - public static final int SOCK_STREAM = 1; - - public static final int SHUT_RD = 0; - public static final int SHUT_WR = 1; - - // Utility class, do not instantiate. - private NGUnixDomainSocketLibrary() { } - - // BSD platforms write a length byte at the start of struct sockaddr_un. - private static final boolean HAS_SUN_LEN = - Platform.isMac() || Platform.isFreeBSD() || Platform.isNetBSD() || - Platform.isOpenBSD() || Platform.iskFreeBSD(); - - /** - * Bridges {@code struct sockaddr_un} to and from native code. - */ - public static class SockaddrUn extends Structure implements Structure.ByReference { - /** - * On BSD platforms, the {@code sun_len} and {@code sun_family} values in - * {@code struct sockaddr_un}. - */ - public static class SunLenAndFamily extends Structure { - public byte sunLen; - public byte sunFamily; - - protected List getFieldOrder() { - return Arrays.asList(new String[] { "sunLen", "sunFamily" }); - } - } - - /** - * On BSD platforms, {@code sunLenAndFamily} will be present. - * On other platforms, only {@code sunFamily} will be present. - */ - public static class SunFamily extends Union { - public SunLenAndFamily sunLenAndFamily; - public short sunFamily; - } - - public SunFamily sunFamily = new SunFamily(); - public byte[] sunPath = new byte[104]; - - /** - * Constructs an empty {@code struct sockaddr_un}. - */ - public SockaddrUn() { - if (HAS_SUN_LEN) { - sunFamily.sunLenAndFamily = new SunLenAndFamily(); - sunFamily.setType(SunLenAndFamily.class); - } else { - sunFamily.setType(Short.TYPE); - } - allocateMemory(); - } - - /** - * Constructs a {@code struct sockaddr_un} with a path whose bytes are encoded - * using the default encoding of the platform. - */ - public SockaddrUn(String path) throws IOException { - byte[] pathBytes = path.getBytes(); - if (pathBytes.length > sunPath.length - 1) { - throw new IOException("Cannot fit name [" + path + "] in maximum unix domain socket length"); - } - System.arraycopy(pathBytes, 0, sunPath, 0, pathBytes.length); - sunPath[pathBytes.length] = (byte) 0; - if (HAS_SUN_LEN) { - int len = fieldOffset("sunPath") + pathBytes.length; - sunFamily.sunLenAndFamily = new SunLenAndFamily(); - sunFamily.sunLenAndFamily.sunLen = (byte) len; - sunFamily.sunLenAndFamily.sunFamily = AF_LOCAL; - sunFamily.setType(SunLenAndFamily.class); - } else { - sunFamily.sunFamily = AF_LOCAL; - sunFamily.setType(Short.TYPE); - } - allocateMemory(); - } - - protected List getFieldOrder() { - return Arrays.asList(new String[] { "sunFamily", "sunPath" }); - } - } - - static { - Native.register(Platform.C_LIBRARY_NAME); - } - - public static native int socket(int domain, int type, int protocol) throws LastErrorException; - public static native int bind(int fd, SockaddrUn address, int addressLen) - throws LastErrorException; - public static native int listen(int fd, int backlog) throws LastErrorException; - public static native int accept(int fd, SockaddrUn address, IntByReference addressLen) - throws LastErrorException; - public static native int connect(int fd, SockaddrUn address, int addressLen) - throws LastErrorException; - public static native int read(int fd, ByteBuffer buffer, int count) - throws LastErrorException; - public static native int write(int fd, ByteBuffer buffer, int count) - throws LastErrorException; - public static native int close(int fd) throws LastErrorException; - public static native int shutdown(int fd, int how) throws LastErrorException; -} diff --git a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeLibrary.java b/main-command/src/main/java/sbt/internal/NGWin32NamedPipeLibrary.java deleted file mode 100644 index dd4d8f15a..000000000 --- a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeLibrary.java +++ /dev/null @@ -1,90 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGWin32NamedPipeLibrary.java - -/* - - Copyright 2004-2017, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import java.nio.ByteBuffer; - -import com.sun.jna.*; -import com.sun.jna.platform.win32.WinNT; -import com.sun.jna.platform.win32.WinNT.*; -import com.sun.jna.platform.win32.WinBase.*; -import com.sun.jna.ptr.IntByReference; - -import com.sun.jna.win32.W32APIOptions; - -public interface NGWin32NamedPipeLibrary extends Library, WinNT { - int PIPE_ACCESS_DUPLEX = 3; - int PIPE_UNLIMITED_INSTANCES = 255; - int FILE_FLAG_FIRST_PIPE_INSTANCE = 524288; - - NGWin32NamedPipeLibrary INSTANCE = - (NGWin32NamedPipeLibrary) Native.loadLibrary( - "kernel32", - NGWin32NamedPipeLibrary.class, - W32APIOptions.UNICODE_OPTIONS); - - HANDLE CreateNamedPipe( - String lpName, - int dwOpenMode, - int dwPipeMode, - int nMaxInstances, - int nOutBufferSize, - int nInBufferSize, - int nDefaultTimeOut, - SECURITY_ATTRIBUTES lpSecurityAttributes); - boolean ConnectNamedPipe( - HANDLE hNamedPipe, - Pointer lpOverlapped); - boolean DisconnectNamedPipe( - HANDLE hObject); - boolean ReadFile( - HANDLE hFile, - Memory lpBuffer, - int nNumberOfBytesToRead, - IntByReference lpNumberOfBytesRead, - Pointer lpOverlapped); - boolean WriteFile( - HANDLE hFile, - ByteBuffer lpBuffer, - int nNumberOfBytesToWrite, - IntByReference lpNumberOfBytesWritten, - Pointer lpOverlapped); - boolean CloseHandle( - HANDLE hObject); - boolean GetOverlappedResult( - HANDLE hFile, - Pointer lpOverlapped, - IntByReference lpNumberOfBytesTransferred, - boolean wait); - boolean CancelIoEx( - HANDLE hObject, - Pointer lpOverlapped); - HANDLE CreateEvent( - SECURITY_ATTRIBUTES lpEventAttributes, - boolean bManualReset, - boolean bInitialState, - String lpName); - int WaitForSingleObject( - HANDLE hHandle, - int dwMilliseconds - ); - - int GetLastError(); -} diff --git a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeServerSocket.java b/main-command/src/main/java/sbt/internal/NGWin32NamedPipeServerSocket.java deleted file mode 100644 index 137d9b5dc..000000000 --- a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeServerSocket.java +++ /dev/null @@ -1,173 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGWin32NamedPipeServerSocket.java - -/* - - Copyright 2004-2017, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import com.sun.jna.platform.win32.WinBase; -import com.sun.jna.platform.win32.WinError; -import com.sun.jna.platform.win32.WinNT; -import com.sun.jna.platform.win32.WinNT.HANDLE; -import com.sun.jna.ptr.IntByReference; - -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.LinkedBlockingQueue; - -public class NGWin32NamedPipeServerSocket extends ServerSocket { - private static final NGWin32NamedPipeLibrary API = NGWin32NamedPipeLibrary.INSTANCE; - private static final String WIN32_PIPE_PREFIX = "\\\\.\\pipe\\"; - private static final int BUFFER_SIZE = 65535; - private final LinkedBlockingQueue openHandles; - private final LinkedBlockingQueue connectedHandles; - private final NGWin32NamedPipeSocket.CloseCallback closeCallback; - private final String path; - private final int maxInstances; - private final HANDLE lockHandle; - - public NGWin32NamedPipeServerSocket(String path) throws IOException { - this(NGWin32NamedPipeLibrary.PIPE_UNLIMITED_INSTANCES, path); - } - - public NGWin32NamedPipeServerSocket(int maxInstances, String path) throws IOException { - this.openHandles = new LinkedBlockingQueue<>(); - this.connectedHandles = new LinkedBlockingQueue<>(); - this.closeCallback = handle -> { - if (connectedHandles.remove(handle)) { - closeConnectedPipe(handle, false); - } - if (openHandles.remove(handle)) { - closeOpenPipe(handle); - } - }; - this.maxInstances = maxInstances; - if (!path.startsWith(WIN32_PIPE_PREFIX)) { - this.path = WIN32_PIPE_PREFIX + path; - } else { - this.path = path; - } - String lockPath = this.path + "_lock"; - lockHandle = API.CreateNamedPipe( - lockPath, - NGWin32NamedPipeLibrary.FILE_FLAG_FIRST_PIPE_INSTANCE | NGWin32NamedPipeLibrary.PIPE_ACCESS_DUPLEX, - 0, - 1, - BUFFER_SIZE, - BUFFER_SIZE, - 0, - null); - if (lockHandle == NGWin32NamedPipeLibrary.INVALID_HANDLE_VALUE) { - throw new IOException(String.format("Could not create lock for %s, error %d", lockPath, API.GetLastError())); - } else { - if (!API.DisconnectNamedPipe(lockHandle)) { - throw new IOException(String.format("Could not disconnect lock %d", API.GetLastError())); - } - } - - } - - public void bind(SocketAddress endpoint) throws IOException { - throw new IOException("Win32 named pipes do not support bind(), pass path to constructor"); - } - - public Socket accept() throws IOException { - HANDLE handle = API.CreateNamedPipe( - path, - NGWin32NamedPipeLibrary.PIPE_ACCESS_DUPLEX | WinNT.FILE_FLAG_OVERLAPPED, - 0, - maxInstances, - BUFFER_SIZE, - BUFFER_SIZE, - 0, - null); - if (handle == NGWin32NamedPipeLibrary.INVALID_HANDLE_VALUE) { - throw new IOException(String.format("Could not create named pipe, error %d", API.GetLastError())); - } - openHandles.add(handle); - - HANDLE connWaitable = API.CreateEvent(null, true, false, null); - WinBase.OVERLAPPED olap = new WinBase.OVERLAPPED(); - olap.hEvent = connWaitable; - olap.write(); - - boolean immediate = API.ConnectNamedPipe(handle, olap.getPointer()); - if (immediate) { - openHandles.remove(handle); - connectedHandles.add(handle); - return new NGWin32NamedPipeSocket(handle, closeCallback); - } - - int connectError = API.GetLastError(); - if (connectError == WinError.ERROR_PIPE_CONNECTED) { - openHandles.remove(handle); - connectedHandles.add(handle); - return new NGWin32NamedPipeSocket(handle, closeCallback); - } else if (connectError == WinError.ERROR_NO_DATA) { - // Client has connected and disconnected between CreateNamedPipe() and ConnectNamedPipe() - // connection is broken, but it is returned it avoid loop here. - // Actual error will happen for NGSession when it will try to read/write from/to pipe - return new NGWin32NamedPipeSocket(handle, closeCallback); - } else if (connectError == WinError.ERROR_IO_PENDING) { - if (!API.GetOverlappedResult(handle, olap.getPointer(), new IntByReference(), true)) { - openHandles.remove(handle); - closeOpenPipe(handle); - throw new IOException("GetOverlappedResult() failed for connect operation: " + API.GetLastError()); - } - openHandles.remove(handle); - connectedHandles.add(handle); - return new NGWin32NamedPipeSocket(handle, closeCallback); - } else { - throw new IOException("ConnectNamedPipe() failed with: " + connectError); - } - } - - public void close() throws IOException { - try { - List handlesToClose = new ArrayList<>(); - openHandles.drainTo(handlesToClose); - for (HANDLE handle : handlesToClose) { - closeOpenPipe(handle); - } - - List handlesToDisconnect = new ArrayList<>(); - connectedHandles.drainTo(handlesToDisconnect); - for (HANDLE handle : handlesToDisconnect) { - closeConnectedPipe(handle, true); - } - } finally { - API.CloseHandle(lockHandle); - } - } - - private void closeOpenPipe(HANDLE handle) throws IOException { - API.CancelIoEx(handle, null); - API.CloseHandle(handle); - } - - private void closeConnectedPipe(HANDLE handle, boolean shutdown) throws IOException { - if (!shutdown) { - API.WaitForSingleObject(handle, 10000); - } - API.DisconnectNamedPipe(handle); - API.CloseHandle(handle); - } -} diff --git a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeSocket.java b/main-command/src/main/java/sbt/internal/NGWin32NamedPipeSocket.java deleted file mode 100644 index b22bb6bbf..000000000 --- a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeSocket.java +++ /dev/null @@ -1,172 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGWin32NamedPipeSocket.java -// Made change in `read` to read just the amount of bytes available. - -/* - - Copyright 2004-2017, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import com.sun.jna.Memory; -import com.sun.jna.platform.win32.WinBase; -import com.sun.jna.platform.win32.WinError; -import com.sun.jna.platform.win32.WinNT.HANDLE; -import com.sun.jna.ptr.IntByReference; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.nio.ByteBuffer; - -public class NGWin32NamedPipeSocket extends Socket { - private static final NGWin32NamedPipeLibrary API = NGWin32NamedPipeLibrary.INSTANCE; - private final HANDLE handle; - private final CloseCallback closeCallback; - private final InputStream is; - private final OutputStream os; - private final HANDLE readerWaitable; - private final HANDLE writerWaitable; - - interface CloseCallback { - void onNamedPipeSocketClose(HANDLE handle) throws IOException; - } - - public NGWin32NamedPipeSocket( - HANDLE handle, - NGWin32NamedPipeSocket.CloseCallback closeCallback) throws IOException { - this.handle = handle; - this.closeCallback = closeCallback; - this.readerWaitable = API.CreateEvent(null, true, false, null); - if (readerWaitable == null) { - throw new IOException("CreateEvent() failed "); - } - writerWaitable = API.CreateEvent(null, true, false, null); - if (writerWaitable == null) { - throw new IOException("CreateEvent() failed "); - } - this.is = new NGWin32NamedPipeSocketInputStream(handle); - this.os = new NGWin32NamedPipeSocketOutputStream(handle); - } - - @Override - public InputStream getInputStream() { - return is; - } - - @Override - public OutputStream getOutputStream() { - return os; - } - - @Override - public void close() throws IOException { - closeCallback.onNamedPipeSocketClose(handle); - } - - @Override - public void shutdownInput() throws IOException { - } - - @Override - public void shutdownOutput() throws IOException { - } - - private class NGWin32NamedPipeSocketInputStream extends InputStream { - private final HANDLE handle; - - NGWin32NamedPipeSocketInputStream(HANDLE handle) { - this.handle = handle; - } - - @Override - public int read() throws IOException { - int result; - byte[] b = new byte[1]; - if (read(b) == 0) { - result = -1; - } else { - result = 0xFF & b[0]; - } - return result; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - Memory readBuffer = new Memory(len); - - WinBase.OVERLAPPED olap = new WinBase.OVERLAPPED(); - olap.hEvent = readerWaitable; - olap.write(); - - boolean immediate = API.ReadFile(handle, readBuffer, len, null, olap.getPointer()); - if (!immediate) { - int lastError = API.GetLastError(); - if (lastError != WinError.ERROR_IO_PENDING) { - throw new IOException("ReadFile() failed: " + lastError); - } - } - - IntByReference read = new IntByReference(); - if (!API.GetOverlappedResult(handle, olap.getPointer(), read, true)) { - int lastError = API.GetLastError(); - throw new IOException("GetOverlappedResult() failed for read operation: " + lastError); - } - int actualLen = read.getValue(); - byte[] byteArray = readBuffer.getByteArray(0, actualLen); - System.arraycopy(byteArray, 0, b, off, actualLen); - return actualLen; - } - } - - private class NGWin32NamedPipeSocketOutputStream extends OutputStream { - private final HANDLE handle; - - NGWin32NamedPipeSocketOutputStream(HANDLE handle) { - this.handle = handle; - } - - @Override - public void write(int b) throws IOException { - write(new byte[]{(byte) (0xFF & b)}); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - ByteBuffer data = ByteBuffer.wrap(b, off, len); - - WinBase.OVERLAPPED olap = new WinBase.OVERLAPPED(); - olap.hEvent = writerWaitable; - olap.write(); - - boolean immediate = API.WriteFile(handle, data, len, null, olap.getPointer()); - if (!immediate) { - int lastError = API.GetLastError(); - if (lastError != WinError.ERROR_IO_PENDING) { - throw new IOException("WriteFile() failed: " + lastError); - } - } - IntByReference written = new IntByReference(); - if (!API.GetOverlappedResult(handle, olap.getPointer(), written, true)) { - int lastError = API.GetLastError(); - throw new IOException("GetOverlappedResult() failed for write operation: " + lastError); - } - if (written.getValue() != len) { - throw new IOException("WriteFile() wrote less bytes than requested"); - } - } - } -} diff --git a/main-command/src/main/java/sbt/internal/ReferenceCountedFileDescriptor.java b/main-command/src/main/java/sbt/internal/ReferenceCountedFileDescriptor.java deleted file mode 100644 index 7fb5d9d53..000000000 --- a/main-command/src/main/java/sbt/internal/ReferenceCountedFileDescriptor.java +++ /dev/null @@ -1,82 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/ReferenceCountedFileDescriptor.java - -/* - - Copyright 2004-2015, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import com.sun.jna.LastErrorException; - -import java.io.IOException; - -/** - * Encapsulates a file descriptor plus a reference count to ensure close requests - * only close the file descriptor once the last reference to the file descriptor - * is released. - * - * If not explicitly closed, the file descriptor will be closed when - * this object is finalized. - */ -public class ReferenceCountedFileDescriptor { - private int fd; - private int fdRefCount; - private boolean closePending; - - public ReferenceCountedFileDescriptor(int fd) { - this.fd = fd; - this.fdRefCount = 0; - this.closePending = false; - } - - protected void finalize() throws IOException { - close(); - } - - public synchronized int acquire() { - fdRefCount++; - return fd; - } - - public synchronized void release() throws IOException { - fdRefCount--; - if (fdRefCount == 0 && closePending && fd != -1) { - doClose(); - } - } - - public synchronized void close() throws IOException { - if (fd == -1 || closePending) { - return; - } - - if (fdRefCount == 0) { - doClose(); - } else { - // Another thread has the FD. We'll close it when they release the reference. - closePending = true; - } - } - - private void doClose() throws IOException { - try { - NGUnixDomainSocketLibrary.close(fd); - fd = -1; - } catch (LastErrorException e) { - throw new IOException(e); - } - } -} diff --git a/main-command/src/main/scala/sbt/internal/server/Server.scala b/main-command/src/main/scala/sbt/internal/server/Server.scala index 8104a1736..7e31910e6 100644 --- a/main-command/src/main/scala/sbt/internal/server/Server.scala +++ b/main-command/src/main/scala/sbt/internal/server/Server.scala @@ -25,6 +25,7 @@ import sjsonnew.support.scalajson.unsafe.{ Converter, CompactPrinter } import sbt.internal.protocol.codec._ import sbt.internal.util.ErrorHandling import sbt.internal.util.Util.isWindows +import org.scalasbt.ipcsocket._ private[sbt] sealed trait ServerInstance { def shutdown(): Unit @@ -57,11 +58,11 @@ private[sbt] object Server { connection.connectionType match { case ConnectionType.Local if isWindows => // Named pipe already has an exclusive lock. - addServerError(new NGWin32NamedPipeServerSocket(pipeName)) + addServerError(new Win32NamedPipeServerSocket(pipeName)) case ConnectionType.Local => - tryClient(new NGUnixDomainSocket(socketfile.getAbsolutePath)) + tryClient(new UnixDomainSocket(socketfile.getAbsolutePath)) prepareSocketfile() - addServerError(new NGUnixDomainServerSocket(socketfile.getAbsolutePath)) + addServerError(new UnixDomainServerSocket(socketfile.getAbsolutePath)) case ConnectionType.Tcp => tryClient(new Socket(InetAddress.getByName(host), port)) addServerError(new ServerSocket(port, 50, InetAddress.getByName(host))) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f8d470ccd..5ad8a4393 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -33,6 +33,7 @@ object Dependencies { val launcherInterface = "org.scala-sbt" % "launcher-interface" % "1.0.2" val rawLauncher = "org.scala-sbt" % "launcher" % "1.0.2" val testInterface = "org.scala-sbt" % "test-interface" % "1.0" + val ipcSocket = "org.scala-sbt.ipcsocket" % "ipcsocket" % "1.0.0" private val compilerInterface = "org.scala-sbt" % "compiler-interface" % zincVersion private val compilerClasspath = "org.scala-sbt" %% "zinc-classpath" % zincVersion From 4b1de14f87090512b913ba7470cb5485b98fb4f3 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 30 Jan 2018 00:42:02 -0500 Subject: [PATCH 089/356] Use State to pick the port file --- main/src/main/scala/sbt/internal/CommandExchange.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 2ece2e12a..094491945 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -114,7 +114,7 @@ private[sbt] final class CommandExchange { subscribe(channel) } if (server.isEmpty && firstInstance.get) { - val portfile = (new File(".")).getAbsoluteFile / "project" / "target" / "active.json" + val portfile = s.baseDir / "project" / "target" / "active.json" val h = Hash.halfHashString(IO.toURI(portfile).toString) val tokenfile = BuildPaths.getGlobalBase(s) / "server" / h / "token.json" val socketfile = BuildPaths.getGlobalBase(s) / "server" / h / "sock" From 1f9a8bf310cbb6b3a60c5e8f33efa4540986a909 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 24 Jan 2018 03:56:42 -0500 Subject: [PATCH 090/356] start an instance of sbt in the background --- .appveyor.yml | 2 +- .travis.yml | 2 +- build.sbt | 4 +- .../scala/sbt/protocol/ClientSocket.scala | 45 +++++ .../sbt-test/server/handshake/Client.scala | 106 ----------- sbt/src/sbt-test/server/handshake/build.sbt | 15 -- sbt/src/sbt-test/server/handshake/test | 6 - sbt/src/server-test/handshake/build.sbt | 6 + .../test/scala/sbt/RunFromSourceMain.scala | 2 +- sbt/src/test/scala/sbt/ServerSpec.scala | 179 ++++++++++++++++++ 10 files changed, 236 insertions(+), 131 deletions(-) create mode 100644 protocol/src/main/scala/sbt/protocol/ClientSocket.scala delete mode 100644 sbt/src/sbt-test/server/handshake/Client.scala delete mode 100644 sbt/src/sbt-test/server/handshake/build.sbt delete mode 100644 sbt/src/sbt-test/server/handshake/test create mode 100644 sbt/src/server-test/handshake/build.sbt create mode 100644 sbt/src/test/scala/sbt/ServerSpec.scala diff --git a/.appveyor.yml b/.appveyor.yml index a0d3292f1..7bc9e8b57 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -20,4 +20,4 @@ install: - SET PATH=C:\sbt\sbt\bin;%PATH% - SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g -Dfile.encoding=UTF8 test_script: - - sbt "scripted actions/* server/*" + - sbt "scripted actions/*" "testOnly sbt.ServerSpec" diff --git a/.travis.yml b/.travis.yml index a8a539237..c6e2d000e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ env: - SBT_CMD="scripted dependency-management/*4of4" - SBT_CMD="scripted java/* package/* reporter/* run/* project-load/*" - SBT_CMD="scripted project/*1of2" - - SBT_CMD="scripted project/*2of2 server/*" + - SBT_CMD="scripted project/*2of2" - SBT_CMD="scripted source-dependencies/*1of3" - SBT_CMD="scripted source-dependencies/*2of3" - SBT_CMD="scripted source-dependencies/*3of3" diff --git a/build.sbt b/build.sbt index 3eb98a312..3cf04650b 100644 --- a/build.sbt +++ b/build.sbt @@ -318,6 +318,7 @@ lazy val actionsProj = (project in file("main-actions")) lazy val protocolProj = (project in file("protocol")) .enablePlugins(ContrabandPlugin, JsonCodecPlugin) + .dependsOn(collectionProj) .settings( testedBaseSettings, scalacOptions -= "-Ywarn-unused", @@ -466,7 +467,7 @@ lazy val mainProj = (project in file("main")) lazy val sbtProj = (project in file("sbt")) .dependsOn(mainProj, scriptedSbtProj % "test->test") .settings( - baseSettings, + testedBaseSettings, name := "sbt", normalizedName := "sbt", crossScalaVersions := Seq(baseScalaVersion), @@ -480,6 +481,7 @@ lazy val sbtProj = (project in file("sbt")) buildInfoKeys in Test := Seq[BuildInfoKey](fullClasspath in Compile), connectInput in run in Test := true, outputStrategy in run in Test := Some(StdoutOutput), + fork in Test := true, ) .configure(addSbtCompilerBridge) diff --git a/protocol/src/main/scala/sbt/protocol/ClientSocket.scala b/protocol/src/main/scala/sbt/protocol/ClientSocket.scala new file mode 100644 index 000000000..df155d439 --- /dev/null +++ b/protocol/src/main/scala/sbt/protocol/ClientSocket.scala @@ -0,0 +1,45 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt +package protocol + +import java.io.File +import java.net.{ Socket, URI, InetAddress } +import sjsonnew.BasicJsonProtocol +import sjsonnew.support.scalajson.unsafe.{ Parser, Converter } +import sjsonnew.shaded.scalajson.ast.unsafe.JValue +import sbt.internal.protocol.{ PortFile, TokenFile } +import sbt.internal.protocol.codec.{ PortFileFormats, TokenFileFormats } +import sbt.internal.util.Util.isWindows +import org.scalasbt.ipcsocket._ + +object ClientSocket { + private lazy val fileFormats = new BasicJsonProtocol with PortFileFormats with TokenFileFormats {} + + def socket(portfile: File): (Socket, Option[String]) = { + import fileFormats._ + val json: JValue = Parser.parseFromFile(portfile).get + val p = Converter.fromJson[PortFile](json).get + val uri = new URI(p.uri) + // println(uri) + val token = p.tokenfilePath map { tp => + val tokeFile = new File(tp) + val json: JValue = Parser.parseFromFile(tokeFile).get + val t = Converter.fromJson[TokenFile](json).get + t.token + } + val sk = uri.getScheme match { + case "local" if isWindows => + new Win32NamedPipeSocket("""\\.\pipe\""" + uri.getSchemeSpecificPart) + case "local" => new UnixDomainSocket(uri.getSchemeSpecificPart) + case "tcp" => new Socket(InetAddress.getByName(uri.getHost), uri.getPort) + case _ => sys.error(s"Unsupported uri: $uri") + } + (sk, token) + } +} diff --git a/sbt/src/sbt-test/server/handshake/Client.scala b/sbt/src/sbt-test/server/handshake/Client.scala deleted file mode 100644 index 2f41f4c18..000000000 --- a/sbt/src/sbt-test/server/handshake/Client.scala +++ /dev/null @@ -1,106 +0,0 @@ -package example - -import java.net.{ URI, Socket, InetAddress, SocketException } -import sbt.io._ -import sbt.io.syntax._ -import java.io.File -import sjsonnew.support.scalajson.unsafe.{ Parser, Converter, CompactPrinter } -import sjsonnew.shaded.scalajson.ast.unsafe.{ JValue, JObject, JString } - -object Client extends App { - val host = "127.0.0.1" - val delimiter: Byte = '\n'.toByte - - lazy val connection = getConnection - lazy val out = connection.getOutputStream - lazy val in = connection.getInputStream - - val t = getToken - val msg0 = s"""{ "type": "InitCommand", "token": "$t" }""" - - writeLine(s"Content-Length: ${ msg0.size + 2 }") - writeLine("Content-Type: application/sbt-x1") - writeLine("") - writeLine(msg0) - out.flush - - writeLine("Content-Length: 49") - writeLine("Content-Type: application/sbt-x1") - writeLine("") - // 12345678901234567890123456789012345678901234567890 - writeLine("""{ "type": "ExecCommand", "commandLine": "exit" }""") - writeLine("") - out.flush - - val baseDirectory = new File(args(0)) - IO.write(baseDirectory / "ok.txt", "ok") - - def getToken: String = { - val tokenfile = new File(getTokenFileUri) - val json: JValue = Parser.parseFromFile(tokenfile).get - json match { - case JObject(fields) => - (fields find { _.field == "token" } map { _.value }) match { - case Some(JString(value)) => value - case _ => - sys.error("json doesn't token field that is JString") - } - case _ => sys.error("json doesn't have token field") - } - } - - def getTokenFileUri: URI = { - val portfile = baseDirectory / "project" / "target" / "active.json" - val json: JValue = Parser.parseFromFile(portfile).get - json match { - case JObject(fields) => - (fields find { _.field == "tokenfileUri" } map { _.value }) match { - case Some(JString(value)) => new URI(value) - case _ => - sys.error("json doesn't tokenfile field that is JString") - } - case _ => sys.error("json doesn't have tokenfile field") - } - } - - def getPort: Int = { - val portfile = baseDirectory / "project" / "target" / "active.json" - val json: JValue = Parser.parseFromFile(portfile).get - json match { - case JObject(fields) => - (fields find { _.field == "uri" } map { _.value }) match { - case Some(JString(value)) => - val u = new URI(value) - u.getPort - case _ => - sys.error("json doesn't uri field that is JString") - } - case _ => sys.error("json doesn't have uri field") - } - } - - def getConnection: Socket = - try { - new Socket(InetAddress.getByName(host), getPort) - } catch { - case _ => - Thread.sleep(1000) - getConnection - } - - def writeLine(s: String): Unit = { - if (s != "") { - out.write(s.getBytes("UTF-8")) - } - writeEndLine - } - - def writeEndLine(): Unit = { - val retByte: Byte = '\r'.toByte - val delimiter: Byte = '\n'.toByte - - out.write(retByte.toInt) - out.write(delimiter.toInt) - out.flush - } -} diff --git a/sbt/src/sbt-test/server/handshake/build.sbt b/sbt/src/sbt-test/server/handshake/build.sbt deleted file mode 100644 index 851648f3c..000000000 --- a/sbt/src/sbt-test/server/handshake/build.sbt +++ /dev/null @@ -1,15 +0,0 @@ -lazy val runClient = taskKey[Unit]("") - -lazy val root = (project in file(".")) - .settings( - serverConnectionType in Global := ConnectionType.Tcp, - scalaVersion := "2.12.3", - serverPort in Global := 5123, - libraryDependencies += "org.scala-sbt" %% "io" % "1.0.1", - libraryDependencies += "com.eed3si9n" %% "sjson-new-scalajson" % "0.8.0", - runClient := (Def.taskDyn { - val b = baseDirectory.value - (bgRun in Compile).toTask(s""" $b""") - }).value - ) - \ No newline at end of file diff --git a/sbt/src/sbt-test/server/handshake/test b/sbt/src/sbt-test/server/handshake/test deleted file mode 100644 index 703942376..000000000 --- a/sbt/src/sbt-test/server/handshake/test +++ /dev/null @@ -1,6 +0,0 @@ -> show serverPort -> runClient - --> shell - -$ exists ok.txt diff --git a/sbt/src/server-test/handshake/build.sbt b/sbt/src/server-test/handshake/build.sbt new file mode 100644 index 000000000..192730eef --- /dev/null +++ b/sbt/src/server-test/handshake/build.sbt @@ -0,0 +1,6 @@ +lazy val root = (project in file(".")) + .settings( + Global / serverLog / logLevel := Level.Debug, + name := "handshake", + scalaVersion := "2.12.3", + ) diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala index bd2f142f7..b4fc8bce4 100644 --- a/sbt/src/test/scala/sbt/RunFromSourceMain.scala +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -22,7 +22,7 @@ object RunFromSourceMain { // this arrangement is because Scala does not always properly optimize away // the tail recursion in a catch statement - @tailrec private def run(baseDir: File, args: Seq[String]): Unit = + @tailrec private[sbt] def run(baseDir: File, args: Seq[String]): Unit = runImpl(baseDir, args) match { case Some((baseDir, args)) => run(baseDir, args) case None => () diff --git a/sbt/src/test/scala/sbt/ServerSpec.scala b/sbt/src/test/scala/sbt/ServerSpec.scala new file mode 100644 index 000000000..648d88203 --- /dev/null +++ b/sbt/src/test/scala/sbt/ServerSpec.scala @@ -0,0 +1,179 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt + +import org.scalatest._ +import scala.concurrent._ +import java.io.{ InputStream, OutputStream } +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.{ ThreadFactory, ThreadPoolExecutor } +import sbt.protocol.ClientSocket + +class ServerSpec extends AsyncFlatSpec with Matchers { + import ServerSpec._ + + "server" should "start" in { + withBuildSocket("handshake") { (out, in, tkn) => + writeLine( + """{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "root/name" } }""", + out) + Thread.sleep(100) + val l2 = contentLength(in) + println(l2) + readLine(in) + readLine(in) + val x2 = readContentLength(in, l2) + println(x2) + assert(1 == 1) + } + } +} + +object ServerSpec { + private val serverTestBase: File = new File(".").getAbsoluteFile / "sbt" / "src" / "server-test" + private val nextThreadId = new AtomicInteger(1) + private val threadGroup = Thread.currentThread.getThreadGroup() + val readBuffer = new Array[Byte](4096) + var buffer: Vector[Byte] = Vector.empty + var bytesRead = 0 + private val delimiter: Byte = '\n'.toByte + private val RetByte = '\r'.toByte + + private val threadFactory = new ThreadFactory() { + override def newThread(runnable: Runnable): Thread = { + val thread = + new Thread(threadGroup, + runnable, + s"sbt-test-server-threads-${nextThreadId.getAndIncrement}") + // Do NOT setDaemon because then the code in TaskExit.scala in sbt will insta-kill + // the backgrounded process, at least for the case of the run task. + thread + } + } + + private val executor = new ThreadPoolExecutor( + 0, /* corePoolSize */ + 1, /* maxPoolSize, max # of servers */ + 2, + java.util.concurrent.TimeUnit.SECONDS, + /* keep alive unused threads this long (if corePoolSize < maxPoolSize) */ + new java.util.concurrent.SynchronousQueue[Runnable](), + threadFactory + ) + + def backgroundRun(baseDir: File, args: Seq[String]): Unit = { + executor.execute(new Runnable { + def run(): Unit = { + RunFromSourceMain.run(baseDir, args) + } + }) + } + + def shutdown(): Unit = executor.shutdown() + + def withBuildSocket(testBuild: String)( + f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = { + IO.withTemporaryDirectory { temp => + IO.copyDirectory(serverTestBase / testBuild, temp / testBuild) + withBuildSocket(temp / testBuild)(f) + } + } + + def sendJsonRpc(message: String, out: OutputStream): Unit = { + writeLine(s"""Content-Length: ${message.size + 2}""", out) + writeLine("", out) + writeLine(message, out) + } + + def contentLength(in: InputStream): Int = { + readLine(in) map { line => + line.drop(16).toInt + } getOrElse (0) + } + + def readLine(in: InputStream): Option[String] = { + if (buffer.isEmpty) { + val bytesRead = in.read(readBuffer) + if (bytesRead > 0) { + buffer = buffer ++ readBuffer.toVector.take(bytesRead) + } + } + val delimPos = buffer.indexOf(delimiter) + if (delimPos > 0) { + val chunk0 = buffer.take(delimPos) + buffer = buffer.drop(delimPos + 1) + // remove \r at the end of line. + if (chunk0.size > 0 && chunk0.indexOf(RetByte) == chunk0.size - 1) + Some(new String(chunk0.dropRight(1).toArray, "utf-8")) + else Some(new String(chunk0.toArray, "utf-8")) + } else None // no EOL yet, so skip this turn. + } + + def readContentLength(in: InputStream, length: Int): Option[String] = { + if (buffer.isEmpty) { + val bytesRead = in.read(readBuffer) + if (bytesRead > 0) { + buffer = buffer ++ readBuffer.toVector.take(bytesRead) + } + } + if (length <= buffer.size) { + val chunk = buffer.take(length) + buffer = buffer.drop(length) + Some(new String(chunk.toArray, "utf-8")) + } else None // have not read enough yet, so skip this turn. + } + + def writeLine(s: String, out: OutputStream): Unit = { + def writeEndLine(): Unit = { + val retByte: Byte = '\r'.toByte + val delimiter: Byte = '\n'.toByte + out.write(retByte.toInt) + out.write(delimiter.toInt) + out.flush + } + + if (s != "") { + out.write(s.getBytes("UTF-8")) + } + writeEndLine + } + + def withBuildSocket(baseDirectory: File)( + f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = { + backgroundRun(baseDirectory, Nil) + + val portfile = baseDirectory / "project" / "target" / "active.json" + + def waitForPortfile(n: Int): Unit = + if (portfile.exists) () + else { + if (n <= 0) sys.error(s"Timeout. $portfile is not found.") + else { + Thread.sleep(1000) + waitForPortfile(n - 1) + } + } + waitForPortfile(10) + val (sk, tkn) = ClientSocket.socket(portfile) + val out = sk.getOutputStream + val in = sk.getInputStream + + sendJsonRpc( + """{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { } } }""", + out) + + try { + f(out, in, tkn) + } finally { + sendJsonRpc( + """{ "jsonrpc": "2.0", "id": 9, "method": "sbt/exec", "params": { "commandLine": "exit" } }""", + out) + shutdown() + } + } +} From 02218582170aeb8953a89ecea963a6863860ae2b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 31 Jan 2018 00:04:23 -0500 Subject: [PATCH 091/356] improve Windows build --- .appveyor.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 7bc9e8b57..ccc70ab7c 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -4,9 +4,8 @@ init: - git config --global core.autocrlf input install: - - cinst jdk8 -params 'installdir=C:\\jdk8' - - SET JAVA_HOME=C:\jdk8 - - SET PATH=C:\jdk8\bin;%PATH% + - SET JAVA_HOME=C:\Program Files\Java\jdk1.8.0 + - SET PATH=%JAVA_HOME%\bin;%PATH% - ps: | Add-Type -AssemblyName System.IO.Compression.FileSystem @@ -21,3 +20,7 @@ install: - SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g -Dfile.encoding=UTF8 test_script: - sbt "scripted actions/*" "testOnly sbt.ServerSpec" + +cache: + - '%USERPROFILE%\.ivy2\cache' + - '%USERPROFILE%\.sbt' From a9bdcc4f0fdd4b85c72c15c9e8925c16e14acba4 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 31 Jan 2018 10:20:33 +0000 Subject: [PATCH 092/356] Fix Codacy issue in ServerSpec --- sbt/src/test/scala/sbt/ServerSpec.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sbt/src/test/scala/sbt/ServerSpec.scala b/sbt/src/test/scala/sbt/ServerSpec.scala index 648d88203..9adeabcc8 100644 --- a/sbt/src/test/scala/sbt/ServerSpec.scala +++ b/sbt/src/test/scala/sbt/ServerSpec.scala @@ -108,9 +108,8 @@ object ServerSpec { val chunk0 = buffer.take(delimPos) buffer = buffer.drop(delimPos + 1) // remove \r at the end of line. - if (chunk0.size > 0 && chunk0.indexOf(RetByte) == chunk0.size - 1) - Some(new String(chunk0.dropRight(1).toArray, "utf-8")) - else Some(new String(chunk0.toArray, "utf-8")) + val chunk1 = if (chunk0.isEmpty || chunk0.last != RetByte) chunk0 else chunk0.dropRight(1) + Some(new String(chunk1.toArray, "utf-8")) } else None // no EOL yet, so skip this turn. } From 0aa133d2762aad2ea3a52d77dbbbac8bdc3dc0f6 Mon Sep 17 00:00:00 2001 From: Steve Waldman Date: Mon, 5 Feb 2018 23:11:42 -0800 Subject: [PATCH 093/356] Implement 'suppressServer' setting, for builds and plugins that prefer to be conservative about exposure to other processes. --- main-command/src/main/scala/sbt/BasicKeys.scala | 6 ++++++ main/src/main/scala/sbt/Defaults.scala | 1 + main/src/main/scala/sbt/Keys.scala | 1 + main/src/main/scala/sbt/Project.scala | 3 +++ main/src/main/scala/sbt/internal/CommandExchange.scala | 7 ++++++- 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/main-command/src/main/scala/sbt/BasicKeys.scala b/main-command/src/main/scala/sbt/BasicKeys.scala index 86dd9343f..a820515db 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 suppressServer = + AttributeKey[Boolean]( + "suppressServer", + "Running the server will be suppressed if 'suppressServer is explicitly set to true.", + 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..35c9db19d 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, + suppressServer := false, 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..1d32e2bb5 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 suppressServer = SettingKey(BasicKeys.suppressServer) 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..bd9fe8b0b 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, + suppressServer, 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 suppressSvr: Option[Boolean] = get(suppressServer) 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(suppressServer.key, suppressSvr) .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..eeadfad33 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.{ + suppressServer, serverHost, serverPort, serverAuthentication, @@ -87,7 +88,11 @@ private[sbt] final class CommandExchange { consoleChannel = Some(x) subscribe(x) } - if (autoStartServer) runServer(s) + val suppress = (s get suppressServer) match { + case Some(bool) => bool + case None => false + } + if (autoStartServer && !suppress) runServer(s) else s } From 9601668199cd52d6fa37aecae4cbda10920f89bf Mon Sep 17 00:00:00 2001 From: Steve Waldman Date: Mon, 5 Feb 2018 23:35:31 -0800 Subject: [PATCH 094/356] Add a note describing the suppressServer feature. --- notes/1.1.1/suppressServer.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 notes/1.1.1/suppressServer.md diff --git a/notes/1.1.1/suppressServer.md b/notes/1.1.1/suppressServer.md new file mode 100644 index 000000000..0e9fdce79 --- /dev/null +++ b/notes/1.1.1/suppressServer.md @@ -0,0 +1,23 @@ +### Improvements + +This pull request implements a Boolean setting called `suppressServer`, whose default value is `false'. + +If a build or plugin explicitly sets it to `true`, the sbt-1.x server will not start up +(exactly as if `sbt.server.autostart` were set to start). + +Users may manually override this setting by running the `startServer` command at the interactive prompt. + +### 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 `suppressServer` +to `true` in their build. Security-sensitive plugins can define this setting 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). From abb6f579a73d7268ae2d8025c8f2b0b8690c36f7 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 1 Feb 2018 17:29:26 +0000 Subject: [PATCH 095/356] Un-stub RunFromSource's ComponentProvider impl --- .../test/scala/sbt/RunFromSourceMain.scala | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala index b4fc8bce4..4ccc110ba 100644 --- a/sbt/src/test/scala/sbt/RunFromSourceMain.scala +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -103,9 +103,31 @@ object RunFromSourceMain { def components = new ComponentProvider { def componentLocation(id: String) = appHome / id def component(id: String) = IO.listFiles(componentLocation(id), _.isFile) - def defineComponent(id: String, components: Array[File]) = () - def addToComponent(id: String, components: Array[File]) = false - def lockFile = null + + def defineComponent(id: String, files: Array[File]) = { + val location = componentLocation(id) + if (location.exists) + sys error s"Cannot redefine component. ID: $id, files: ${files mkString ","}" + else { + copy(files.toList, location) + () + } + } + + def addToComponent(id: String, files: Array[File]) = + copy(files.toList, componentLocation(id)) + + def lockFile = appHome / "sbt.components.lock" + + private def copy(files: List[File], toDirectory: File): Boolean = + files exists (copy(_, toDirectory)) + + private def copy(file: File, toDirectory: File): Boolean = { + val to = toDirectory / file.getName + val missing = !to.exists + IO.copyFile(file, to) + missing + } } } } From 4f4328748c33f141f19a74f5f92ac54a7cc704b1 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 1 Feb 2018 14:53:11 +0000 Subject: [PATCH 096/356] Try RunFromSourceBasedRemoteSbtCreator --- build.sbt | 16 ++ .../sbt/scriptedtest/RemoteSbtCreator.scala | 67 ++++++++ .../scala/sbt/scriptedtest/SbtHandler.scala | 31 ++-- .../sbt/scriptedtest/ScriptedTests.scala | 150 ++++++++++++++++-- 4 files changed, 232 insertions(+), 32 deletions(-) create mode 100644 scripted/sbt/src/main/scala/sbt/scriptedtest/RemoteSbtCreator.scala diff --git a/build.sbt b/build.sbt index 3cf04650b..b6ed67172 100644 --- a/build.sbt +++ b/build.sbt @@ -261,12 +261,27 @@ lazy val runProj = (project in file("run")) ) .configure(addSbtIO, addSbtUtilLogging, addSbtCompilerClasspath) +val sbtProjDepsCompileScopeFilter = + ScopeFilter(inDependencies(LocalProject("sbtProj"), includeRoot = false), inConfigurations(Compile)) + lazy val scriptedSbtProj = (project in scriptedPath / "sbt") .dependsOn(commandProj) .settings( baseSettings, name := "Scripted sbt", libraryDependencies ++= Seq(launcherInterface % "provided"), + resourceGenerators in Compile += Def task { + val mainClassDir = (classDirectory in Compile in LocalProject("sbtProj")).value + val testClassDir = (classDirectory in Test in LocalProject("sbtProj")).value + val classDirs = (classDirectory all sbtProjDepsCompileScopeFilter).value + val extDepsCp = (externalDependencyClasspath in Compile in LocalProject("sbtProj")).value + + val cpStrings = (mainClassDir +: testClassDir +: classDirs) ++ extDepsCp.files map (_.toString) + + val file = (resourceManaged in Compile).value / "RunFromSource.classpath" + IO.writeLines(file, cpStrings) + List(file) + }, mimaSettings, mimaBinaryIssueFilters ++= Seq( // sbt.test package is renamed to sbt.scriptedtest. @@ -550,6 +565,7 @@ def scriptedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask { val result = scriptedSource(dir => (s: State) => Scripted.scriptedParser(dir)).parsed // publishLocalBinAll.value // TODO: Restore scripted needing only binary jars. publishAll.value + (sbtProj / Test / compile).value // make sure sbt.RunFromSourceMain is compiled Scripted.doScripted( (sbtLaunchJar in bundledLauncherProj).value, (fullClasspath in scriptedSbtProj in Test).value, diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/RemoteSbtCreator.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/RemoteSbtCreator.scala new file mode 100644 index 000000000..5dbf2ef51 --- /dev/null +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/RemoteSbtCreator.scala @@ -0,0 +1,67 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt +package scriptedtest + +import java.io.File + +import scala.sys.process.{ BasicIO, Process } + +import sbt.io.IO +import sbt.util.Logger + +import xsbt.IPC + +private[sbt] sealed trait RemoteSbtCreatorKind +private[sbt] object RemoteSbtCreatorKind { + case object LauncherBased extends RemoteSbtCreatorKind + case object RunFromSourceBased extends RemoteSbtCreatorKind +} + +abstract class RemoteSbtCreator private[sbt] { + def newRemote(server: IPC.Server): Process +} + +final class LauncherBasedRemoteSbtCreator( + directory: File, + launcher: File, + log: Logger, + launchOpts: Seq[String] = Nil, +) extends RemoteSbtCreator { + def newRemote(server: IPC.Server) = { + val launcherJar = launcher.getAbsolutePath + val globalBase = "-Dsbt.global.base=" + (new File(directory, "global")).getAbsolutePath + val args = List("<" + server.port) + val cmd = "java" :: launchOpts.toList ::: globalBase :: "-jar" :: launcherJar :: args ::: Nil + val io = BasicIO(false, log).withInput(_.close()) + val p = Process(cmd, directory) run (io) + val thread = new Thread() { override def run() = { p.exitValue(); server.close() } } + thread.start() + p + } +} + +final class RunFromSourceBasedRemoteSbtCreator( + directory: File, + log: Logger, + launchOpts: Seq[String] = Nil, +) extends RemoteSbtCreator { + def newRemote(server: IPC.Server) = { + val globalBase = "-Dsbt.global.base=" + (new File(directory, "global")).getAbsolutePath + val cp = IO readLinesURL (getClass getResource "/RunFromSource.classpath") + val cpString = cp mkString File.pathSeparator + val mainClassName = "sbt.RunFromSourceMain" + val args = List(mainClassName, directory.toString, "<" + server.port) + val cmd = "java" :: launchOpts.toList ::: globalBase :: "-cp" :: cpString :: args ::: Nil + val io = BasicIO(false, log).withInput(_.close()) + val p = Process(cmd, directory) run (io) + val thread = new Thread() { override def run() = { p.exitValue(); server.close() } } + thread.start() + p + } +} diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/SbtHandler.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/SbtHandler.scala index 3ddecbab8..9ba5be673 100644 --- a/scripted/sbt/src/main/scala/sbt/scriptedtest/SbtHandler.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/SbtHandler.scala @@ -8,23 +8,19 @@ package sbt package scriptedtest -import java.io.{ File, IOException } -import xsbt.IPC +import java.io.IOException +import java.net.SocketException + +import scala.sys.process.Process import sbt.internal.scripted.{ StatementHandler, TestFailed } -import sbt.util.Logger -import sbt.util.Logger._ - -import scala.sys.process.{ BasicIO, Process } +import xsbt.IPC final case class SbtInstance(process: Process, server: IPC.Server) -final class SbtHandler(directory: File, - launcher: File, - log: Logger, - launchOpts: Seq[String] = Seq()) - extends StatementHandler { +final class SbtHandler(remoteSbtCreator: RemoteSbtCreator) extends StatementHandler { + type State = Option[SbtInstance] def initialState = None @@ -78,16 +74,9 @@ final class SbtHandler(directory: File, if (!resultMessage.toBoolean) throw new TestFailed(errorMessage) } def newRemote(server: IPC.Server): Process = { - val launcherJar = launcher.getAbsolutePath - val globalBase = "-Dsbt.global.base=" + (new File(directory, "global")).getAbsolutePath - val args = "java" :: (launchOpts.toList ++ (globalBase :: "-jar" :: launcherJar :: ("<" + server.port) :: Nil)) - val io = BasicIO(false, log).withInput(_.close()) - val p = Process(args, directory) run (io) - val thread = new Thread() { override def run() = { p.exitValue(); server.close() } } - thread.start() - try { receive("Remote sbt initialization failed", server) } catch { - case _: java.net.SocketException => throw new TestFailed("Remote sbt initialization failed") - } + val p = remoteSbtCreator.newRemote(server) + try receive("Remote sbt initialization failed", server) + catch { case _: SocketException => throw new TestFailed("Remote sbt initialization failed") } p } import java.util.regex.Pattern.{ quote => q } diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index 4d552994c..88c28d01c 100644 --- a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -57,7 +57,8 @@ final class ScriptedTests(resourceBaseDirectory: File, val result = testResources.readWriteResourceDirectory(g, n) { testDirectory => val buffer = new BufferedLogger(new FullLogger(log)) val singleTestRunner = () => { - val handlers = createScriptedHandlers(testDirectory, buffer) + val handlers = + createScriptedHandlers(testDirectory, buffer, RemoteSbtCreatorKind.LauncherBased) val runner = new BatchScriptRunner val states = new mutable.HashMap[StatementHandler, Any]() commonRunTest(label, testDirectory, prescripted, handlers, runner, states, buffer) @@ -71,10 +72,17 @@ final class ScriptedTests(resourceBaseDirectory: File, private def createScriptedHandlers( testDir: File, - buffered: Logger + buffered: Logger, + remoteSbtCreatorKind: RemoteSbtCreatorKind, ): Map[Char, StatementHandler] = { val fileHandler = new FileCommands(testDir) - val sbtHandler = new SbtHandler(testDir, launcher, buffered, launchOpts) + val remoteSbtCreator = remoteSbtCreatorKind match { + case RemoteSbtCreatorKind.LauncherBased => + new LauncherBasedRemoteSbtCreator(testDir, launcher, buffered, launchOpts) + case RemoteSbtCreatorKind.RunFromSourceBased => + new RunFromSourceBasedRemoteSbtCreator(testDir, buffered, launchOpts) + } + val sbtHandler = new SbtHandler(remoteSbtCreator) Map('$' -> fileHandler, '>' -> sbtHandler, '#' -> CommentHandler) } @@ -94,6 +102,8 @@ final class ScriptedTests(resourceBaseDirectory: File, } yield (groupDir, testDir) } + type TestInfo = ((String, String), File) + val labelsAndDirs = groupAndNameDirs.map { case (groupDir, nameDir) => val groupName = groupDir.getName @@ -104,15 +114,132 @@ final class ScriptedTests(resourceBaseDirectory: File, if (labelsAndDirs.isEmpty) List() else { - val batchSeed = labelsAndDirs.size / sbtInstances - val batchSize = if (batchSeed == 0) labelsAndDirs.size else batchSeed - labelsAndDirs - .grouped(batchSize) - .map(batch => () => IO.withTemporaryDirectory(runBatchedTests(batch, _, prescripted, log))) - .toList + val totalSize = labelsAndDirs.size + val batchSize = totalSize / sbtInstances + + val (launcherBasedTests, runFromSourceBasedTests) = labelsAndDirs.partition { + case (testName, _) => + determineRemoteSbtCreatorKind(testName) match { + case RemoteSbtCreatorKind.LauncherBased => true + case RemoteSbtCreatorKind.RunFromSourceBased => false + } + } + + def logTests(size: Int, how: String) = + log.info( + f"Running $size / $totalSize (${size * 100D / totalSize}%3.2f%%) scripted tests with $how") + logTests(runFromSourceBasedTests.size, "RunFromSourceMain") + logTests(launcherBasedTests.size, "sbt/launcher") + + def createTestRunners( + tests: Seq[TestInfo], + remoteSbtCreatorKind: RemoteSbtCreatorKind, + ): Seq[TestRunner] = { + tests + .grouped(batchSize) + .map { batch => () => + IO.withTemporaryDirectory { + runBatchedTests(batch, _, prescripted, remoteSbtCreatorKind, log) + } + } + .toList + } + + createTestRunners(runFromSourceBasedTests, RemoteSbtCreatorKind.RunFromSourceBased) ++ + createTestRunners(launcherBasedTests, RemoteSbtCreatorKind.LauncherBased) } } + private def determineRemoteSbtCreatorKind(testName: (String, String)): RemoteSbtCreatorKind = { + import RemoteSbtCreatorKind._ + val (group, name) = testName + s"$group/$name" match { + case "actions/add-alias" => LauncherBased // sbt/Package$ + case "actions/cross-multiproject" => LauncherBased // tbd + case "actions/external-doc" => LauncherBased // sbt/Package$ + case "actions/input-task" => LauncherBased // sbt/Package$ + case "actions/input-task-dyn" => LauncherBased // sbt/Package$ + case "compiler-project/run-test" => LauncherBased // sbt/Package$ + case "compiler-project/src-dep-plugin" => LauncherBased // sbt/Package$ + case "dependency-management/artifact" => LauncherBased // tbd + case "dependency-management/cache-classifiers" => LauncherBased // tbd + case "dependency-management/cache-local" => LauncherBased // tbd + case "dependency-management/cache-resolver" => LauncherBased // sbt/Package$ + case "dependency-management/cache-update" => LauncherBased // tbd + case "dependency-management/cached-resolution-circular" => LauncherBased // tbd + case "dependency-management/cached-resolution-classifier" => LauncherBased // tbd + case "dependency-management/cached-resolution-configurations" => LauncherBased // tbd + case "dependency-management/cached-resolution-conflicts" => LauncherBased // tbd + case "dependency-management/cached-resolution-exclude" => LauncherBased // tbd + case "dependency-management/cached-resolution-force" => LauncherBased // tbd + case "dependency-management/cached-resolution-interproj" => LauncherBased // tbd + case "dependency-management/cached-resolution-overrides" => LauncherBased // tbd + case "dependency-management/chainresolver" => LauncherBased // tbd + case "dependency-management/circular-dependency" => LauncherBased // tbd + case "dependency-management/classifier" => LauncherBased // tbd + case "dependency-management/default-resolvers" => LauncherBased // tbd + case "dependency-management/deliver-artifacts" => LauncherBased // tbd + case "dependency-management/exclude-transitive" => LauncherBased // tbd + case "dependency-management/extra" => LauncherBased // tbd + case "dependency-management/force" => LauncherBased // tbd + case "dependency-management/info" => LauncherBased // tbd + case "dependency-management/inline-dependencies-a" => LauncherBased // tbd + case "dependency-management/ivy-settings-c" => LauncherBased // sbt/Package$ + case "dependency-management/latest-local-plugin" => LauncherBased // sbt/Package$ + case "dependency-management/metadata-only-resolver" => LauncherBased // tbd + case "dependency-management/no-file-fails-publish" => LauncherBased // tbd + case "dependency-management/override" => LauncherBased // tbd + case "dependency-management/parent-publish" => LauncherBased // sbt/Package$ + case "dependency-management/pom-parent-pom" => LauncherBased // tbd + case "dependency-management/publish-to-maven-local-file" => LauncherBased // sbt/Package$ + case "dependency-management/snapshot-resolution" => LauncherBased // tbd + case "dependency-management/test-artifact" => LauncherBased // sbt/Package$ + case "dependency-management/transitive-version-range" => LauncherBased // tbd + case "dependency-management/update-sbt-classifiers" => LauncherBased // tbd + case "dependency-management/url" => LauncherBased // tbd + case "java/argfile" => LauncherBased // sbt/Package$ + case "java/basic" => LauncherBased // sbt/Package$ + case "java/varargs-main" => LauncherBased // sbt/Package$ + case "package/lazy-name" => LauncherBased // sbt/Package$ + case "package/manifest" => LauncherBased // sbt/Package$ + case "package/resources" => LauncherBased // sbt/Package$ + case "project/Class.forName" => LauncherBased // sbt/Package$ + case "project/binary-plugin" => LauncherBased // sbt/Package$ + case "project/default-settings" => LauncherBased // sbt/Package$ + case "project/extra" => LauncherBased // tbd + case "project/flatten" => LauncherBased // sbt/Package$ + case "project/generated-root-no-publish" => LauncherBased // tbd + case "project/lib" => LauncherBased // sbt/Package$ + case "project/scripted-plugin" => LauncherBased // tbd + case "project/scripted-skip-incompatible" => LauncherBased // sbt/Package$ + case "project/session-update-from-cmd" => LauncherBased // tbd + case "project/transitive-plugins" => LauncherBased // tbd + case "run/awt" => LauncherBased // sbt/Package$ + case "run/classpath" => LauncherBased // sbt/Package$ + case "run/daemon" => LauncherBased // sbt/Package$ + case "run/daemon-exit" => LauncherBased // sbt/Package$ + case "run/error" => LauncherBased // sbt/Package$ + case "run/fork" => LauncherBased // sbt/Package$ + case "run/fork-loader" => LauncherBased // sbt/Package$ + case "run/non-local-main" => LauncherBased // sbt/Package$ + case "run/spawn" => LauncherBased // sbt/Package$ + case "run/spawn-exit" => LauncherBased // sbt/Package$ + case "source-dependencies/binary" => LauncherBased // sbt/Package$ + case "source-dependencies/export-jars" => LauncherBased // sbt/Package$ + case "source-dependencies/implicit-search" => LauncherBased // sbt/Package$ + case "source-dependencies/java-basic" => LauncherBased // sbt/Package$ + case "source-dependencies/less-inter-inv" => LauncherBased // sbt/Package$ + case "source-dependencies/less-inter-inv-java" => LauncherBased // sbt/Package$ + case "source-dependencies/linearization" => LauncherBased // sbt/Package$ + case "source-dependencies/named" => LauncherBased // sbt/Package$ + case "source-dependencies/specialized" => LauncherBased // sbt/Package$ + case _ => RunFromSourceBased + } + // sbt/Package$ means: + // java.lang.NoClassDefFoundError: sbt/Package$ (wrong name: sbt/package$) + // Typically from Compile / packageBin / packageOptions + } + /** Defines an auto plugin that is injected to sbt between every scripted session. * * It sets the name of the local root project for those tests run in batch mode. @@ -168,12 +295,13 @@ final class ScriptedTests(resourceBaseDirectory: File, groupedTests: Seq[((String, String), File)], tempTestDir: File, preHook: File => Unit, - log: Logger + remoteSbtCreatorKind: RemoteSbtCreatorKind, + log: Logger, ): Seq[Option[String]] = { val runner = new BatchScriptRunner val buffer = new BufferedLogger(new FullLogger(log)) - val handlers = createScriptedHandlers(tempTestDir, buffer) + val handlers = createScriptedHandlers(tempTestDir, buffer, remoteSbtCreatorKind) val states = new BatchScriptRunner.States val seqHandlers = handlers.values.toList runner.initStates(states, seqHandlers) From 3d0619fa3797edfcab248ec7e06cc6d59bfd0cd3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 6 Feb 2018 15:18:50 +0000 Subject: [PATCH 097/356] Add MavenCentral to RunFromSourceMain's repos --- sbt/src/test/scala/sbt/RunFromSourceMain.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala index 4ccc110ba..b369fd5a7 100644 --- a/sbt/src/test/scala/sbt/RunFromSourceMain.scala +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -64,8 +64,10 @@ object RunFromSourceMain { def globalLock = noGlobalLock def bootDirectory = appProvider.bootDirectory def ivyHome = file(sys.props("user.home")) / ".ivy2" - def ivyRepositories = Array(new PredefinedRepository { def id() = Predefined.Local }) - def appRepositories = Array(new PredefinedRepository { def id() = Predefined.Local }) + final case class PredefRepo(id: Predefined) extends PredefinedRepository + import Predefined._ + def ivyRepositories = Array(PredefRepo(Local), PredefRepo(MavenCentral)) + def appRepositories = Array(PredefRepo(Local), PredefRepo(MavenCentral)) def isOverrideRepositories = false def checksums = Array("sha1", "md5") } From 2db5c77442b62eacfc339c881bbac6489e036ccb Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 6 Feb 2018 16:02:45 +0000 Subject: [PATCH 098/356] Upgrade Scala versions that ^^ uses Refs #3907 --- main/src/main/scala/sbt/PluginCross.scala | 4 ++-- project/Dependencies.scala | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/main/src/main/scala/sbt/PluginCross.scala b/main/src/main/scala/sbt/PluginCross.scala index 0e9e1462d..9828b42a7 100644 --- a/main/src/main/scala/sbt/PluginCross.scala +++ b/main/src/main/scala/sbt/PluginCross.scala @@ -92,8 +92,8 @@ private[sbt] object PluginCross { def scalaVersionFromSbtBinaryVersion(sv: String): String = VersionNumber(sv) match { case VersionNumber(Seq(0, 12, _*), _, _) => "2.9.2" - case VersionNumber(Seq(0, 13, _*), _, _) => "2.10.6" - case VersionNumber(Seq(1, 0, _*), _, _) => "2.12.3" + case VersionNumber(Seq(0, 13, _*), _, _) => "2.10.7" + case VersionNumber(Seq(1, 0, _*), _, _) => "2.12.4" case _ => sys.error(s"Unsupported sbt binary version: $sv") } } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f8d470ccd..31e6b3c15 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -3,11 +3,7 @@ import Keys._ import sbt.contraband.ContrabandPlugin.autoImport._ object Dependencies { - val scala282 = "2.8.2" - val scala292 = "2.9.2" - val scala293 = "2.9.3" - val scala210 = "2.10.7" - val scala211 = "2.11.12" + // WARNING: Please Scala update versions in PluginCross.scala too val scala212 = "2.12.4" val baseScalaVersion = scala212 From 96b94296693a31fd4db61444b264f53bf718ee38 Mon Sep 17 00:00:00 2001 From: Steve Waldman Date: Tue, 6 Feb 2018 11:49:46 -0800 Subject: [PATCH 099/356] Rework false-defaulting 'suppressServer' to true-defaulting 'autoStartServer'. --- .../src/main/scala/sbt/BasicKeys.scala | 6 ++-- main/src/main/scala/sbt/Defaults.scala | 2 +- main/src/main/scala/sbt/Keys.scala | 2 +- main/src/main/scala/sbt/Project.scala | 6 ++-- .../scala/sbt/internal/CommandExchange.scala | 10 +++--- notes/1.1.1/autoStartServer.md | 31 +++++++++++++++++++ notes/1.1.1/suppressServer.md | 23 -------------- 7 files changed, 44 insertions(+), 36 deletions(-) create mode 100644 notes/1.1.1/autoStartServer.md delete mode 100644 notes/1.1.1/suppressServer.md diff --git a/main-command/src/main/scala/sbt/BasicKeys.scala b/main-command/src/main/scala/sbt/BasicKeys.scala index a820515db..1570d392b 100644 --- a/main-command/src/main/scala/sbt/BasicKeys.scala +++ b/main-command/src/main/scala/sbt/BasicKeys.scala @@ -39,10 +39,10 @@ object BasicKeys { "The wire protocol for the server command.", 10000) - val suppressServer = + val autoStartServer = AttributeKey[Boolean]( - "suppressServer", - "Running the server will be suppressed if 'suppressServer is explicitly set to true.", + "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, diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 35c9db19d..b9ab939ef 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -268,7 +268,7 @@ object Defaults extends BuildCommon { .getOrElse(GCUtil.defaultForceGarbageCollection), minForcegcInterval :== GCUtil.defaultMinForcegcInterval, interactionService :== CommandLineUIService, - suppressServer := false, + 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 1d32e2bb5..8e26b54fb 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -131,7 +131,7 @@ object Keys { // Command keys val historyPath = SettingKey(BasicKeys.historyPath) val shellPrompt = SettingKey(BasicKeys.shellPrompt) - val suppressServer = SettingKey(BasicKeys.suppressServer) + 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 bd9fe8b0b..f292ca0e1 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -21,7 +21,7 @@ import Keys.{ sessionSettings, shellPrompt, templateResolverInfos, - suppressServer, + autoStartServer, serverHost, serverLog, serverPort, @@ -463,7 +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 suppressSvr: Option[Boolean] = get(suppressServer) + 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) @@ -476,7 +476,7 @@ object Project extends ProjectExtra { s.attributes .setCond(Watched.Configuration, watched) .put(historyPath.key, history) - .setCond(suppressServer.key, suppressSvr) + .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 eeadfad33..0b68cae24 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -14,7 +14,7 @@ import java.util.concurrent.atomic._ import scala.collection.mutable.ListBuffer import scala.annotation.tailrec import BasicKeys.{ - suppressServer, + autoStartServer, serverHost, serverPort, serverAuthentication, @@ -44,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 {} @@ -88,11 +88,11 @@ private[sbt] final class CommandExchange { consoleChannel = Some(x) subscribe(x) } - val suppress = (s get suppressServer) match { + val autoStartServerAttr = (s get autoStartServer) match { case Some(bool) => bool - case None => false + case None => true } - if (autoStartServer && !suppress) runServer(s) + 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 + diff --git a/notes/1.1.1/suppressServer.md b/notes/1.1.1/suppressServer.md deleted file mode 100644 index 0e9fdce79..000000000 --- a/notes/1.1.1/suppressServer.md +++ /dev/null @@ -1,23 +0,0 @@ -### Improvements - -This pull request implements a Boolean setting called `suppressServer`, whose default value is `false'. - -If a build or plugin explicitly sets it to `true`, the sbt-1.x server will not start up -(exactly as if `sbt.server.autostart` were set to start). - -Users may manually override this setting by running the `startServer` command at the interactive prompt. - -### 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 `suppressServer` -to `true` in their build. Security-sensitive plugins can define this setting 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). From 11dbbd0cfa6df6e4e536a1e833ed8aa3a86eee4a Mon Sep 17 00:00:00 2001 From: Deokhwan Kim Date: Wed, 7 Feb 2018 00:27:58 -0500 Subject: [PATCH 100/356] Update documentation for skip 'skip' is also supported for 'publish' by sbt/sbt#3380. --- main/src/main/scala/sbt/Keys.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index bfcb3139e..78cc96dfd 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -445,7 +445,7 @@ object Keys { val sbtDependency = settingKey[ModuleID]("Provides a definition for declaring the current version of sbt.").withRank(BMinusSetting) val sbtVersion = settingKey[String]("Provides the version of sbt. This setting should not be modified.").withRank(AMinusSetting) val sbtBinaryVersion = settingKey[String]("Defines the binary compatibility version substring.").withRank(BPlusSetting) - val skip = taskKey[Boolean]("For tasks that support it (currently only 'compile' and 'update'), setting skip to true will force the task to not to do its work. This exact semantics may vary by task.").withRank(BSetting) + val skip = taskKey[Boolean]("For tasks that support it (currently only 'compile', 'update', and 'publish'), setting skip to true will force the task to not to do its work. This exact semantics may vary by task.").withRank(BSetting) val templateResolverInfos = settingKey[Seq[TemplateResolverInfo]]("Template resolvers used for 'new'.").withRank(BSetting) val interactionService = taskKey[InteractionService]("Service used to ask for user input through the current user interface(s).").withRank(CTask) val insideCI = SettingKey[Boolean]("insideCI", "Determines if the SBT is running in a Continuous Integration environment", AMinusSetting) From a2347332ab9271f75c2cb8a4881922d2eecbeb44 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 30 Jan 2018 00:43:30 -0500 Subject: [PATCH 101/356] Use ipcsocket --- build.sbt | 5 +- .../internal/NGUnixDomainServerSocket.java | 178 ---------------- .../java/sbt/internal/NGUnixDomainSocket.java | 192 ------------------ .../internal/NGUnixDomainSocketLibrary.java | 142 ------------- .../sbt/internal/NGWin32NamedPipeLibrary.java | 90 -------- .../NGWin32NamedPipeServerSocket.java | 173 ---------------- .../sbt/internal/NGWin32NamedPipeSocket.java | 172 ---------------- .../ReferenceCountedFileDescriptor.java | 82 -------- .../scala/sbt/internal/server/Server.scala | 7 +- project/Dependencies.scala | 1 + 10 files changed, 9 insertions(+), 1033 deletions(-) delete mode 100644 main-command/src/main/java/sbt/internal/NGUnixDomainServerSocket.java delete mode 100644 main-command/src/main/java/sbt/internal/NGUnixDomainSocket.java delete mode 100644 main-command/src/main/java/sbt/internal/NGUnixDomainSocketLibrary.java delete mode 100644 main-command/src/main/java/sbt/internal/NGWin32NamedPipeLibrary.java delete mode 100644 main-command/src/main/java/sbt/internal/NGWin32NamedPipeServerSocket.java delete mode 100644 main-command/src/main/java/sbt/internal/NGWin32NamedPipeSocket.java delete mode 100644 main-command/src/main/java/sbt/internal/ReferenceCountedFileDescriptor.java diff --git a/build.sbt b/build.sbt index a4e810d3b..17d4b477e 100644 --- a/build.sbt +++ b/build.sbt @@ -297,7 +297,7 @@ lazy val protocolProj = (project in file("protocol")) .settings( testedBaseSettings, name := "Protocol", - libraryDependencies ++= Seq(sjsonNewScalaJson.value), + libraryDependencies ++= Seq(sjsonNewScalaJson.value, ipcSocket), managedSourceDirectories in Compile += baseDirectory.value / "src" / "main" / "contraband-scala", sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", @@ -329,6 +329,9 @@ lazy val commandProj = (project in file("main-command")) exclude[ReversedMissingMethodProblem]("sbt.internal.CommandChannel.*"), // Added an overload to reboot. The overload is private[sbt]. exclude[ReversedMissingMethodProblem]("sbt.StateOps.reboot"), + // Replace nailgun socket stuff + exclude[MissingClassProblem]("sbt.internal.NG*"), + exclude[MissingClassProblem]("sbt.internal.ReferenceCountedFileDescriptor"), ), unmanagedSources in (Compile, headerCreate) := { val old = (unmanagedSources in (Compile, headerCreate)).value diff --git a/main-command/src/main/java/sbt/internal/NGUnixDomainServerSocket.java b/main-command/src/main/java/sbt/internal/NGUnixDomainServerSocket.java deleted file mode 100644 index 89d3bcf43..000000000 --- a/main-command/src/main/java/sbt/internal/NGUnixDomainServerSocket.java +++ /dev/null @@ -1,178 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGUnixDomainServerSocket.java - -/* - - Copyright 2004-2015, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketAddress; -import java.util.concurrent.atomic.AtomicInteger; - -import com.sun.jna.LastErrorException; -import com.sun.jna.ptr.IntByReference; - -/** - * Implements a {@link ServerSocket} which binds to a local Unix domain socket - * and returns instances of {@link NGUnixDomainSocket} from - * {@link #accept()}. - */ -public class NGUnixDomainServerSocket extends ServerSocket { - private static final int DEFAULT_BACKLOG = 50; - - // We use an AtomicInteger to prevent a race in this situation which - // could happen if fd were just an int: - // - // Thread 1 -> NGUnixDomainServerSocket.accept() - // -> lock this - // -> check isBound and isClosed - // -> unlock this - // -> descheduled while still in method - // Thread 2 -> NGUnixDomainServerSocket.close() - // -> lock this - // -> check isClosed - // -> NGUnixDomainSocketLibrary.close(fd) - // -> now fd is invalid - // -> unlock this - // Thread 1 -> re-scheduled while still in method - // -> NGUnixDomainSocketLibrary.accept(fd, which is invalid and maybe re-used) - // - // By using an AtomicInteger, we'll set this to -1 after it's closed, which - // will cause the accept() call above to cleanly fail instead of possibly - // being called on an unrelated fd (which may or may not fail). - private final AtomicInteger fd; - - private final int backlog; - private boolean isBound; - private boolean isClosed; - - public static class NGUnixDomainServerSocketAddress extends SocketAddress { - private final String path; - - public NGUnixDomainServerSocketAddress(String path) { - this.path = path; - } - - public String getPath() { - return path; - } - } - - /** - * Constructs an unbound Unix domain server socket. - */ - public NGUnixDomainServerSocket() throws IOException { - this(DEFAULT_BACKLOG, null); - } - - /** - * Constructs an unbound Unix domain server socket with the specified listen backlog. - */ - public NGUnixDomainServerSocket(int backlog) throws IOException { - this(backlog, null); - } - - /** - * Constructs and binds a Unix domain server socket to the specified path. - */ - public NGUnixDomainServerSocket(String path) throws IOException { - this(DEFAULT_BACKLOG, path); - } - - /** - * Constructs and binds a Unix domain server socket to the specified path - * with the specified listen backlog. - */ - public NGUnixDomainServerSocket(int backlog, String path) throws IOException { - try { - fd = new AtomicInteger( - NGUnixDomainSocketLibrary.socket( - NGUnixDomainSocketLibrary.PF_LOCAL, - NGUnixDomainSocketLibrary.SOCK_STREAM, - 0)); - this.backlog = backlog; - if (path != null) { - bind(new NGUnixDomainServerSocketAddress(path)); - } - } catch (LastErrorException e) { - throw new IOException(e); - } - } - - public synchronized void bind(SocketAddress endpoint) throws IOException { - if (!(endpoint instanceof NGUnixDomainServerSocketAddress)) { - throw new IllegalArgumentException( - "endpoint must be an instance of NGUnixDomainServerSocketAddress"); - } - if (isBound) { - throw new IllegalStateException("Socket is already bound"); - } - if (isClosed) { - throw new IllegalStateException("Socket is already closed"); - } - NGUnixDomainServerSocketAddress unEndpoint = (NGUnixDomainServerSocketAddress) endpoint; - NGUnixDomainSocketLibrary.SockaddrUn address = - new NGUnixDomainSocketLibrary.SockaddrUn(unEndpoint.getPath()); - try { - int socketFd = fd.get(); - NGUnixDomainSocketLibrary.bind(socketFd, address, address.size()); - NGUnixDomainSocketLibrary.listen(socketFd, backlog); - isBound = true; - } catch (LastErrorException e) { - throw new IOException(e); - } - } - - public Socket accept() throws IOException { - // We explicitly do not make this method synchronized, since the - // call to NGUnixDomainSocketLibrary.accept() will block - // indefinitely, causing another thread's call to close() to deadlock. - synchronized (this) { - if (!isBound) { - throw new IllegalStateException("Socket is not bound"); - } - if (isClosed) { - throw new IllegalStateException("Socket is already closed"); - } - } - try { - NGUnixDomainSocketLibrary.SockaddrUn sockaddrUn = - new NGUnixDomainSocketLibrary.SockaddrUn(); - IntByReference addressLen = new IntByReference(); - addressLen.setValue(sockaddrUn.size()); - int clientFd = NGUnixDomainSocketLibrary.accept(fd.get(), sockaddrUn, addressLen); - return new NGUnixDomainSocket(clientFd); - } catch (LastErrorException e) { - throw new IOException(e); - } - } - - public synchronized void close() throws IOException { - if (isClosed) { - throw new IllegalStateException("Socket is already closed"); - } - try { - // Ensure any pending call to accept() fails. - NGUnixDomainSocketLibrary.close(fd.getAndSet(-1)); - isClosed = true; - } catch (LastErrorException e) { - throw new IOException(e); - } - } -} diff --git a/main-command/src/main/java/sbt/internal/NGUnixDomainSocket.java b/main-command/src/main/java/sbt/internal/NGUnixDomainSocket.java deleted file mode 100644 index b70bef611..000000000 --- a/main-command/src/main/java/sbt/internal/NGUnixDomainSocket.java +++ /dev/null @@ -1,192 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGUnixDomainSocket.java - -/* - - Copyright 2004-2015, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import com.sun.jna.LastErrorException; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import java.nio.ByteBuffer; - -import java.net.Socket; - -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Implements a {@link Socket} backed by a native Unix domain socket. - * - * Instances of this class always return {@code null} for - * {@link Socket#getInetAddress()}, {@link Socket#getLocalAddress()}, - * {@link Socket#getLocalSocketAddress()}, {@link Socket#getRemoteSocketAddress()}. - */ -public class NGUnixDomainSocket extends Socket { - private final ReferenceCountedFileDescriptor fd; - private final InputStream is; - private final OutputStream os; - - public NGUnixDomainSocket(String path) throws IOException { - try { - AtomicInteger fd = new AtomicInteger( - NGUnixDomainSocketLibrary.socket( - NGUnixDomainSocketLibrary.PF_LOCAL, - NGUnixDomainSocketLibrary.SOCK_STREAM, - 0)); - NGUnixDomainSocketLibrary.SockaddrUn address = - new NGUnixDomainSocketLibrary.SockaddrUn(path); - int socketFd = fd.get(); - NGUnixDomainSocketLibrary.connect(socketFd, address, address.size()); - this.fd = new ReferenceCountedFileDescriptor(socketFd); - this.is = new NGUnixDomainSocketInputStream(); - this.os = new NGUnixDomainSocketOutputStream(); - } catch (LastErrorException e) { - throw new IOException(e); - } - } - - /** - * Creates a Unix domain socket backed by a native file descriptor. - */ - public NGUnixDomainSocket(int fd) { - this.fd = new ReferenceCountedFileDescriptor(fd); - this.is = new NGUnixDomainSocketInputStream(); - this.os = new NGUnixDomainSocketOutputStream(); - } - - public InputStream getInputStream() { - return is; - } - - public OutputStream getOutputStream() { - return os; - } - - public void shutdownInput() throws IOException { - doShutdown(NGUnixDomainSocketLibrary.SHUT_RD); - } - - public void shutdownOutput() throws IOException { - doShutdown(NGUnixDomainSocketLibrary.SHUT_WR); - } - - private void doShutdown(int how) throws IOException { - try { - int socketFd = fd.acquire(); - if (socketFd != -1) { - NGUnixDomainSocketLibrary.shutdown(socketFd, how); - } - } catch (LastErrorException e) { - throw new IOException(e); - } finally { - fd.release(); - } - } - - public void close() throws IOException { - super.close(); - try { - // This might not close the FD right away. In case we are about - // to read or write on another thread, it will delay the close - // until the read or write completes, to prevent the FD from - // being re-used for a different purpose and the other thread - // reading from a different FD. - fd.close(); - } catch (LastErrorException e) { - throw new IOException(e); - } - } - - private class NGUnixDomainSocketInputStream extends InputStream { - public int read() throws IOException { - ByteBuffer buf = ByteBuffer.allocate(1); - int result; - if (doRead(buf) == 0) { - result = -1; - } else { - // Make sure to & with 0xFF to avoid sign extension - result = 0xFF & buf.get(); - } - return result; - } - - public int read(byte[] b, int off, int len) throws IOException { - if (len == 0) { - return 0; - } - ByteBuffer buf = ByteBuffer.wrap(b, off, len); - int result = doRead(buf); - if (result == 0) { - result = -1; - } - return result; - } - - private int doRead(ByteBuffer buf) throws IOException { - try { - int fdToRead = fd.acquire(); - if (fdToRead == -1) { - return -1; - } - return NGUnixDomainSocketLibrary.read(fdToRead, buf, buf.remaining()); - } catch (LastErrorException e) { - throw new IOException(e); - } finally { - fd.release(); - } - } - } - - private class NGUnixDomainSocketOutputStream extends OutputStream { - - public void write(int b) throws IOException { - ByteBuffer buf = ByteBuffer.allocate(1); - buf.put(0, (byte) (0xFF & b)); - doWrite(buf); - } - - public void write(byte[] b, int off, int len) throws IOException { - if (len == 0) { - return; - } - ByteBuffer buf = ByteBuffer.wrap(b, off, len); - doWrite(buf); - } - - private void doWrite(ByteBuffer buf) throws IOException { - try { - int fdToWrite = fd.acquire(); - if (fdToWrite == -1) { - return; - } - int ret = NGUnixDomainSocketLibrary.write(fdToWrite, buf, buf.remaining()); - if (ret != buf.remaining()) { - // This shouldn't happen with standard blocking Unix domain sockets. - throw new IOException("Could not write " + buf.remaining() + " bytes as requested " + - "(wrote " + ret + " bytes instead)"); - } - } catch (LastErrorException e) { - throw new IOException(e); - } finally { - fd.release(); - } - } - } -} diff --git a/main-command/src/main/java/sbt/internal/NGUnixDomainSocketLibrary.java b/main-command/src/main/java/sbt/internal/NGUnixDomainSocketLibrary.java deleted file mode 100644 index 4d781b6b6..000000000 --- a/main-command/src/main/java/sbt/internal/NGUnixDomainSocketLibrary.java +++ /dev/null @@ -1,142 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGUnixDomainSocketLibrary.java - -/* - - Copyright 2004-2015, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import com.sun.jna.LastErrorException; -import com.sun.jna.Native; -import com.sun.jna.Platform; -import com.sun.jna.Structure; -import com.sun.jna.Union; -import com.sun.jna.ptr.IntByReference; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.List; - -/** - * Utility class to bridge native Unix domain socket calls to Java using JNA. - */ -public class NGUnixDomainSocketLibrary { - public static final int PF_LOCAL = 1; - public static final int AF_LOCAL = 1; - public static final int SOCK_STREAM = 1; - - public static final int SHUT_RD = 0; - public static final int SHUT_WR = 1; - - // Utility class, do not instantiate. - private NGUnixDomainSocketLibrary() { } - - // BSD platforms write a length byte at the start of struct sockaddr_un. - private static final boolean HAS_SUN_LEN = - Platform.isMac() || Platform.isFreeBSD() || Platform.isNetBSD() || - Platform.isOpenBSD() || Platform.iskFreeBSD(); - - /** - * Bridges {@code struct sockaddr_un} to and from native code. - */ - public static class SockaddrUn extends Structure implements Structure.ByReference { - /** - * On BSD platforms, the {@code sun_len} and {@code sun_family} values in - * {@code struct sockaddr_un}. - */ - public static class SunLenAndFamily extends Structure { - public byte sunLen; - public byte sunFamily; - - protected List getFieldOrder() { - return Arrays.asList(new String[] { "sunLen", "sunFamily" }); - } - } - - /** - * On BSD platforms, {@code sunLenAndFamily} will be present. - * On other platforms, only {@code sunFamily} will be present. - */ - public static class SunFamily extends Union { - public SunLenAndFamily sunLenAndFamily; - public short sunFamily; - } - - public SunFamily sunFamily = new SunFamily(); - public byte[] sunPath = new byte[104]; - - /** - * Constructs an empty {@code struct sockaddr_un}. - */ - public SockaddrUn() { - if (HAS_SUN_LEN) { - sunFamily.sunLenAndFamily = new SunLenAndFamily(); - sunFamily.setType(SunLenAndFamily.class); - } else { - sunFamily.setType(Short.TYPE); - } - allocateMemory(); - } - - /** - * Constructs a {@code struct sockaddr_un} with a path whose bytes are encoded - * using the default encoding of the platform. - */ - public SockaddrUn(String path) throws IOException { - byte[] pathBytes = path.getBytes(); - if (pathBytes.length > sunPath.length - 1) { - throw new IOException("Cannot fit name [" + path + "] in maximum unix domain socket length"); - } - System.arraycopy(pathBytes, 0, sunPath, 0, pathBytes.length); - sunPath[pathBytes.length] = (byte) 0; - if (HAS_SUN_LEN) { - int len = fieldOffset("sunPath") + pathBytes.length; - sunFamily.sunLenAndFamily = new SunLenAndFamily(); - sunFamily.sunLenAndFamily.sunLen = (byte) len; - sunFamily.sunLenAndFamily.sunFamily = AF_LOCAL; - sunFamily.setType(SunLenAndFamily.class); - } else { - sunFamily.sunFamily = AF_LOCAL; - sunFamily.setType(Short.TYPE); - } - allocateMemory(); - } - - protected List getFieldOrder() { - return Arrays.asList(new String[] { "sunFamily", "sunPath" }); - } - } - - static { - Native.register(Platform.C_LIBRARY_NAME); - } - - public static native int socket(int domain, int type, int protocol) throws LastErrorException; - public static native int bind(int fd, SockaddrUn address, int addressLen) - throws LastErrorException; - public static native int listen(int fd, int backlog) throws LastErrorException; - public static native int accept(int fd, SockaddrUn address, IntByReference addressLen) - throws LastErrorException; - public static native int connect(int fd, SockaddrUn address, int addressLen) - throws LastErrorException; - public static native int read(int fd, ByteBuffer buffer, int count) - throws LastErrorException; - public static native int write(int fd, ByteBuffer buffer, int count) - throws LastErrorException; - public static native int close(int fd) throws LastErrorException; - public static native int shutdown(int fd, int how) throws LastErrorException; -} diff --git a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeLibrary.java b/main-command/src/main/java/sbt/internal/NGWin32NamedPipeLibrary.java deleted file mode 100644 index dd4d8f15a..000000000 --- a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeLibrary.java +++ /dev/null @@ -1,90 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGWin32NamedPipeLibrary.java - -/* - - Copyright 2004-2017, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import java.nio.ByteBuffer; - -import com.sun.jna.*; -import com.sun.jna.platform.win32.WinNT; -import com.sun.jna.platform.win32.WinNT.*; -import com.sun.jna.platform.win32.WinBase.*; -import com.sun.jna.ptr.IntByReference; - -import com.sun.jna.win32.W32APIOptions; - -public interface NGWin32NamedPipeLibrary extends Library, WinNT { - int PIPE_ACCESS_DUPLEX = 3; - int PIPE_UNLIMITED_INSTANCES = 255; - int FILE_FLAG_FIRST_PIPE_INSTANCE = 524288; - - NGWin32NamedPipeLibrary INSTANCE = - (NGWin32NamedPipeLibrary) Native.loadLibrary( - "kernel32", - NGWin32NamedPipeLibrary.class, - W32APIOptions.UNICODE_OPTIONS); - - HANDLE CreateNamedPipe( - String lpName, - int dwOpenMode, - int dwPipeMode, - int nMaxInstances, - int nOutBufferSize, - int nInBufferSize, - int nDefaultTimeOut, - SECURITY_ATTRIBUTES lpSecurityAttributes); - boolean ConnectNamedPipe( - HANDLE hNamedPipe, - Pointer lpOverlapped); - boolean DisconnectNamedPipe( - HANDLE hObject); - boolean ReadFile( - HANDLE hFile, - Memory lpBuffer, - int nNumberOfBytesToRead, - IntByReference lpNumberOfBytesRead, - Pointer lpOverlapped); - boolean WriteFile( - HANDLE hFile, - ByteBuffer lpBuffer, - int nNumberOfBytesToWrite, - IntByReference lpNumberOfBytesWritten, - Pointer lpOverlapped); - boolean CloseHandle( - HANDLE hObject); - boolean GetOverlappedResult( - HANDLE hFile, - Pointer lpOverlapped, - IntByReference lpNumberOfBytesTransferred, - boolean wait); - boolean CancelIoEx( - HANDLE hObject, - Pointer lpOverlapped); - HANDLE CreateEvent( - SECURITY_ATTRIBUTES lpEventAttributes, - boolean bManualReset, - boolean bInitialState, - String lpName); - int WaitForSingleObject( - HANDLE hHandle, - int dwMilliseconds - ); - - int GetLastError(); -} diff --git a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeServerSocket.java b/main-command/src/main/java/sbt/internal/NGWin32NamedPipeServerSocket.java deleted file mode 100644 index 137d9b5dc..000000000 --- a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeServerSocket.java +++ /dev/null @@ -1,173 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGWin32NamedPipeServerSocket.java - -/* - - Copyright 2004-2017, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import com.sun.jna.platform.win32.WinBase; -import com.sun.jna.platform.win32.WinError; -import com.sun.jna.platform.win32.WinNT; -import com.sun.jna.platform.win32.WinNT.HANDLE; -import com.sun.jna.ptr.IntByReference; - -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.LinkedBlockingQueue; - -public class NGWin32NamedPipeServerSocket extends ServerSocket { - private static final NGWin32NamedPipeLibrary API = NGWin32NamedPipeLibrary.INSTANCE; - private static final String WIN32_PIPE_PREFIX = "\\\\.\\pipe\\"; - private static final int BUFFER_SIZE = 65535; - private final LinkedBlockingQueue openHandles; - private final LinkedBlockingQueue connectedHandles; - private final NGWin32NamedPipeSocket.CloseCallback closeCallback; - private final String path; - private final int maxInstances; - private final HANDLE lockHandle; - - public NGWin32NamedPipeServerSocket(String path) throws IOException { - this(NGWin32NamedPipeLibrary.PIPE_UNLIMITED_INSTANCES, path); - } - - public NGWin32NamedPipeServerSocket(int maxInstances, String path) throws IOException { - this.openHandles = new LinkedBlockingQueue<>(); - this.connectedHandles = new LinkedBlockingQueue<>(); - this.closeCallback = handle -> { - if (connectedHandles.remove(handle)) { - closeConnectedPipe(handle, false); - } - if (openHandles.remove(handle)) { - closeOpenPipe(handle); - } - }; - this.maxInstances = maxInstances; - if (!path.startsWith(WIN32_PIPE_PREFIX)) { - this.path = WIN32_PIPE_PREFIX + path; - } else { - this.path = path; - } - String lockPath = this.path + "_lock"; - lockHandle = API.CreateNamedPipe( - lockPath, - NGWin32NamedPipeLibrary.FILE_FLAG_FIRST_PIPE_INSTANCE | NGWin32NamedPipeLibrary.PIPE_ACCESS_DUPLEX, - 0, - 1, - BUFFER_SIZE, - BUFFER_SIZE, - 0, - null); - if (lockHandle == NGWin32NamedPipeLibrary.INVALID_HANDLE_VALUE) { - throw new IOException(String.format("Could not create lock for %s, error %d", lockPath, API.GetLastError())); - } else { - if (!API.DisconnectNamedPipe(lockHandle)) { - throw new IOException(String.format("Could not disconnect lock %d", API.GetLastError())); - } - } - - } - - public void bind(SocketAddress endpoint) throws IOException { - throw new IOException("Win32 named pipes do not support bind(), pass path to constructor"); - } - - public Socket accept() throws IOException { - HANDLE handle = API.CreateNamedPipe( - path, - NGWin32NamedPipeLibrary.PIPE_ACCESS_DUPLEX | WinNT.FILE_FLAG_OVERLAPPED, - 0, - maxInstances, - BUFFER_SIZE, - BUFFER_SIZE, - 0, - null); - if (handle == NGWin32NamedPipeLibrary.INVALID_HANDLE_VALUE) { - throw new IOException(String.format("Could not create named pipe, error %d", API.GetLastError())); - } - openHandles.add(handle); - - HANDLE connWaitable = API.CreateEvent(null, true, false, null); - WinBase.OVERLAPPED olap = new WinBase.OVERLAPPED(); - olap.hEvent = connWaitable; - olap.write(); - - boolean immediate = API.ConnectNamedPipe(handle, olap.getPointer()); - if (immediate) { - openHandles.remove(handle); - connectedHandles.add(handle); - return new NGWin32NamedPipeSocket(handle, closeCallback); - } - - int connectError = API.GetLastError(); - if (connectError == WinError.ERROR_PIPE_CONNECTED) { - openHandles.remove(handle); - connectedHandles.add(handle); - return new NGWin32NamedPipeSocket(handle, closeCallback); - } else if (connectError == WinError.ERROR_NO_DATA) { - // Client has connected and disconnected between CreateNamedPipe() and ConnectNamedPipe() - // connection is broken, but it is returned it avoid loop here. - // Actual error will happen for NGSession when it will try to read/write from/to pipe - return new NGWin32NamedPipeSocket(handle, closeCallback); - } else if (connectError == WinError.ERROR_IO_PENDING) { - if (!API.GetOverlappedResult(handle, olap.getPointer(), new IntByReference(), true)) { - openHandles.remove(handle); - closeOpenPipe(handle); - throw new IOException("GetOverlappedResult() failed for connect operation: " + API.GetLastError()); - } - openHandles.remove(handle); - connectedHandles.add(handle); - return new NGWin32NamedPipeSocket(handle, closeCallback); - } else { - throw new IOException("ConnectNamedPipe() failed with: " + connectError); - } - } - - public void close() throws IOException { - try { - List handlesToClose = new ArrayList<>(); - openHandles.drainTo(handlesToClose); - for (HANDLE handle : handlesToClose) { - closeOpenPipe(handle); - } - - List handlesToDisconnect = new ArrayList<>(); - connectedHandles.drainTo(handlesToDisconnect); - for (HANDLE handle : handlesToDisconnect) { - closeConnectedPipe(handle, true); - } - } finally { - API.CloseHandle(lockHandle); - } - } - - private void closeOpenPipe(HANDLE handle) throws IOException { - API.CancelIoEx(handle, null); - API.CloseHandle(handle); - } - - private void closeConnectedPipe(HANDLE handle, boolean shutdown) throws IOException { - if (!shutdown) { - API.WaitForSingleObject(handle, 10000); - } - API.DisconnectNamedPipe(handle); - API.CloseHandle(handle); - } -} diff --git a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeSocket.java b/main-command/src/main/java/sbt/internal/NGWin32NamedPipeSocket.java deleted file mode 100644 index b22bb6bbf..000000000 --- a/main-command/src/main/java/sbt/internal/NGWin32NamedPipeSocket.java +++ /dev/null @@ -1,172 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGWin32NamedPipeSocket.java -// Made change in `read` to read just the amount of bytes available. - -/* - - Copyright 2004-2017, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import com.sun.jna.Memory; -import com.sun.jna.platform.win32.WinBase; -import com.sun.jna.platform.win32.WinError; -import com.sun.jna.platform.win32.WinNT.HANDLE; -import com.sun.jna.ptr.IntByReference; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.nio.ByteBuffer; - -public class NGWin32NamedPipeSocket extends Socket { - private static final NGWin32NamedPipeLibrary API = NGWin32NamedPipeLibrary.INSTANCE; - private final HANDLE handle; - private final CloseCallback closeCallback; - private final InputStream is; - private final OutputStream os; - private final HANDLE readerWaitable; - private final HANDLE writerWaitable; - - interface CloseCallback { - void onNamedPipeSocketClose(HANDLE handle) throws IOException; - } - - public NGWin32NamedPipeSocket( - HANDLE handle, - NGWin32NamedPipeSocket.CloseCallback closeCallback) throws IOException { - this.handle = handle; - this.closeCallback = closeCallback; - this.readerWaitable = API.CreateEvent(null, true, false, null); - if (readerWaitable == null) { - throw new IOException("CreateEvent() failed "); - } - writerWaitable = API.CreateEvent(null, true, false, null); - if (writerWaitable == null) { - throw new IOException("CreateEvent() failed "); - } - this.is = new NGWin32NamedPipeSocketInputStream(handle); - this.os = new NGWin32NamedPipeSocketOutputStream(handle); - } - - @Override - public InputStream getInputStream() { - return is; - } - - @Override - public OutputStream getOutputStream() { - return os; - } - - @Override - public void close() throws IOException { - closeCallback.onNamedPipeSocketClose(handle); - } - - @Override - public void shutdownInput() throws IOException { - } - - @Override - public void shutdownOutput() throws IOException { - } - - private class NGWin32NamedPipeSocketInputStream extends InputStream { - private final HANDLE handle; - - NGWin32NamedPipeSocketInputStream(HANDLE handle) { - this.handle = handle; - } - - @Override - public int read() throws IOException { - int result; - byte[] b = new byte[1]; - if (read(b) == 0) { - result = -1; - } else { - result = 0xFF & b[0]; - } - return result; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - Memory readBuffer = new Memory(len); - - WinBase.OVERLAPPED olap = new WinBase.OVERLAPPED(); - olap.hEvent = readerWaitable; - olap.write(); - - boolean immediate = API.ReadFile(handle, readBuffer, len, null, olap.getPointer()); - if (!immediate) { - int lastError = API.GetLastError(); - if (lastError != WinError.ERROR_IO_PENDING) { - throw new IOException("ReadFile() failed: " + lastError); - } - } - - IntByReference read = new IntByReference(); - if (!API.GetOverlappedResult(handle, olap.getPointer(), read, true)) { - int lastError = API.GetLastError(); - throw new IOException("GetOverlappedResult() failed for read operation: " + lastError); - } - int actualLen = read.getValue(); - byte[] byteArray = readBuffer.getByteArray(0, actualLen); - System.arraycopy(byteArray, 0, b, off, actualLen); - return actualLen; - } - } - - private class NGWin32NamedPipeSocketOutputStream extends OutputStream { - private final HANDLE handle; - - NGWin32NamedPipeSocketOutputStream(HANDLE handle) { - this.handle = handle; - } - - @Override - public void write(int b) throws IOException { - write(new byte[]{(byte) (0xFF & b)}); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - ByteBuffer data = ByteBuffer.wrap(b, off, len); - - WinBase.OVERLAPPED olap = new WinBase.OVERLAPPED(); - olap.hEvent = writerWaitable; - olap.write(); - - boolean immediate = API.WriteFile(handle, data, len, null, olap.getPointer()); - if (!immediate) { - int lastError = API.GetLastError(); - if (lastError != WinError.ERROR_IO_PENDING) { - throw new IOException("WriteFile() failed: " + lastError); - } - } - IntByReference written = new IntByReference(); - if (!API.GetOverlappedResult(handle, olap.getPointer(), written, true)) { - int lastError = API.GetLastError(); - throw new IOException("GetOverlappedResult() failed for write operation: " + lastError); - } - if (written.getValue() != len) { - throw new IOException("WriteFile() wrote less bytes than requested"); - } - } - } -} diff --git a/main-command/src/main/java/sbt/internal/ReferenceCountedFileDescriptor.java b/main-command/src/main/java/sbt/internal/ReferenceCountedFileDescriptor.java deleted file mode 100644 index 7fb5d9d53..000000000 --- a/main-command/src/main/java/sbt/internal/ReferenceCountedFileDescriptor.java +++ /dev/null @@ -1,82 +0,0 @@ -// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/ReferenceCountedFileDescriptor.java - -/* - - Copyright 2004-2015, Martian Software, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package sbt.internal; - -import com.sun.jna.LastErrorException; - -import java.io.IOException; - -/** - * Encapsulates a file descriptor plus a reference count to ensure close requests - * only close the file descriptor once the last reference to the file descriptor - * is released. - * - * If not explicitly closed, the file descriptor will be closed when - * this object is finalized. - */ -public class ReferenceCountedFileDescriptor { - private int fd; - private int fdRefCount; - private boolean closePending; - - public ReferenceCountedFileDescriptor(int fd) { - this.fd = fd; - this.fdRefCount = 0; - this.closePending = false; - } - - protected void finalize() throws IOException { - close(); - } - - public synchronized int acquire() { - fdRefCount++; - return fd; - } - - public synchronized void release() throws IOException { - fdRefCount--; - if (fdRefCount == 0 && closePending && fd != -1) { - doClose(); - } - } - - public synchronized void close() throws IOException { - if (fd == -1 || closePending) { - return; - } - - if (fdRefCount == 0) { - doClose(); - } else { - // Another thread has the FD. We'll close it when they release the reference. - closePending = true; - } - } - - private void doClose() throws IOException { - try { - NGUnixDomainSocketLibrary.close(fd); - fd = -1; - } catch (LastErrorException e) { - throw new IOException(e); - } - } -} diff --git a/main-command/src/main/scala/sbt/internal/server/Server.scala b/main-command/src/main/scala/sbt/internal/server/Server.scala index 521c8d399..f45019fb5 100644 --- a/main-command/src/main/scala/sbt/internal/server/Server.scala +++ b/main-command/src/main/scala/sbt/internal/server/Server.scala @@ -25,6 +25,7 @@ import sjsonnew.support.scalajson.unsafe.{ Converter, CompactPrinter } import sbt.internal.protocol.codec._ import sbt.internal.util.ErrorHandling import sbt.internal.util.Util.isWindows +import org.scalasbt.ipcsocket._ private[sbt] sealed trait ServerInstance { def shutdown(): Unit @@ -57,11 +58,11 @@ private[sbt] object Server { connection.connectionType match { case ConnectionType.Local if isWindows => // Named pipe already has an exclusive lock. - addServerError(new NGWin32NamedPipeServerSocket(pipeName)) + addServerError(new Win32NamedPipeServerSocket(pipeName)) case ConnectionType.Local => - tryClient(new NGUnixDomainSocket(socketfile.getAbsolutePath)) + tryClient(new UnixDomainSocket(socketfile.getAbsolutePath)) prepareSocketfile() - addServerError(new NGUnixDomainServerSocket(socketfile.getAbsolutePath)) + addServerError(new UnixDomainServerSocket(socketfile.getAbsolutePath)) case ConnectionType.Tcp => tryClient(new Socket(InetAddress.getByName(host), port)) addServerError(new ServerSocket(port, 50, InetAddress.getByName(host))) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f8d470ccd..5ad8a4393 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -33,6 +33,7 @@ object Dependencies { val launcherInterface = "org.scala-sbt" % "launcher-interface" % "1.0.2" val rawLauncher = "org.scala-sbt" % "launcher" % "1.0.2" val testInterface = "org.scala-sbt" % "test-interface" % "1.0" + val ipcSocket = "org.scala-sbt.ipcsocket" % "ipcsocket" % "1.0.0" private val compilerInterface = "org.scala-sbt" % "compiler-interface" % zincVersion private val compilerClasspath = "org.scala-sbt" %% "zinc-classpath" % zincVersion From bd0e44c2929ef34a7c83bf5f98df711e0df0dcb6 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 24 Jan 2018 03:56:42 -0500 Subject: [PATCH 102/356] start an instance of sbt in the background --- .appveyor.yml | 2 +- .travis.yml | 2 +- build.sbt | 5 +- .../scala/sbt/protocol/ClientSocket.scala | 45 +++++ .../sbt-test/server/handshake/Client.scala | 106 ----------- sbt/src/sbt-test/server/handshake/build.sbt | 15 -- sbt/src/sbt-test/server/handshake/test | 6 - sbt/src/server-test/handshake/build.sbt | 6 + .../test/scala/sbt/RunFromSourceMain.scala | 2 +- sbt/src/test/scala/sbt/ServerSpec.scala | 179 ++++++++++++++++++ 10 files changed, 237 insertions(+), 131 deletions(-) create mode 100644 protocol/src/main/scala/sbt/protocol/ClientSocket.scala delete mode 100644 sbt/src/sbt-test/server/handshake/Client.scala delete mode 100644 sbt/src/sbt-test/server/handshake/build.sbt delete mode 100644 sbt/src/sbt-test/server/handshake/test create mode 100644 sbt/src/server-test/handshake/build.sbt create mode 100644 sbt/src/test/scala/sbt/ServerSpec.scala diff --git a/.appveyor.yml b/.appveyor.yml index a0d3292f1..7bc9e8b57 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -20,4 +20,4 @@ install: - SET PATH=C:\sbt\sbt\bin;%PATH% - SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g -Dfile.encoding=UTF8 test_script: - - sbt "scripted actions/* server/*" + - sbt "scripted actions/*" "testOnly sbt.ServerSpec" diff --git a/.travis.yml b/.travis.yml index 4d33b9c23..85db736d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ env: - SBT_CMD="scripted dependency-management/*4of4" - SBT_CMD="scripted java/* package/* reporter/* run/* project-load/*" - SBT_CMD="scripted project/*1of2" - - SBT_CMD="scripted project/*2of2 server/*" + - SBT_CMD="scripted project/*2of2" - SBT_CMD="scripted source-dependencies/*1of3" - SBT_CMD="scripted source-dependencies/*2of3" - SBT_CMD="scripted source-dependencies/*3of3" diff --git a/build.sbt b/build.sbt index 17d4b477e..0dca48aae 100644 --- a/build.sbt +++ b/build.sbt @@ -294,6 +294,7 @@ lazy val actionsProj = (project in file("main-actions")) lazy val protocolProj = (project in file("protocol")) .enablePlugins(ContrabandPlugin, JsonCodecPlugin) + .dependsOn(collectionProj) .settings( testedBaseSettings, name := "Protocol", @@ -441,7 +442,7 @@ lazy val sbtProj = (project in file("sbt")) .dependsOn(mainProj, scriptedSbtProj % "test->test") .enablePlugins(BuildInfoPlugin) .settings( - baseSettings, + testedBaseSettings, name := "sbt", normalizedName := "sbt", crossScalaVersions := Seq(baseScalaVersion), @@ -453,6 +454,8 @@ lazy val sbtProj = (project in file("sbt")) buildInfoObject in Test := "TestBuildInfo", buildInfoKeys in Test := Seq[BuildInfoKey](fullClasspath in Compile), connectInput in run in Test := true, + outputStrategy in run in Test := Some(StdoutOutput), + fork in Test := true, ) .configure(addSbtCompilerBridge) diff --git a/protocol/src/main/scala/sbt/protocol/ClientSocket.scala b/protocol/src/main/scala/sbt/protocol/ClientSocket.scala new file mode 100644 index 000000000..df155d439 --- /dev/null +++ b/protocol/src/main/scala/sbt/protocol/ClientSocket.scala @@ -0,0 +1,45 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt +package protocol + +import java.io.File +import java.net.{ Socket, URI, InetAddress } +import sjsonnew.BasicJsonProtocol +import sjsonnew.support.scalajson.unsafe.{ Parser, Converter } +import sjsonnew.shaded.scalajson.ast.unsafe.JValue +import sbt.internal.protocol.{ PortFile, TokenFile } +import sbt.internal.protocol.codec.{ PortFileFormats, TokenFileFormats } +import sbt.internal.util.Util.isWindows +import org.scalasbt.ipcsocket._ + +object ClientSocket { + private lazy val fileFormats = new BasicJsonProtocol with PortFileFormats with TokenFileFormats {} + + def socket(portfile: File): (Socket, Option[String]) = { + import fileFormats._ + val json: JValue = Parser.parseFromFile(portfile).get + val p = Converter.fromJson[PortFile](json).get + val uri = new URI(p.uri) + // println(uri) + val token = p.tokenfilePath map { tp => + val tokeFile = new File(tp) + val json: JValue = Parser.parseFromFile(tokeFile).get + val t = Converter.fromJson[TokenFile](json).get + t.token + } + val sk = uri.getScheme match { + case "local" if isWindows => + new Win32NamedPipeSocket("""\\.\pipe\""" + uri.getSchemeSpecificPart) + case "local" => new UnixDomainSocket(uri.getSchemeSpecificPart) + case "tcp" => new Socket(InetAddress.getByName(uri.getHost), uri.getPort) + case _ => sys.error(s"Unsupported uri: $uri") + } + (sk, token) + } +} diff --git a/sbt/src/sbt-test/server/handshake/Client.scala b/sbt/src/sbt-test/server/handshake/Client.scala deleted file mode 100644 index 2f41f4c18..000000000 --- a/sbt/src/sbt-test/server/handshake/Client.scala +++ /dev/null @@ -1,106 +0,0 @@ -package example - -import java.net.{ URI, Socket, InetAddress, SocketException } -import sbt.io._ -import sbt.io.syntax._ -import java.io.File -import sjsonnew.support.scalajson.unsafe.{ Parser, Converter, CompactPrinter } -import sjsonnew.shaded.scalajson.ast.unsafe.{ JValue, JObject, JString } - -object Client extends App { - val host = "127.0.0.1" - val delimiter: Byte = '\n'.toByte - - lazy val connection = getConnection - lazy val out = connection.getOutputStream - lazy val in = connection.getInputStream - - val t = getToken - val msg0 = s"""{ "type": "InitCommand", "token": "$t" }""" - - writeLine(s"Content-Length: ${ msg0.size + 2 }") - writeLine("Content-Type: application/sbt-x1") - writeLine("") - writeLine(msg0) - out.flush - - writeLine("Content-Length: 49") - writeLine("Content-Type: application/sbt-x1") - writeLine("") - // 12345678901234567890123456789012345678901234567890 - writeLine("""{ "type": "ExecCommand", "commandLine": "exit" }""") - writeLine("") - out.flush - - val baseDirectory = new File(args(0)) - IO.write(baseDirectory / "ok.txt", "ok") - - def getToken: String = { - val tokenfile = new File(getTokenFileUri) - val json: JValue = Parser.parseFromFile(tokenfile).get - json match { - case JObject(fields) => - (fields find { _.field == "token" } map { _.value }) match { - case Some(JString(value)) => value - case _ => - sys.error("json doesn't token field that is JString") - } - case _ => sys.error("json doesn't have token field") - } - } - - def getTokenFileUri: URI = { - val portfile = baseDirectory / "project" / "target" / "active.json" - val json: JValue = Parser.parseFromFile(portfile).get - json match { - case JObject(fields) => - (fields find { _.field == "tokenfileUri" } map { _.value }) match { - case Some(JString(value)) => new URI(value) - case _ => - sys.error("json doesn't tokenfile field that is JString") - } - case _ => sys.error("json doesn't have tokenfile field") - } - } - - def getPort: Int = { - val portfile = baseDirectory / "project" / "target" / "active.json" - val json: JValue = Parser.parseFromFile(portfile).get - json match { - case JObject(fields) => - (fields find { _.field == "uri" } map { _.value }) match { - case Some(JString(value)) => - val u = new URI(value) - u.getPort - case _ => - sys.error("json doesn't uri field that is JString") - } - case _ => sys.error("json doesn't have uri field") - } - } - - def getConnection: Socket = - try { - new Socket(InetAddress.getByName(host), getPort) - } catch { - case _ => - Thread.sleep(1000) - getConnection - } - - def writeLine(s: String): Unit = { - if (s != "") { - out.write(s.getBytes("UTF-8")) - } - writeEndLine - } - - def writeEndLine(): Unit = { - val retByte: Byte = '\r'.toByte - val delimiter: Byte = '\n'.toByte - - out.write(retByte.toInt) - out.write(delimiter.toInt) - out.flush - } -} diff --git a/sbt/src/sbt-test/server/handshake/build.sbt b/sbt/src/sbt-test/server/handshake/build.sbt deleted file mode 100644 index 851648f3c..000000000 --- a/sbt/src/sbt-test/server/handshake/build.sbt +++ /dev/null @@ -1,15 +0,0 @@ -lazy val runClient = taskKey[Unit]("") - -lazy val root = (project in file(".")) - .settings( - serverConnectionType in Global := ConnectionType.Tcp, - scalaVersion := "2.12.3", - serverPort in Global := 5123, - libraryDependencies += "org.scala-sbt" %% "io" % "1.0.1", - libraryDependencies += "com.eed3si9n" %% "sjson-new-scalajson" % "0.8.0", - runClient := (Def.taskDyn { - val b = baseDirectory.value - (bgRun in Compile).toTask(s""" $b""") - }).value - ) - \ No newline at end of file diff --git a/sbt/src/sbt-test/server/handshake/test b/sbt/src/sbt-test/server/handshake/test deleted file mode 100644 index 703942376..000000000 --- a/sbt/src/sbt-test/server/handshake/test +++ /dev/null @@ -1,6 +0,0 @@ -> show serverPort -> runClient - --> shell - -$ exists ok.txt diff --git a/sbt/src/server-test/handshake/build.sbt b/sbt/src/server-test/handshake/build.sbt new file mode 100644 index 000000000..192730eef --- /dev/null +++ b/sbt/src/server-test/handshake/build.sbt @@ -0,0 +1,6 @@ +lazy val root = (project in file(".")) + .settings( + Global / serverLog / logLevel := Level.Debug, + name := "handshake", + scalaVersion := "2.12.3", + ) diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala index be79cc54a..27cba48fe 100644 --- a/sbt/src/test/scala/sbt/RunFromSourceMain.scala +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -22,7 +22,7 @@ object RunFromSourceMain { // this arrangement is because Scala does not always properly optimize away // the tail recursion in a catch statement - @tailrec private def run(baseDir: File, args: Seq[String]): Unit = + @tailrec private[sbt] def run(baseDir: File, args: Seq[String]): Unit = runImpl(baseDir, args) match { case Some((baseDir, args)) => run(baseDir, args) case None => () diff --git a/sbt/src/test/scala/sbt/ServerSpec.scala b/sbt/src/test/scala/sbt/ServerSpec.scala new file mode 100644 index 000000000..648d88203 --- /dev/null +++ b/sbt/src/test/scala/sbt/ServerSpec.scala @@ -0,0 +1,179 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt + +import org.scalatest._ +import scala.concurrent._ +import java.io.{ InputStream, OutputStream } +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.{ ThreadFactory, ThreadPoolExecutor } +import sbt.protocol.ClientSocket + +class ServerSpec extends AsyncFlatSpec with Matchers { + import ServerSpec._ + + "server" should "start" in { + withBuildSocket("handshake") { (out, in, tkn) => + writeLine( + """{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "root/name" } }""", + out) + Thread.sleep(100) + val l2 = contentLength(in) + println(l2) + readLine(in) + readLine(in) + val x2 = readContentLength(in, l2) + println(x2) + assert(1 == 1) + } + } +} + +object ServerSpec { + private val serverTestBase: File = new File(".").getAbsoluteFile / "sbt" / "src" / "server-test" + private val nextThreadId = new AtomicInteger(1) + private val threadGroup = Thread.currentThread.getThreadGroup() + val readBuffer = new Array[Byte](4096) + var buffer: Vector[Byte] = Vector.empty + var bytesRead = 0 + private val delimiter: Byte = '\n'.toByte + private val RetByte = '\r'.toByte + + private val threadFactory = new ThreadFactory() { + override def newThread(runnable: Runnable): Thread = { + val thread = + new Thread(threadGroup, + runnable, + s"sbt-test-server-threads-${nextThreadId.getAndIncrement}") + // Do NOT setDaemon because then the code in TaskExit.scala in sbt will insta-kill + // the backgrounded process, at least for the case of the run task. + thread + } + } + + private val executor = new ThreadPoolExecutor( + 0, /* corePoolSize */ + 1, /* maxPoolSize, max # of servers */ + 2, + java.util.concurrent.TimeUnit.SECONDS, + /* keep alive unused threads this long (if corePoolSize < maxPoolSize) */ + new java.util.concurrent.SynchronousQueue[Runnable](), + threadFactory + ) + + def backgroundRun(baseDir: File, args: Seq[String]): Unit = { + executor.execute(new Runnable { + def run(): Unit = { + RunFromSourceMain.run(baseDir, args) + } + }) + } + + def shutdown(): Unit = executor.shutdown() + + def withBuildSocket(testBuild: String)( + f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = { + IO.withTemporaryDirectory { temp => + IO.copyDirectory(serverTestBase / testBuild, temp / testBuild) + withBuildSocket(temp / testBuild)(f) + } + } + + def sendJsonRpc(message: String, out: OutputStream): Unit = { + writeLine(s"""Content-Length: ${message.size + 2}""", out) + writeLine("", out) + writeLine(message, out) + } + + def contentLength(in: InputStream): Int = { + readLine(in) map { line => + line.drop(16).toInt + } getOrElse (0) + } + + def readLine(in: InputStream): Option[String] = { + if (buffer.isEmpty) { + val bytesRead = in.read(readBuffer) + if (bytesRead > 0) { + buffer = buffer ++ readBuffer.toVector.take(bytesRead) + } + } + val delimPos = buffer.indexOf(delimiter) + if (delimPos > 0) { + val chunk0 = buffer.take(delimPos) + buffer = buffer.drop(delimPos + 1) + // remove \r at the end of line. + if (chunk0.size > 0 && chunk0.indexOf(RetByte) == chunk0.size - 1) + Some(new String(chunk0.dropRight(1).toArray, "utf-8")) + else Some(new String(chunk0.toArray, "utf-8")) + } else None // no EOL yet, so skip this turn. + } + + def readContentLength(in: InputStream, length: Int): Option[String] = { + if (buffer.isEmpty) { + val bytesRead = in.read(readBuffer) + if (bytesRead > 0) { + buffer = buffer ++ readBuffer.toVector.take(bytesRead) + } + } + if (length <= buffer.size) { + val chunk = buffer.take(length) + buffer = buffer.drop(length) + Some(new String(chunk.toArray, "utf-8")) + } else None // have not read enough yet, so skip this turn. + } + + def writeLine(s: String, out: OutputStream): Unit = { + def writeEndLine(): Unit = { + val retByte: Byte = '\r'.toByte + val delimiter: Byte = '\n'.toByte + out.write(retByte.toInt) + out.write(delimiter.toInt) + out.flush + } + + if (s != "") { + out.write(s.getBytes("UTF-8")) + } + writeEndLine + } + + def withBuildSocket(baseDirectory: File)( + f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = { + backgroundRun(baseDirectory, Nil) + + val portfile = baseDirectory / "project" / "target" / "active.json" + + def waitForPortfile(n: Int): Unit = + if (portfile.exists) () + else { + if (n <= 0) sys.error(s"Timeout. $portfile is not found.") + else { + Thread.sleep(1000) + waitForPortfile(n - 1) + } + } + waitForPortfile(10) + val (sk, tkn) = ClientSocket.socket(portfile) + val out = sk.getOutputStream + val in = sk.getInputStream + + sendJsonRpc( + """{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { } } }""", + out) + + try { + f(out, in, tkn) + } finally { + sendJsonRpc( + """{ "jsonrpc": "2.0", "id": 9, "method": "sbt/exec", "params": { "commandLine": "exit" } }""", + out) + shutdown() + } + } +} From 5ab122c3cfcb78f21868c89a45b63acd7d4e7841 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 30 Jan 2018 00:42:02 -0500 Subject: [PATCH 103/356] Use State to pick the port file --- .../scala/sbt/internal/CommandExchange.scala | 79 +++++++++---------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index d7b52280d..bdf477510 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -131,47 +131,44 @@ private[sbt] final class CommandExchange { new NetworkChannel(name, socket, Project structure s, auth, instance, logger) subscribe(channel) } - server match { - case Some(_) => // do nothing - case None if !firstInstance.get => // there's another server - case _ => - val portfile = (new File(".")).getAbsoluteFile / "project" / "target" / "active.json" - val h = Hash.halfHashString(IO.toURI(portfile).toString) - val tokenfile = BuildPaths.getGlobalBase(s) / "server" / h / "token.json" - val socketfile = BuildPaths.getGlobalBase(s) / "server" / h / "sock" - val pipeName = "sbt-server-" + h - val connection = - ServerConnection(connectionType, - host, - port, - auth, - portfile, - tokenfile, - socketfile, - pipeName) - val x = Server.start(connection, onIncomingSocket, s.log) - - // don't throw exception when it times out - val d = "10s" - Try(Await.ready(x.ready, Duration(d))) - x.ready.value match { - case Some(Success(_)) => - // rememeber to shutdown only when the server comes up - server = Some(x) - case Some(Failure(e: AlreadyRunningException)) => - s.log.warn( - "sbt server could not start because there's another instance of sbt running on this build.") - s.log.warn("Running multiple instances is unsupported") - server = None - firstInstance.set(false) - case Some(Failure(e)) => - s.log.error(e.toString) - server = None - case None => - s.log.warn(s"sbt server could not start in $d") - server = None - firstInstance.set(false) - } + if (server.isEmpty && firstInstance.get) { + val portfile = s.baseDir / "project" / "target" / "active.json" + val h = Hash.halfHashString(IO.toURI(portfile).toString) + val tokenfile = BuildPaths.getGlobalBase(s) / "server" / h / "token.json" + val socketfile = BuildPaths.getGlobalBase(s) / "server" / h / "sock" + val pipeName = "sbt-server-" + h + val connection = ServerConnection( + connectionType, + host, + port, + auth, + portfile, + tokenfile, + socketfile, + pipeName, + ) + val serverInstance = Server.start(connection, onIncomingSocket, s.log) + // don't throw exception when it times out + val d = "10s" + Try(Await.ready(serverInstance.ready, Duration(d))) + serverInstance.ready.value match { + case Some(Success(())) => + // remember to shutdown only when the server comes up + server = Some(serverInstance) + case Some(Failure(_: AlreadyRunningException)) => + s.log.warn( + "sbt server could not start because there's another instance of sbt running on this build.") + s.log.warn("Running multiple instances is unsupported") + server = None + firstInstance.set(false) + case Some(Failure(e)) => + s.log.error(e.toString) + server = None + case None => + s.log.warn(s"sbt server could not start in $d") + server = None + firstInstance.set(false) + } } s } From 39549855067c8c6a2de30d976f1564677c3f11a8 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 7 Feb 2018 18:16:02 -0500 Subject: [PATCH 104/356] formatting --- main/src/main/scala/sbt/Extracted.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Extracted.scala b/main/src/main/scala/sbt/Extracted.scala index f507fa2c7..4ed0e0c8b 100644 --- a/main/src/main/scala/sbt/Extracted.scala +++ b/main/src/main/scala/sbt/Extracted.scala @@ -120,7 +120,9 @@ final case class Extracted(structure: BuildStructure, structure.data.get(scope, key) getOrElse sys.error( display.show(ScopedKey(scope, key)) + " is undefined.") - @deprecated("This discards session settings. Migrate to appendWithSession or appendWithoutSession.", "1.2.0") + @deprecated( + "This discards session settings. Migrate to appendWithSession or appendWithoutSession.", + "1.2.0") def append(settings: Seq[Setting[_]], state: State): State = appendWithoutSession(settings, state) From 23cdfd8369925c5640a4d77d914e07b551263d5e Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 31 Jan 2018 00:04:23 -0500 Subject: [PATCH 105/356] improve Windows build --- .appveyor.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 7bc9e8b57..ccc70ab7c 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -4,9 +4,8 @@ init: - git config --global core.autocrlf input install: - - cinst jdk8 -params 'installdir=C:\\jdk8' - - SET JAVA_HOME=C:\jdk8 - - SET PATH=C:\jdk8\bin;%PATH% + - SET JAVA_HOME=C:\Program Files\Java\jdk1.8.0 + - SET PATH=%JAVA_HOME%\bin;%PATH% - ps: | Add-Type -AssemblyName System.IO.Compression.FileSystem @@ -21,3 +20,7 @@ install: - SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g -Dfile.encoding=UTF8 test_script: - sbt "scripted actions/*" "testOnly sbt.ServerSpec" + +cache: + - '%USERPROFILE%\.ivy2\cache' + - '%USERPROFILE%\.sbt' From 639ee220dc3c641c7e608e0a03813500ef1c2d50 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 31 Jan 2018 10:20:33 +0000 Subject: [PATCH 106/356] Fix Codacy issue in ServerSpec --- sbt/src/test/scala/sbt/ServerSpec.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sbt/src/test/scala/sbt/ServerSpec.scala b/sbt/src/test/scala/sbt/ServerSpec.scala index 648d88203..9adeabcc8 100644 --- a/sbt/src/test/scala/sbt/ServerSpec.scala +++ b/sbt/src/test/scala/sbt/ServerSpec.scala @@ -108,9 +108,8 @@ object ServerSpec { val chunk0 = buffer.take(delimPos) buffer = buffer.drop(delimPos + 1) // remove \r at the end of line. - if (chunk0.size > 0 && chunk0.indexOf(RetByte) == chunk0.size - 1) - Some(new String(chunk0.dropRight(1).toArray, "utf-8")) - else Some(new String(chunk0.toArray, "utf-8")) + val chunk1 = if (chunk0.isEmpty || chunk0.last != RetByte) chunk0 else chunk0.dropRight(1) + Some(new String(chunk1.toArray, "utf-8")) } else None // no EOL yet, so skip this turn. } From 7df32cff849e4c9ef1412d979628fe20702b73d0 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 8 Feb 2018 01:06:09 -0500 Subject: [PATCH 107/356] Zinc 1.1.1, LM 1.1.3, IO 1.1.4 --- project/Dependencies.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index fd613b0a7..a8bb47140 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,10 +8,10 @@ object Dependencies { val baseScalaVersion = scala212 // sbt modules - private val ioVersion = "1.1.3" + private val ioVersion = "1.1.4" private val utilVersion = "1.1.2" - private val lmVersion = "1.1.2" - private val zincVersion = "1.1.0" + private val lmVersion = "1.1.3" + private val zincVersion = "1.1.1" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion From 8e7dfb4b203e6d14924db0604e81429ca990f3b5 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 8 Feb 2018 09:08:13 +0000 Subject: [PATCH 108/356] Handle very long socket file paths on UNIX Fixes #3930 --- .../scala/sbt/internal/CommandExchange.scala | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 69cb89477..cfc48c69a 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -9,6 +9,7 @@ package sbt package internal import java.io.IOException +import java.nio.file.Files import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic._ import scala.collection.mutable.ListBuffer @@ -23,6 +24,7 @@ import BasicKeys.{ logLevel } import java.net.Socket +import org.scalasbt.ipcsocket.UnixDomainSocketLibrary import sjsonnew.JsonFormat import sjsonnew.shaded.scalajson.ast.unsafe._ import scala.concurrent.Await @@ -32,7 +34,7 @@ import sbt.io.syntax._ import sbt.io.{ Hash, IO } import sbt.internal.server._ import sbt.internal.langserver.{ LogMessageParams, MessageType } -import sbt.internal.util.{ StringEvent, ObjectEvent, MainAppender } +import sbt.internal.util.{ StringEvent, ObjectEvent, MainAppender, Util } import sbt.internal.util.codec.JValueFormats import sbt.protocol.{ EventMessage, ExecStatusEvent } import sbt.util.{ Level, Logger, LogExchange } @@ -140,7 +142,18 @@ private[sbt] final class CommandExchange { val portfile = s.baseDir / "project" / "target" / "active.json" val h = Hash.halfHashString(IO.toURI(portfile).toString) val tokenfile = BuildPaths.getGlobalBase(s) / "server" / h / "token.json" - val socketfile = BuildPaths.getGlobalBase(s) / "server" / h / "sock" + val socketfile = { + val socketfile = BuildPaths.getGlobalBase(s) / "server" / h / "sock" + connectionType match { + case ConnectionType.Local if !Util.isWindows => + val maxSocketLength = new UnixDomainSocketLibrary.SockaddrUn().sunPath.length - 1 + if (socketfile.absolutePath.length > maxSocketLength) + Files.createTempFile("sbt-server", ".sock").toFile // assuming this is short enough.. + else + socketfile + case _ => socketfile + } + } val pipeName = "sbt-server-" + h val connection = ServerConnection( connectionType, From 402378a81e9a8d4981e04bc6d72f9fee04a90156 Mon Sep 17 00:00:00 2001 From: Dale Wijnand <344610+dwijnand@users.noreply.github.com> Date: Wed, 7 Feb 2018 09:22:42 +0000 Subject: [PATCH 109/356] Adds sbt.boot.lock sysprop to opt-out Adds sbt.boot.lock as a system property to opt-out of locking. This might be useful when running on filesystems on which the locking code path would throw an exception. Upstream issue: https://bugs.openjdk.java.net/browse/JDK-8193594 Provides a workaround for #2222, thus... Closes #2222. --- launch/src/main/input_resources/sbt/sbt.boot.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/launch/src/main/input_resources/sbt/sbt.boot.properties b/launch/src/main/input_resources/sbt/sbt.boot.properties index cd46ae8af..f13961dcf 100644 --- a/launch/src/main/input_resources/sbt/sbt.boot.properties +++ b/launch/src/main/input_resources/sbt/sbt.boot.properties @@ -22,6 +22,7 @@ [boot] directory: ${sbt.boot.directory-${sbt.global.base-${user.home}/.sbt}/boot/} + lock: ${sbt.boot.lock-true} [ivy] ivy-home: ${sbt.ivy.home-${user.home}/.ivy2/} From 74ff6aa1ceb8029d9bc9c422fb179a3c1d89f51f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 8 Feb 2018 14:16:14 +0000 Subject: [PATCH 110/356] Fix another Codacy issue in ServerSpec --- sbt/src/test/scala/sbt/ServerSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt/src/test/scala/sbt/ServerSpec.scala b/sbt/src/test/scala/sbt/ServerSpec.scala index 9adeabcc8..7ad307fd8 100644 --- a/sbt/src/test/scala/sbt/ServerSpec.scala +++ b/sbt/src/test/scala/sbt/ServerSpec.scala @@ -108,7 +108,7 @@ object ServerSpec { val chunk0 = buffer.take(delimPos) buffer = buffer.drop(delimPos + 1) // remove \r at the end of line. - val chunk1 = if (chunk0.isEmpty || chunk0.last != RetByte) chunk0 else chunk0.dropRight(1) + val chunk1 = if (chunk0.lastOption contains RetByte) chunk0.dropRight(1) else chunk0 Some(new String(chunk1.toArray, "utf-8")) } else None // no EOL yet, so skip this turn. } From 4da6a8952b8f52962dafb89e6ca572852e946b53 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 8 Feb 2018 13:08:21 -0500 Subject: [PATCH 111/356] 1.1.1 launchconfig --- src/main/conscript/scalas/launchconfig | 2 +- src/main/conscript/screpl/launchconfig | 2 +- src/main/conscript/xsbt/launchconfig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/conscript/scalas/launchconfig b/src/main/conscript/scalas/launchconfig index 98febc38c..61b9b5108 100644 --- a/src/main/conscript/scalas/launchconfig +++ b/src/main/conscript/scalas/launchconfig @@ -4,7 +4,7 @@ [app] org: ${sbt.organization-org.scala-sbt} name: sbt - version: ${sbt.version-read(sbt.version)[1.1.0]} + version: ${sbt.version-read(sbt.version)[1.1.1]} class: sbt.ScriptMain components: xsbti,extra cross-versioned: ${sbt.cross.versioned-false} diff --git a/src/main/conscript/screpl/launchconfig b/src/main/conscript/screpl/launchconfig index 17a32efb0..23a9a6a39 100644 --- a/src/main/conscript/screpl/launchconfig +++ b/src/main/conscript/screpl/launchconfig @@ -4,7 +4,7 @@ [app] org: ${sbt.organization-org.scala-sbt} name: sbt - version: ${sbt.version-read(sbt.version)[1.1.0]} + version: ${sbt.version-read(sbt.version)[1.1.1]} class: sbt.ConsoleMain components: xsbti,extra cross-versioned: ${sbt.cross.versioned-false} diff --git a/src/main/conscript/xsbt/launchconfig b/src/main/conscript/xsbt/launchconfig index 79ddd5ef7..ff2abceab 100644 --- a/src/main/conscript/xsbt/launchconfig +++ b/src/main/conscript/xsbt/launchconfig @@ -4,7 +4,7 @@ [app] org: ${sbt.organization-org.scala-sbt} name: sbt - version: ${sbt.version-read(sbt.version)[1.1.0]} + version: ${sbt.version-read(sbt.version)[1.1.1]} class: sbt.xMain components: xsbti,extra cross-versioned: ${sbt.cross.versioned-false} From 39d992717929f8d5f935b727c8ee095ed726ca1f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 8 Feb 2018 13:08:32 -0500 Subject: [PATCH 112/356] 1.1.2-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 0dca48aae..59cf7394c 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def buildLevelSettings: Seq[Setting[_]] = inThisBuild( Seq( organization := "org.scala-sbt", - version := "1.1.1-SNAPSHOT", + version := "1.1.2-SNAPSHOT", description := "sbt is an interactive build tool", bintrayOrganization := Some("sbt"), bintrayRepository := { From 15018c1971a2adfaea922c3ec82218cd713f73d9 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 9 Feb 2018 23:33:08 -0500 Subject: [PATCH 113/356] notes --- notes/1.1.1.markdown | 45 +++++++++++++++++++++++++++++ notes/1.1.1/autoStartServer.md | 31 -------------------- notes/1.1.1/console_jline.md | 13 --------- notes/1.1.1/fix-Extracted.append.md | 11 ------- 4 files changed, 45 insertions(+), 55 deletions(-) create mode 100644 notes/1.1.1.markdown delete mode 100644 notes/1.1.1/autoStartServer.md delete mode 100644 notes/1.1.1/console_jline.md delete mode 100644 notes/1.1.1/fix-Extracted.append.md diff --git a/notes/1.1.1.markdown b/notes/1.1.1.markdown new file mode 100644 index 000000000..58cba0a73 --- /dev/null +++ b/notes/1.1.1.markdown @@ -0,0 +1,45 @@ +### Fixes + +- Fixes "Modified names for (class) is empty" error. [zinc#292][zinc292] / [zinc#484][zinc484] by [@jvican][@jvican] +- Fixes tab completion in `console` while running in batch mode as `sbt console`. [#3841][3841]/[#3876][3876] by [@eed3si9n][@eed3si9n] +- Fixes file timestamp retrieval of missing files on Windows. [#3871][3871] / [io#120][io120] by [@cunei][@cunei] +- Aligns the errors thrown by file timestamp implementations. Fixes [#3894][3894] / [io#121][io121] by [@j-keck][@j-keck] +- Adds file timestamps native support for FreeBSD. [#3894][3894] / [io#124][io124] by [@cunei][@cunei] +- Fixes JDK 10 version string parsing. [launcher#209][launcher209] by [@2m][@2m] + +### Improvements + +- Deprecates `Extracted#append` in favour of `appendWithSession` or `appendWithoutSession`. [#3865][3865] by [@dwijnand][@dwijnand] +- Adds a new global `Boolean` setting called `autoStartServer`. See below. +- Upgrades Scala versions used for sbt cross building `^^`. [#3923][3923] by [@dwijnand][@dwijnand] +- Many documentation maintenance changes by [@xuwei-k][@xuwei-k] + +### autoStartServer setting + +sbt 1.1.1 adds a new global `Boolean` setting called `autoStartServer`, which is set to `true` by default. +When set to `true`, sbt shell will automatically start sbt server. Otherwise, it will not start the server until `startSever` command is issued. This could be used to opt out of server for security reasons. + +[#3922][3922] by [@swaldman][@swaldman] + + [@eed3si9n]: https://github.com/eed3si9n + [@dwijnand]: http://github.com/dwijnand + [@cunei]: https://github.com/cunei + [@jvican]: https://github.com/jvican + [@Duhemm]: https://github.com/Duhemm + [@j-keck]: https://github.com/j-keck + [@swaldman]: https://github.com/swaldman + [@xuwei-k]: https://github.com/xuwei-k + [@2m]: https://github.com/2m + [3871]: https://github.com/sbt/sbt/issues/3871 + [io120]: https://github.com/sbt/io/pull/120 + [3894]: https://github.com/sbt/sbt/issues/3894 + [io121]: https://github.com/sbt/io/pull/121 + [io124]: https://github.com/sbt/io/pull/124 + [zinc292]: https://github.com/sbt/zinc/issues/292 + [zinc484]: https://github.com/sbt/zinc/pull/484 + [3865]: https://github.com/sbt/sbt/pull/3865 + [3841]: https://github.com/sbt/sbt/issues/3841 + [3876]: https://github.com/sbt/sbt/pull/3876 + [3923]: https://github.com/sbt/sbt/pull/3923 + [3922]: https://github.com/sbt/sbt/pull/3922 + [launcher209]: https://github.com/sbt/sbt-launcher-package/pull/209 diff --git a/notes/1.1.1/autoStartServer.md b/notes/1.1.1/autoStartServer.md deleted file mode 100644 index cc00c3fbc..000000000 --- a/notes/1.1.1/autoStartServer.md +++ /dev/null @@ -1,31 +0,0 @@ -### 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 - diff --git a/notes/1.1.1/console_jline.md b/notes/1.1.1/console_jline.md deleted file mode 100644 index fd851ce16..000000000 --- a/notes/1.1.1/console_jline.md +++ /dev/null @@ -1,13 +0,0 @@ - [@eed3si9n]: https://github.com/eed3si9n - - [3841]: https://github.com/sbt/sbt/issues/3841 - [3876]: https://github.com/sbt/sbt/pull/3876 - -### Fixes with compatibility implications - -### Improvements - - -### Bug fixes - -- Fixes tab completion in `console` while running in batch mode as `sbt console`. [#3841][3841]/[#3876][3876] by [@eed3si9n][@eed3si9n] diff --git a/notes/1.1.1/fix-Extracted.append.md b/notes/1.1.1/fix-Extracted.append.md deleted file mode 100644 index 56e450391..000000000 --- a/notes/1.1.1/fix-Extracted.append.md +++ /dev/null @@ -1,11 +0,0 @@ -[@dwijnand]: https://github.com/dwijnand - -[#3865]: https://github.com/sbt/sbt/pull/3865 - -### Fixes with compatibility implications - -### Improvements - -- Deprecates `Extracted#append` in favour of `appendWithSession` or `appendWithoutSession`. [#3865][] by [@dwijnand][] - -### Bug fixes From 4e038c91ce93196efff6490e1c1182ba5e8ee9fe Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 12 Feb 2018 17:56:53 +0000 Subject: [PATCH 114/356] Introduce SBT_GLOBAL_SERVER_DIR env var to override too long paths --- .../scala/sbt/internal/server/Server.scala | 11 ++++++++-- .../scala/sbt/internal/CommandExchange.scala | 21 +++++-------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/main-command/src/main/scala/sbt/internal/server/Server.scala b/main-command/src/main/scala/sbt/internal/server/Server.scala index f45019fb5..05b86c48a 100644 --- a/main-command/src/main/scala/sbt/internal/server/Server.scala +++ b/main-command/src/main/scala/sbt/internal/server/Server.scala @@ -60,9 +60,16 @@ private[sbt] object Server { // Named pipe already has an exclusive lock. addServerError(new Win32NamedPipeServerSocket(pipeName)) case ConnectionType.Local => - tryClient(new UnixDomainSocket(socketfile.getAbsolutePath)) + val maxSocketLength = new UnixDomainSocketLibrary.SockaddrUn().sunPath.length - 1 + val path = socketfile.getAbsolutePath + if (path.length > maxSocketLength) + sys.error("socket file absolute path too long; " + + "either switch to another connection type " + + "or define a short \"SBT_GLOBAL_SERVER_DIR\" value. " + + s"Current path: ${path}") + tryClient(new UnixDomainSocket(path)) prepareSocketfile() - addServerError(new UnixDomainServerSocket(socketfile.getAbsolutePath)) + addServerError(new UnixDomainServerSocket(path)) case ConnectionType.Tcp => tryClient(new Socket(InetAddress.getByName(host), port)) addServerError(new ServerSocket(port, 50, InetAddress.getByName(host))) diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index cfc48c69a..873946340 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -9,7 +9,6 @@ package sbt package internal import java.io.IOException -import java.nio.file.Files import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic._ import scala.collection.mutable.ListBuffer @@ -24,7 +23,6 @@ import BasicKeys.{ logLevel } import java.net.Socket -import org.scalasbt.ipcsocket.UnixDomainSocketLibrary import sjsonnew.JsonFormat import sjsonnew.shaded.scalajson.ast.unsafe._ import scala.concurrent.Await @@ -34,7 +32,7 @@ import sbt.io.syntax._ import sbt.io.{ Hash, IO } import sbt.internal.server._ import sbt.internal.langserver.{ LogMessageParams, MessageType } -import sbt.internal.util.{ StringEvent, ObjectEvent, MainAppender, Util } +import sbt.internal.util.{ StringEvent, ObjectEvent, MainAppender } import sbt.internal.util.codec.JValueFormats import sbt.protocol.{ EventMessage, ExecStatusEvent } import sbt.util.{ Level, Logger, LogExchange } @@ -141,19 +139,10 @@ private[sbt] final class CommandExchange { if (server.isEmpty && firstInstance.get) { val portfile = s.baseDir / "project" / "target" / "active.json" val h = Hash.halfHashString(IO.toURI(portfile).toString) - val tokenfile = BuildPaths.getGlobalBase(s) / "server" / h / "token.json" - val socketfile = { - val socketfile = BuildPaths.getGlobalBase(s) / "server" / h / "sock" - connectionType match { - case ConnectionType.Local if !Util.isWindows => - val maxSocketLength = new UnixDomainSocketLibrary.SockaddrUn().sunPath.length - 1 - if (socketfile.absolutePath.length > maxSocketLength) - Files.createTempFile("sbt-server", ".sock").toFile // assuming this is short enough.. - else - socketfile - case _ => socketfile - } - } + val serverDir = + sys.env get "SBT_GLOBAL_SERVER_DIR" map file getOrElse BuildPaths.getGlobalBase(s) / "server" + val tokenfile = serverDir / h / "token.json" + val socketfile = serverDir / h / "sock" val pipeName = "sbt-server-" + h val connection = ServerConnection( connectionType, From f56d5b2d90095271ba963ad14c18001b79cb9b97 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Tue, 13 Feb 2018 10:00:50 +0900 Subject: [PATCH 115/356] update README. s/1.0.x/1.x/ --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dab1a3eee..1d1a8dc08 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,10 @@ sbt is a build tool for Scala, Java, and more. For general documentation, see http://www.scala-sbt.org/. -sbt 1.0.x +sbt 1.x --------- -This is the 1.0.x series of sbt. The source code of sbt is split across +This is the 1.x series of sbt. The source code of sbt is split across several Github repositories, including this one. - [sbt/io][sbt/io] hosts `sbt.io` module. From e8d1a304744ebe5602878a1d2b0310a162641d25 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 14 Feb 2018 10:53:37 +0000 Subject: [PATCH 116/356] Update mimaPreviousArtifacts/sbt.version --- build.sbt | 5 ++++- project/build.properties | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 59cf7394c..82fcd4ef6 100644 --- a/build.sbt +++ b/build.sbt @@ -77,7 +77,10 @@ def testedBaseSettings: Seq[Setting[_]] = val mimaSettings = Def settings ( mimaPreviousArtifacts := { - ((0 to 4).map(v => s"1.0.$v") ++ (0 to 0).map(v => s"1.1.$v")).map{ v => + Seq( + "1.0.0", "1.0.1", "1.0.2", "1.0.3", "1.0.4", + "1.1.0", "1.1.1", + ).map { v => organization.value % moduleName.value % v cross (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled) }.toSet } diff --git a/project/build.properties b/project/build.properties index 394cb75cf..31334bbd3 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.0.4 +sbt.version=1.1.1 From 20367938b180e5f76b7eb8dfeff6edc27d3d9d52 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 14 Feb 2018 14:38:25 +0000 Subject: [PATCH 117/356] version := "1.2.0-SNAPSHOT" --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 431491cb9..893c2c9f1 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def buildLevelSettings: Seq[Setting[_]] = inThisBuild( Seq( organization := "org.scala-sbt", - version := "1.1.2-SNAPSHOT", + version := "1.2.0-SNAPSHOT", description := "sbt is an interactive build tool", bintrayOrganization := Some("sbt"), bintrayRepository := { From be43c43783f81bcebe131c12ce2aa690458e05c7 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Sat, 17 Feb 2018 13:41:00 -0500 Subject: [PATCH 118/356] Fix typo in the 1.1.1 notes --- notes/1.1.1.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notes/1.1.1.markdown b/notes/1.1.1.markdown index 58cba0a73..2115809d3 100644 --- a/notes/1.1.1.markdown +++ b/notes/1.1.1.markdown @@ -17,7 +17,7 @@ ### autoStartServer setting sbt 1.1.1 adds a new global `Boolean` setting called `autoStartServer`, which is set to `true` by default. -When set to `true`, sbt shell will automatically start sbt server. Otherwise, it will not start the server until `startSever` command is issued. This could be used to opt out of server for security reasons. +When set to `true`, sbt shell will automatically start sbt server. Otherwise, it will not start the server until `startServer` command is issued. This could be used to opt out of server for security reasons. [#3922][3922] by [@swaldman][@swaldman] From 9370a2adf0404d4588a27d61dd88e4ca7be9ffd4 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Wed, 7 Feb 2018 16:10:48 +0100 Subject: [PATCH 119/356] Speedup Parsers.validID It turned up in profiling sessions. Previously, it used parser combinators which are somewhat slow especially when the JVM is still cold. The grammar for ID is simple enough to afford this handwritten parser. --- .../sbt/internal/util/complete/Parsers.scala | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala index b04b61127..c148095ec 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala @@ -12,15 +12,17 @@ import Parser._ import java.io.File import java.net.URI import java.lang.Character.{ - getType, - MATH_SYMBOL, - OTHER_SYMBOL, + CURRENCY_SYMBOL, DASH_PUNCTUATION, - OTHER_PUNCTUATION, + MATH_SYMBOL, MODIFIER_SYMBOL, - CURRENCY_SYMBOL + OTHER_PUNCTUATION, + OTHER_SYMBOL, + getType } +import scala.annotation.tailrec + /** Provides standard implementations of commonly useful [[Parser]]s. */ trait Parsers { @@ -313,6 +315,16 @@ object DefaultParsers extends Parsers with ParserMain { apply(p)(s).resultEmpty.isValid /** Returns `true` if `s` parses successfully according to [[ID]].*/ - def validID(s: String): Boolean = matches(ID, s) + def validID(s: String): Boolean = { + // Handwritten version of `matches(ID, s)` because validID turned up in profiling. + def isIdChar(c: Char): Boolean = Character.isLetterOrDigit(c) || (c == '_') + @tailrec def isRestIdChar(cur: Int, s: String, length: Int): Boolean = + if (cur < length) + isIdChar(s.charAt(cur)) && isRestIdChar(cur + 1, s, length) + else + true + + !s.isEmpty && Character.isLetter(s.charAt(0)) && isRestIdChar(1, s, s.length) + } } From d66d0e34a96282f0d3ff68a3e05775c1720e335e Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 19 Feb 2018 13:59:51 +0000 Subject: [PATCH 120/356] Add prop-based test for the validID re-impl --- .../src/test/scala/DefaultParsersSpec.scala | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 internal/util-complete/src/test/scala/DefaultParsersSpec.scala diff --git a/internal/util-complete/src/test/scala/DefaultParsersSpec.scala b/internal/util-complete/src/test/scala/DefaultParsersSpec.scala new file mode 100644 index 000000000..ce3a6b2e9 --- /dev/null +++ b/internal/util-complete/src/test/scala/DefaultParsersSpec.scala @@ -0,0 +1,17 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt.internal.util +package complete + +import org.scalacheck._, Prop._ + +object DefaultParsersSpec extends Properties("DefaultParsers") { + import DefaultParsers._ + + property("validID == matches(ID, s)") = forAll((s: String) => validID(s) == matches(ID, s)) +} From fc73203d0b0823038403975b7d3334665afa8350 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 19 Feb 2018 18:42:24 +0000 Subject: [PATCH 121/356] Fix validID & expand tests Make sure that we generate valid ID (according to matches(ID, s)) so that we properly test the new validID implementation. And that's what led to the bug fix. :) --- .../sbt/internal/util/complete/Parsers.scala | 2 +- .../src/test/scala/DefaultParsersSpec.scala | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala index c148095ec..1120ed173 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala @@ -317,7 +317,7 @@ object DefaultParsers extends Parsers with ParserMain { /** Returns `true` if `s` parses successfully according to [[ID]].*/ def validID(s: String): Boolean = { // Handwritten version of `matches(ID, s)` because validID turned up in profiling. - def isIdChar(c: Char): Boolean = Character.isLetterOrDigit(c) || (c == '_') + def isIdChar(c: Char): Boolean = Character.isLetterOrDigit(c) || (c == '-') || (c == '_') @tailrec def isRestIdChar(cur: Int, s: String, length: Int): Boolean = if (cur < length) isIdChar(s.charAt(cur)) && isRestIdChar(cur + 1, s, length) diff --git a/internal/util-complete/src/test/scala/DefaultParsersSpec.scala b/internal/util-complete/src/test/scala/DefaultParsersSpec.scala index ce3a6b2e9..253a4216b 100644 --- a/internal/util-complete/src/test/scala/DefaultParsersSpec.scala +++ b/internal/util-complete/src/test/scala/DefaultParsersSpec.scala @@ -8,10 +8,21 @@ package sbt.internal.util package complete -import org.scalacheck._, Prop._ +import org.scalacheck._, Gen._, Prop._ object DefaultParsersSpec extends Properties("DefaultParsers") { - import DefaultParsers._ + import DefaultParsers.{ ID, isIDChar, matches, validID } - property("validID == matches(ID, s)") = forAll((s: String) => validID(s) == matches(ID, s)) + property("∀ s ∈ String: validID(s) == matches(ID, s)") = forAll( + (s: String) => validID(s) == matches(ID, s)) + + property("∀ s ∈ genID: matches(ID, s)") = forAll(genID)(s => matches(ID, s)) + property("∀ s ∈ genID: validID(s)") = forAll(genID)(s => validID(s)) + + private val chars: Seq[Char] = Char.MinValue to Char.MaxValue + private val genID: Gen[String] = + for { + c <- oneOf(chars filter (_.isLetter)) + cs <- listOf(oneOf(chars filter isIDChar)) + } yield (c :: cs).mkString } From 72f3289b55b5b4f603a48b47fb9ba06287e88f03 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 21 Feb 2018 00:20:52 -0500 Subject: [PATCH 122/356] update CONTRIBUTING Fixes #3950 --- CONTRIBUTING.md | 161 ++++++++++++++++++++++++++++-------------------- 1 file changed, 94 insertions(+), 67 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0b6b0d37d..d37622bac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,35 +3,70 @@ [Setup]: http://www.scala-sbt.org/release/docs/Getting-Started/Setup [Issues]: https://github.com/sbt/sbt/issues [sbt-dev]: https://groups.google.com/d/forum/sbt-dev + [sbt-contrib]: https://gitter.im/sbt/sbt-contrib + [Lightbend]: https://www.lightbend.com/ [subscriptions]: https://www.lightbend.com/platform/subscription [327]: https://github.com/sbt/sbt/issues/327 + [gitter]: https://gitter.im/sbt/sbt + [documentation]: https://github.com/sbt/website + +Support +======= + +[Lightbend] sponsors sbt and encourages contributions from the active community. Enterprises can adopt it for mission critical systems with confidence because Lightbend stands behind sbt with commercial support and services. + +For community support please [ask] on StackOverflow with the tag "sbt". + +- State the problem or question clearly and provide enough context. Code examples and `build.sbt` are often useful when appropriately edited. +- There's also [Gitter sbt/sbt room][gitter], but Stackoverflow is recommended so others can benefit from the answers. + +For professional support, [Lightbend], the maintainer of Scala compiler and sbt, provides: + +- [Lightbend Subscriptions][subscriptions], which includes Expert Support +- Training +- Consulting + +How to contribute to sbt +======================== + +There are lots of ways to contribute to sbt ecosystem depending on your interests and skill level. + +- Help someone at work or online help their build problem. +- Answer StackOverflow questions. +- Create plugins that extends sbt's feature. +- Maintain and update [documentation]. +- Garden the issue tracker. +- Report issues. +- Patch the core (send pull requests to code). +- On-ramp other contributors. Issues and Pull Requests -======================== +------------------------ When you find a bug in sbt we want to hear about it. Your bug reports play an important part in making sbt more reliable and usable. Effective bug reports are more likely to be fixed. These guidelines explain how to write such reports and pull requests. -Preliminaries --------------- +### Notes about Documentation + +Documentation fixes and contributions are as much welcome as to patching the core. Visit [the website project][documentation] to learn about how to contribute. + +### Preliminaries - Make sure your sbt version is up to date. - Search [StackOverflow] and [Issues] to see whether your bug has already been reported. - Open one case for each problem. - Proceed to the next steps for details. -Where to get help and/or file a bug report ------------------------------------------- +### Where to get help and/or file a bug report -sbt project uses GitHub Issues as a publicly visible todo list. Please open a GitHub issue only when asked to do so. +sbt project uses GitHub Issues as a publicly visible todo list. Please open a GitHub issue when you are 90% sure it's an actual bug. - If you need help with sbt, please [ask] on StackOverflow with the tag "sbt" and the name of the sbt plugin if any. -- If you run into an issue, have an enhancement idea, or a general discussion, bring it up to [sbt-dev] Google Group first. +- If you have an enhancement idea, or a general discussion, bring it up to [sbt-contrib]. - If you need a faster response time, consider one of the [Lightbend subscriptions][subscriptions]. -What to report --------------- +### What to report The developers need three things from you: **steps**, **problems**, and **expectations**. @@ -81,10 +116,7 @@ Finally, thank you for taking the time to report a problem. Pull Requests ------------- -### Branch to work against - -Whether implementing a new feature, fixing a bug, or modifying documentation, please work against the latest development branch (currently, 1.0.x). -See below for instructions on building sbt from source. +See below for the branch to work against. ### Adding notes @@ -111,73 +143,80 @@ Make sure you document each commit and squash them appropriately. You can use th * Scala's documentation on [Git Hygiene](https://github.com/scala/scala/tree/v2.12.0-M3#git-hygiene) * Play's documentation on [Working with Git](https://www.playframework.com/documentation/2.4.4/WorkingWithGit#Squashing-commits) -Documentation -------------- - -Documentation fixes and contributions are as much welcome as to the source code itself. Visit [the website project](https://github.com/sbt/website) to learn about how to contribute. - Build from source ================= +### Branch to work against + +sbt uses two branches for development: + +- Development branch: `1.x` (this is also called "master") +- Stable branch: `1.$MINOR.x`, where `$MINOR` is current minor version (e.g. `1.1.x` during 1.1.x series) + +If you're working on a bug fix, it's a good idea to start with the `1.$MINOR.x` branch. Since we can always safely merge from stable to `1.x`, but not other way around. + +### Instruction to build all modules from source + 1. Install the current stable binary release of sbt (see [Setup]), which will be used to build sbt from source. 2. Get the source code. - $ git clone git://github.com/sbt/sbt.git - $ cd sbt + ``` + $ mkdir sbt-modules + $ cd sbt-modules + $ for i in sbt io util librarymanagement zinc; do \ + git clone https://github.com/sbt/$i.git && (cd $i; git checkout -b 1.1.x origin/1.1.x) + done + $ cd sbt + $ ./sbt-allsources.sh + ``` -3. The default branch is the development branch [1.0.x](https://github.com/sbt/sbt/tree/1.0.x), which contains the latest code for the next major sbt release. To build a specific release or commit, switch to the associated tag. The tag for the latest stable release is [v0.13.13](https://github.com/sbt/sbt/tree/v0.13.13): +3. To build the launcher and publish all components locally, - $ git checkout v0.13.13 + ``` + $ ./sbt-allsources.sh + > ;{../io}/publishLocal; {../util}/publishLocal; {../librarymanagement}/publishLocal; {../zinc}/publishLocal + > publishLocal + ``` - Note that sbt is always built with the previous stable release. For example, the [1.0.x](https://github.com/sbt/sbt/tree/1.0.x) branch is built with 0.13.13 and the [v0.13.13](https://github.com/sbt/sbt/tree/v0.13.13) tag is built with 0.13.12. +### Instruction to build just sbt -4. To build the launcher and publish all components locally, +If the change you are making is contained in sbt/sbt, you could publishLocal on sbt/sbt: - $ sbt - > publishLocal +``` +$ sbt +> publishLocal +``` -5. To use this locally built version of sbt, copy your stable `~/bin/sbt` script to `~/bin/xsbt` and change it to use the launcher jar at `/launch/target/sbt-launch.jar`. +### Using the locally built sbt - Directory `target` is removed by `clean` command. Second solution is using the artifact stored in the local ivy repository. +To use the locally built sbt, set the version in `build.properties` file to `1.$MINOR.$PATCH-SNAPSHOT`, or pass it in as `-Dsbt-version` property. - The launcher is located in: +``` +$ cd ../hello +$ sbt -Dsbt-version=1.1.2-SNAPSHOT +``` - $HOME/.ivy2/local/org.scala-sbt/sbt-launch/0.13.9/jars/sbt-launch.jar +### Clearing out boot and local cache - for v0.13.9 tag, or in: +When you run a locally built sbt, the JAR artifacts will be now cached under `$HOME/.sbt/boot/scala-2.12.4/org.scala-sbt/sbt/1.$MINOR.$PATCh-SNAPSHOT` directory. To clear this out run: `reboot dev` command from sbt's session of your test application. - $HOME/.ivy2/local/org.scala-sbt/sbt-launch/0.13.10-SNAPSHOT/jars/sbt-launch.jar +One drawback of `-SNAPSHOT` version is that it's slow to resolve as it tries to hit all the resolvers. You can workaround that by using a version name like `1.$MONIR.$PATCH-LOCAL1`. A non-SNAPSHOT artifacts will now be cached under `$HOME/.ivy/cache/` directory, so you need to clear that out using [sbt-dirty-money](https://github.com/sbt/sbt-dirty-money)'s `cleanLocal` task. - for the development branch. - -## Modifying sbt - -1. When developing sbt itself, run `compile` when checking compilation only. - -2. To use your modified version of sbt in a project locally, run `publishLocal`. - -3. After each `publishLocal`, clean the `~/.sbt/boot/` directory. Alternatively, if sbt is running and the launcher hasn't changed, run `reboot full` to have sbt do this for you. - -4. If a project has `project/build.properties` defined, either delete the file or change `sbt.version` to `1.0.0-SNAPSHOT`. - -## Diagnosing build failures +### Diagnosing build failures Globally included plugins can interfere building `sbt`; if you are getting errors building sbt, try disabling all globally included plugins and try again. -Running Tests -============= +### Running Tests -sbt has an extensive test suite of Unit tests and Integration tests! +sbt has a suite of unit tests and integration tests, also known as scripted tests. -Unit / Functional tests ------------------------ +#### Unit / Functional tests Various functional and unit tests are defined throughout the project. To run all of them, run `sbt test`. You can run a single test suite with `sbt testOnly` -Integration tests ------------------ +#### Integration tests Scripted integration tests reside in `sbt/src/sbt-test` and are written using the same testing infrastructure sbt plugin authors can @@ -190,23 +229,11 @@ command. To run a single test, such as the test in sbt "scripted project/global-plugin" -Please note that these tests run PAINFULLY slow if the version set in -`build.sbt` is set to SNAPSHOT, as every time the scripted test boots -up a test instance of sbt, remote mirrors are scanned for possible -updates. It is recommended that you set the version suffix to -`-devel`, as in `1.0.0-devel`. +Other notes for maintainers +--------------------------- -Building Documentation -====================== +### Publishing VS Code Extensions -The scala-sbt.org site documentation is a separate project [website](https://github.com/sbt/website). Follow [the steps in the README](https://github.com/sbt/website#scala-sbtorg) to generate the documentation. - - -Note for maintainers -==================== - -Publishing VS Code Extensions ------------------------------ https://code.visualstudio.com/docs/extensions/publish-extension From 594738bb9c21c1a4d8a4df17cda076a8770b32f1 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 21 Feb 2018 02:28:33 -0500 Subject: [PATCH 123/356] add publishLocalAllModule command --- CONTRIBUTING.md | 18 +++++++++--------- build.sbt | 23 +++++++++++++++++++++++ project/Dependencies.scala | 2 +- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d37622bac..7d12e06ca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -153,7 +153,7 @@ sbt uses two branches for development: - Development branch: `1.x` (this is also called "master") - Stable branch: `1.$MINOR.x`, where `$MINOR` is current minor version (e.g. `1.1.x` during 1.1.x series) -If you're working on a bug fix, it's a good idea to start with the `1.$MINOR.x` branch. Since we can always safely merge from stable to `1.x`, but not other way around. +If you're working on a bug fix, it's a good idea to start with the `1.$MINOR.x` branch, since we can always safely merge from stable to `1.x`, but not other way around. ### Instruction to build all modules from source @@ -170,12 +170,11 @@ If you're working on a bug fix, it's a good idea to start with the `1.$MINOR.x` $ ./sbt-allsources.sh ``` -3. To build the launcher and publish all components locally, +3. To build and publish all components locally, ``` $ ./sbt-allsources.sh - > ;{../io}/publishLocal; {../util}/publishLocal; {../librarymanagement}/publishLocal; {../zinc}/publishLocal - > publishLocal + sbt:sbtRoot> publishLocalAllModule ``` ### Instruction to build just sbt @@ -184,23 +183,24 @@ If the change you are making is contained in sbt/sbt, you could publishLocal on ``` $ sbt -> publishLocal +sbt:sbtRoot> publishLocal ``` ### Using the locally built sbt -To use the locally built sbt, set the version in `build.properties` file to `1.$MINOR.$PATCH-SNAPSHOT`, or pass it in as `-Dsbt-version` property. +To use the locally built sbt, set the version in `build.properties` file to `1.$MINOR.$PATCH-SNAPSHOT`. ``` $ cd ../hello -$ sbt -Dsbt-version=1.1.2-SNAPSHOT +$ sbt +> compile ``` ### Clearing out boot and local cache -When you run a locally built sbt, the JAR artifacts will be now cached under `$HOME/.sbt/boot/scala-2.12.4/org.scala-sbt/sbt/1.$MINOR.$PATCh-SNAPSHOT` directory. To clear this out run: `reboot dev` command from sbt's session of your test application. +When you run a locally built sbt, the JAR artifacts will be now cached under `$HOME/.sbt/boot/scala-2.12.4/org.scala-sbt/sbt/1.$MINOR.$PATCH-SNAPSHOT` directory. To clear this out run: `reboot dev` command from sbt's session of your test application. -One drawback of `-SNAPSHOT` version is that it's slow to resolve as it tries to hit all the resolvers. You can workaround that by using a version name like `1.$MONIR.$PATCH-LOCAL1`. A non-SNAPSHOT artifacts will now be cached under `$HOME/.ivy/cache/` directory, so you need to clear that out using [sbt-dirty-money](https://github.com/sbt/sbt-dirty-money)'s `cleanLocal` task. +One drawback of `-SNAPSHOT` version is that it's slow to resolve as it tries to hit all the resolvers. You can workaround that by using a version name like `1.$MINOR.$PATCH-LOCAL1`. A non-SNAPSHOT artifacts will now be cached under `$HOME/.ivy/cache/` directory, so you need to clear that out using [sbt-dirty-money](https://github.com/sbt/sbt-dirty-money)'s `cleanLocal` task. ### Diagnosing build failures diff --git a/build.sbt b/build.sbt index 82fcd4ef6..adadee53c 100644 --- a/build.sbt +++ b/build.sbt @@ -669,6 +669,29 @@ def customCommands: Seq[Setting[_]] = Seq( "reload" :: state }, + commands += Command.command("publishLocalAllModule") { state => + val extracted = Project.extract(state) + import extracted._ + val sv = get(scalaVersion) + val projs = structure.allProjectRefs + val ioOpt = projs find { case ProjectRef(_, id) => id == "ioRoot"; case _ => false } + val utilOpt = projs find { case ProjectRef(_, id) => id == "utilRoot"; case _ => false } + val lmOpt = projs find { case ProjectRef(_, id) => id == "lmRoot"; case _ => false } + val zincOpt = projs find { case ProjectRef(_, id) => id == "zincRoot"; case _ => false } + (ioOpt map { case ProjectRef(build, _) => "{" + build.toString + "}/publishLocal" }).toList ::: + (utilOpt map { case ProjectRef(build, _) => "{" + build.toString + "}/publishLocal" }).toList ::: + (lmOpt map { case ProjectRef(build, _) => "{" + build.toString + "}/publishLocal" }).toList ::: + (zincOpt map { case ProjectRef(build, _) => + val zincSv = get(scalaVersion in ProjectRef(build, "zinc")) + val csv = get(crossScalaVersions in ProjectRef(build, "compilerBridge")).toList + (csv flatMap { bridgeSv => + s"++$bridgeSv" :: ("{" + build.toString + "}compilerBridge/publishLocal") :: Nil + }) ::: + List(s"++$zincSv", "{" + build.toString + "}/publishLocal") + }).getOrElse(Nil) ::: + List(s"++$sv", "publishLocal") ::: + state + }, /** There are several complications with sbt's build. * First is the fact that interface project is a Java-only project * that uses source generator from datatype subproject in Scala 2.10.6. diff --git a/project/Dependencies.scala b/project/Dependencies.scala index a8bb47140..aa50fc3e1 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -55,7 +55,7 @@ object Dependencies { def addSbtModule(p: Project, path: Option[String], projectName: String, m: ModuleID) = path match { case Some(f) => p dependsOn ProjectRef(file(f), projectName) - case None => p settings (libraryDependencies += m) + case None => p settings (libraryDependencies += m, dependencyOverrides += m) } def addSbtIO(p: Project): Project = addSbtModule(p, sbtIoPath, "io", sbtIO) From 0e1823d7305e99c9485690c763a8fab51f81389c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 21 Feb 2018 15:08:10 -0500 Subject: [PATCH 124/356] cleanCache --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d12e06ca..190d5a0cc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -200,7 +200,7 @@ $ sbt When you run a locally built sbt, the JAR artifacts will be now cached under `$HOME/.sbt/boot/scala-2.12.4/org.scala-sbt/sbt/1.$MINOR.$PATCH-SNAPSHOT` directory. To clear this out run: `reboot dev` command from sbt's session of your test application. -One drawback of `-SNAPSHOT` version is that it's slow to resolve as it tries to hit all the resolvers. You can workaround that by using a version name like `1.$MINOR.$PATCH-LOCAL1`. A non-SNAPSHOT artifacts will now be cached under `$HOME/.ivy/cache/` directory, so you need to clear that out using [sbt-dirty-money](https://github.com/sbt/sbt-dirty-money)'s `cleanLocal` task. +One drawback of `-SNAPSHOT` version is that it's slow to resolve as it tries to hit all the resolvers. You can workaround that by using a version name like `1.$MINOR.$PATCH-LOCAL1`. A non-SNAPSHOT artifacts will now be cached under `$HOME/.ivy/cache/` directory, so you need to clear that out using [sbt-dirty-money](https://github.com/sbt/sbt-dirty-money)'s `cleanCache` task. ### Diagnosing build failures From c1e0785a1f40ce1cb68223f8273dd8dbdd1a7153 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Tue, 26 Dec 2017 16:32:29 +0900 Subject: [PATCH 125/356] delete buildinfo.BuildInfo from sbt main sbt-buildinfo plugin have `buildInfoScopedSettings(Compile)` in default. I think it is unnecessary. or we should set "buildinfoPackage in Compile" and "buildinfoObject in Compile" https://github.com/sbt/sbt-buildinfo/blob/v0.7.0/src/main/scala/sbtbuildinfo/BuildInfoPlugin.scala#L11 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index adadee53c..f859d7ee0 100644 --- a/build.sbt +++ b/build.sbt @@ -443,7 +443,6 @@ lazy val mainProj = (project in file("main")) // with the sole purpose of providing certain identifiers without qualification (with a package object) lazy val sbtProj = (project in file("sbt")) .dependsOn(mainProj, scriptedSbtProj % "test->test") - .enablePlugins(BuildInfoPlugin) .settings( testedBaseSettings, name := "sbt", @@ -453,6 +452,7 @@ lazy val sbtProj = (project in file("sbt")) javaOptions ++= Seq("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"), mimaSettings, mimaBinaryIssueFilters ++= sbtIgnoredProblems, + BuildInfoPlugin.buildInfoDefaultSettings, addBuildInfoToConfig(Test), buildInfoObject in Test := "TestBuildInfo", buildInfoKeys in Test := Seq[BuildInfoKey](fullClasspath in Compile), From c7cc52092e6379e5a98b0c239dc8dab0f389a41a Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 1 Feb 2018 13:38:00 +0000 Subject: [PATCH 126/356] Fix how fullClasspath is defined in TestBuildInfo --- build.sbt | 4 +++- sbt/src/test/scala/sbt/RunFromSourceMain.scala | 6 +----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index f859d7ee0..a3d213b6a 100644 --- a/build.sbt +++ b/build.sbt @@ -455,7 +455,9 @@ lazy val sbtProj = (project in file("sbt")) BuildInfoPlugin.buildInfoDefaultSettings, addBuildInfoToConfig(Test), buildInfoObject in Test := "TestBuildInfo", - buildInfoKeys in Test := Seq[BuildInfoKey](fullClasspath in Compile), + buildInfoKeys in Test := Seq[BuildInfoKey]( + BuildInfoKey.map(fullClasspath in Compile) { case (ident, cp) => ident -> cp.files }, + ), connectInput in run in Test := true, outputStrategy in run in Test := Some(StdoutOutput), fork in Test := true, diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala index 27cba48fe..6816e24c7 100644 --- a/sbt/src/test/scala/sbt/RunFromSourceMain.scala +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -87,11 +87,7 @@ object RunFromSourceMain { Nil ) - def mainClasspath = - buildinfo.TestBuildInfo.fullClasspath.iterator - .map(s => file(s.stripPrefix("Attributed(").stripSuffix(")"))) - .toArray - + def mainClasspath = buildinfo.TestBuildInfo.fullClasspath.toArray def loader = new java.net.URLClassLoader(mainClasspath map (_.toURI.toURL), null) def entryPoint = classOf[xMain] def mainClass = classOf[xMain] From edb828a8d7c8275163d086d4954842db8b859d38 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 22 Feb 2018 16:48:09 +0000 Subject: [PATCH 127/356] Upgrade to sbt-buildinfo 0.8.0 --- build.sbt | 3 ++- project/plugins.sbt | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index a3d213b6a..abdca46ad 100644 --- a/build.sbt +++ b/build.sbt @@ -456,7 +456,8 @@ lazy val sbtProj = (project in file("sbt")) addBuildInfoToConfig(Test), buildInfoObject in Test := "TestBuildInfo", buildInfoKeys in Test := Seq[BuildInfoKey]( - BuildInfoKey.map(fullClasspath in Compile) { case (ident, cp) => ident -> cp.files }, + // WORKAROUND https://github.com/sbt/sbt-buildinfo/issues/117 + BuildInfoKey.map((fullClasspath in Compile).taskValue) { case (ident, cp) => ident -> cp.files }, ), connectInput in run in Test := true, outputStrategy in run in Test := Some(StdoutOutput), diff --git a/project/plugins.sbt b/project/plugins.sbt index 7ea70f8ea..8988c3d05 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ scalaVersion := "2.12.4" scalacOptions ++= Seq("-feature", "-language:postfixOps") -addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.5") -addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.2") -addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") -addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0") +addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.5") +addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.2") +addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.8.0") From 81827414806894e6a2f615c9630506dfe7478869 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 26 Jan 2018 15:29:15 +0000 Subject: [PATCH 128/356] Cleanup generateToolboxClasspath --- build.sbt | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/build.sbt b/build.sbt index abdca46ad..1c932281c 100644 --- a/build.sbt +++ b/build.sbt @@ -364,21 +364,20 @@ lazy val coreMacrosProj = (project in file("core-macros")) /* Write all the compile-time dependencies of the spores macro to a file, * in order to read it from the created Toolbox to run the neg tests. */ lazy val generateToolboxClasspath = Def.task { - val classpathAttributes = (dependencyClasspath in Compile).value - val dependenciesClasspath = - classpathAttributes.map(_.data.getAbsolutePath).mkString(":") - val scalaBinVersion = (scalaBinaryVersion in Compile).value - val targetDir = (target in Compile).value - val compiledClassesDir = targetDir / s"scala-$scalaBinVersion/classes" - val testClassesDir = targetDir / s"scala-$scalaBinVersion/test-classes" - val classpath = s"$compiledClassesDir:$testClassesDir:$dependenciesClasspath" + val mainClassesDir = (classDirectory in Compile).value + val testClassesDir = (classDirectory in Test).value + val depsClasspath = (dependencyClasspath in Compile).value + + val classpath = mainClassesDir +: testClassesDir +: (Attributed data depsClasspath) mkString ":" + val resourceDir = (resourceDirectory in Compile).value - resourceDir.mkdir() // In case it doesn't exist val toolboxTestClasspath = resourceDir / "toolbox.classpath" + IO.write(toolboxTestClasspath, classpath) - val result = List(toolboxTestClasspath.getAbsoluteFile) + streams.value.log.success("Wrote the classpath for the macro neg test suite.") - result + + List(toolboxTestClasspath) } // Fixes scope=Scope for Setting (core defined in collectionProj) to define the settings system used in build definitions @@ -387,7 +386,7 @@ lazy val mainSettingsProj = (project in file("main-settings")) .settings( testedBaseSettings, name := "Main Settings", - resourceGenerators in Compile += generateToolboxClasspath.taskValue, + resourceGenerators in Test += generateToolboxClasspath.taskValue, mimaSettings, mimaBinaryIssueFilters ++= Seq( exclude[DirectMissingMethodProblem]("sbt.Scope.display012StyleMasked"), From 27fe5a6957b751bc0d5d6d311f2f8b1fd382e4e0 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 26 Jan 2018 16:02:30 +0000 Subject: [PATCH 129/356] Re-write toolboxClasspath to use sbt-buildinfo --- .gitignore | 1 - build.sbt | 29 ++++++------------- .../src/test/scala/sbt/std/TestUtil.scala | 8 ++--- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 3996caad8..42de74605 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ target/ __pycache__ -toolbox.classpath out node_modules vscode-sbt-scala/client/server diff --git a/build.sbt b/build.sbt index 1c932281c..bb6df2c9d 100644 --- a/build.sbt +++ b/build.sbt @@ -361,32 +361,21 @@ lazy val coreMacrosProj = (project in file("core-macros")) mimaSettings, ) -/* Write all the compile-time dependencies of the spores macro to a file, - * in order to read it from the created Toolbox to run the neg tests. */ -lazy val generateToolboxClasspath = Def.task { - val mainClassesDir = (classDirectory in Compile).value - val testClassesDir = (classDirectory in Test).value - val depsClasspath = (dependencyClasspath in Compile).value - - val classpath = mainClassesDir +: testClassesDir +: (Attributed data depsClasspath) mkString ":" - - val resourceDir = (resourceDirectory in Compile).value - val toolboxTestClasspath = resourceDir / "toolbox.classpath" - - IO.write(toolboxTestClasspath, classpath) - - streams.value.log.success("Wrote the classpath for the macro neg test suite.") - - List(toolboxTestClasspath) -} - // Fixes scope=Scope for Setting (core defined in collectionProj) to define the settings system used in build definitions lazy val mainSettingsProj = (project in file("main-settings")) .dependsOn(completeProj, commandProj, stdTaskProj, coreMacrosProj) .settings( testedBaseSettings, name := "Main Settings", - resourceGenerators in Test += generateToolboxClasspath.taskValue, + BuildInfoPlugin.buildInfoDefaultSettings, + addBuildInfoToConfig(Test), + buildInfoObject in Test := "TestBuildInfo", + buildInfoKeys in Test := Seq[BuildInfoKey]( + classDirectory in Compile, + classDirectory in Test, + // WORKAROUND https://github.com/sbt/sbt-buildinfo/issues/117 + BuildInfoKey.map((dependencyClasspath in Compile).taskValue) { case (ident, cp) => ident -> cp.files }, + ), mimaSettings, mimaBinaryIssueFilters ++= Seq( exclude[DirectMissingMethodProblem]("sbt.Scope.display012StyleMasked"), diff --git a/main-settings/src/test/scala/sbt/std/TestUtil.scala b/main-settings/src/test/scala/sbt/std/TestUtil.scala index ed6f15530..df02fb29c 100644 --- a/main-settings/src/test/scala/sbt/std/TestUtil.scala +++ b/main-settings/src/test/scala/sbt/std/TestUtil.scala @@ -24,9 +24,9 @@ object TestUtil { } lazy val toolboxClasspath: String = { - val resource = getClass.getClassLoader.getResource("toolbox.classpath") - val classpathFile = scala.io.Source.fromFile(resource.toURI) - val completeSporesCoreClasspath = classpathFile.getLines.mkString - completeSporesCoreClasspath + val mainClassesDir = buildinfo.TestBuildInfo.classDirectory + val testClassesDir = buildinfo.TestBuildInfo.test_classDirectory + val depsClasspath = buildinfo.TestBuildInfo.dependencyClasspath + mainClassesDir +: testClassesDir +: depsClasspath mkString ":" } } From b0f52510e0c31bb324439903563ec202e2b849f2 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 26 Feb 2018 11:29:31 +1000 Subject: [PATCH 130/356] Use Java's redirectInput rather than sys.process's connectInput Fixes #3737 --- run/src/main/scala/sbt/Fork.scala | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/run/src/main/scala/sbt/Fork.scala b/run/src/main/scala/sbt/Fork.scala index 7058e3a31..74b70d024 100644 --- a/run/src/main/scala/sbt/Fork.scala +++ b/run/src/main/scala/sbt/Fork.scala @@ -8,10 +8,14 @@ package sbt import java.io.File +import java.lang.ProcessBuilder.Redirect + import scala.sys.process.Process import OutputStrategy._ import sbt.internal.util.Util +import java.lang.{ ProcessBuilder => JProcessBuilder } + /** * Represents a command that can be forked. * @@ -45,13 +49,19 @@ final class Fork(val commandName: String, val runnerClass: Option[String]) { (classpathEnv map { value => Fork.ClasspathEnvKey -> value }) - val process = Process(command, workingDirectory, environment.toList: _*) + val jpb = 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) match { - case StdoutOutput => process.run(connectInput) - case out: BufferedOutput => out.logger.buffer { process.run(out.logger, connectInput) } - case out: LoggedOutput => process.run(out.logger, connectInput) - case out: CustomOutput => (process #> out.output).run(connectInput) + 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[this] def makeOptions(jvmOptions: Seq[String], From 36438d2ac31d0e89348dafd21f9e924553a6cbe7 Mon Sep 17 00:00:00 2001 From: exoego Date: Thu, 15 Feb 2018 22:33:11 +0900 Subject: [PATCH 131/356] Add eviction warnings options to global --- main/src/main/scala/sbt/Defaults.scala | 3 ++- notes/1.2.0/global-eviction-warnin-options.md | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 notes/1.2.0/global-eviction-warnin-options.md diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index aba681b1a..2ab1b4259 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1753,6 +1753,7 @@ object Classpaths { Defaults.globalDefaults( Seq( conflictWarning :== ConflictWarning.default("global"), + evictionWarningOptions := EvictionWarningOptions.default, compatibilityWarningOptions :== CompatibilityWarningOptions.default, homepage :== None, startYear :== None, @@ -1988,7 +1989,6 @@ object Classpaths { val suffix = if (crossPaths.value) s"_$binVersion" else "" s"update_cache$suffix" }, - evictionWarningOptions in update := EvictionWarningOptions.default, dependencyPositions := dependencyPositionsTask.value, unresolvedWarningConfiguration in update := UnresolvedWarningConfiguration( dependencyPositions.value), @@ -1999,6 +1999,7 @@ object Classpaths { ConflictWarning(conflictWarning.value, report, log) report }, + evictionWarningOptions in update := (evictionWarningOptions in GlobalScope).value, evictionWarningOptions in evicted := EvictionWarningOptions.full, evicted := { import ShowLines._ diff --git a/notes/1.2.0/global-eviction-warnin-options.md b/notes/1.2.0/global-eviction-warnin-options.md new file mode 100644 index 000000000..981bc2477 --- /dev/null +++ b/notes/1.2.0/global-eviction-warnin-options.md @@ -0,0 +1,3 @@ +### Improvements + +- Add the eviction warning options to global, so that one can change the options for all sub projects at a time. From 0bea7e9e182329f888d7c9b2f5ce8c3fc034defe Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 5 Mar 2018 18:06:46 +1000 Subject: [PATCH 132/356] Fix race condition in non-forked, parallel tests. Non forked tests that are run in parallel groups can call into a single instance of TestStatusReporter concurrently. This seems to be limited to the startGroup/endGroup/testEvent methods called in: https://github.com/sbt/sbt/blob/a41727fb17bb923036f90ca2ca62897def1a893e/testing/src/main/scala/sbt/TestFramework.scala#L100-L124 Which itself is called within: https://github.com/sbt/sbt/blob/a41727fb17bb923036f90ca2ca62897def1a893e/testing/src/main/scala/sbt/TestFramework.scala#L203-L229 Creating the `runnables` that are run in parallel (in builds so configured): https://github.com/sbt/sbt/blob/a6eb1260c8162bfbfcbfb8656f152854a93d33ae/main-actions/src/main/scala/sbt/Tests.scala#L222-L230 We believe this to be the cause of the hang witnessed in the a suite of Scalacheck-framework tests in the Scala build: https://github.com/scala/scala-jenkins-infra/issues/249 This commit uses a concurrent map to support concurrent status updates. --- .../main/scala/sbt/internal/server/Server.scala | 3 ++- .../src/main/scala/sbt/TestStatusReporter.scala | 17 +++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/main-command/src/main/scala/sbt/internal/server/Server.scala b/main-command/src/main/scala/sbt/internal/server/Server.scala index 05b86c48a..c2bccb886 100644 --- a/main-command/src/main/scala/sbt/internal/server/Server.scala +++ b/main-command/src/main/scala/sbt/internal/server/Server.scala @@ -63,7 +63,8 @@ private[sbt] object Server { val maxSocketLength = new UnixDomainSocketLibrary.SockaddrUn().sunPath.length - 1 val path = socketfile.getAbsolutePath if (path.length > maxSocketLength) - sys.error("socket file absolute path too long; " + + sys.error( + "socket file absolute path too long; " + "either switch to another connection type " + "or define a short \"SBT_GLOBAL_SERVER_DIR\" value. " + s"Current path: ${path}") diff --git a/testing/src/main/scala/sbt/TestStatusReporter.scala b/testing/src/main/scala/sbt/TestStatusReporter.scala index 732e3257c..a99d084fe 100644 --- a/testing/src/main/scala/sbt/TestStatusReporter.scala +++ b/testing/src/main/scala/sbt/TestStatusReporter.scala @@ -8,14 +8,16 @@ package sbt import java.io.File -import sbt.io.IO -import scala.collection.mutable.Map +import sbt.io.IO import sbt.protocol.testing.TestResult +import java.util.concurrent.ConcurrentHashMap + +import scala.collection.concurrent // Assumes exclusive ownership of the file. private[sbt] class TestStatusReporter(f: File) extends TestsListener { - private lazy val succeeded = TestStatus.read(f) + private lazy val succeeded: concurrent.Map[String, Long] = TestStatus.read(f) def doInit = () def startGroup(name: String): Unit = { succeeded remove name } @@ -32,13 +34,16 @@ private[sbt] class TestStatusReporter(f: File) extends TestsListener { private[sbt] object TestStatus { import java.util.Properties - def read(f: File): Map[String, Long] = { + def read(f: File): concurrent.Map[String, Long] = { import scala.collection.JavaConverters._ val properties = new Properties IO.load(properties, f) - properties.asScala map { case (k, v) => (k, v.toLong) } + val result = new ConcurrentHashMap[String, Long]() + properties.asScala.iterator.foreach { case (k, v) => result.put(k, v.toLong) } + result.asScala } - def write(map: Map[String, Long], label: String, f: File): Unit = { + + def write(map: collection.Map[String, Long], label: String, f: File): Unit = { val properties = new Properties for ((test, lastSuccessTime) <- map) properties.setProperty(test, lastSuccessTime.toString) From 3921c45563461ada21d7a8c84e14885715b294f0 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 6 Mar 2018 10:37:42 +0000 Subject: [PATCH 133/356] Document RichInitX classes --- main-settings/src/main/scala/sbt/Structure.scala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/main-settings/src/main/scala/sbt/Structure.scala b/main-settings/src/main/scala/sbt/Structure.scala index 72cc84b63..36955839d 100644 --- a/main-settings/src/main/scala/sbt/Structure.scala +++ b/main-settings/src/main/scala/sbt/Structure.scala @@ -338,6 +338,11 @@ object Scoped { (this.? zipWith i)((x, y) => (x, y) map { case (a, b) => a getOrElse b }) } + /** Enriches `Initialize[Task[S]]` types. + * + * @param i the original `Initialize[Task[S]]` value to enrich + * @tparam S the type of the underlying value + */ final class RichInitializeTask[S](i: Initialize[Task[S]]) extends RichInitTaskBase[S, Task] { protected def onTask[T](f: Task[S] => Task[T]): Initialize[Task[T]] = i apply f @@ -367,8 +372,14 @@ object Scoped { } } + /** Enriches `Initialize[InputTask[S]]` types. + * + * @param i the original `Initialize[InputTask[S]]` value to enrich + * @tparam S the type of the underlying value + */ final class RichInitializeInputTask[S](i: Initialize[InputTask[S]]) extends RichInitTaskBase[S, InputTask] { + protected def onTask[T](f: Task[S] => Task[T]): Initialize[InputTask[T]] = i(_ mapTask f) def dependsOn(tasks: AnyInitTask*): Initialize[InputTask[S]] = { @@ -378,6 +389,11 @@ object Scoped { } } + /** Enriches `Initialize[R[S]]` types. Abstracts over the specific task-like type constructor. + * + * @tparam S the type of the underlying vault + * @tparam R the task-like type constructor (either Task or InputTask) + */ sealed abstract class RichInitTaskBase[S, R[_]] { protected def onTask[T](f: Task[S] => Task[T]): Initialize[R[T]] From 77abb9ee292a785095285faf6c3eea6c3402e1e7 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 27 Jan 2018 09:40:42 +0000 Subject: [PATCH 134/356] Cleanup InputConvert --- .../src/main/scala/sbt/std/InputConvert.scala | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/main-settings/src/main/scala/sbt/std/InputConvert.scala b/main-settings/src/main/scala/sbt/std/InputConvert.scala index 3437af145..f7d581946 100644 --- a/main-settings/src/main/scala/sbt/std/InputConvert.scala +++ b/main-settings/src/main/scala/sbt/std/InputConvert.scala @@ -8,11 +8,11 @@ package sbt package std -import reflect.macros._ +import scala.reflect.macros._ -import Def.Initialize import sbt.internal.util.complete.Parser import sbt.internal.util.appmacro.{ Convert, Converted } +import Def.Initialize object InputInitConvert extends Convert { def apply[T: c.WeakTypeTag](c: blackbox.Context)(nme: String, in: c.Tree): Converted[c.type] = @@ -46,14 +46,13 @@ object TaskConvert extends Convert { /** Converts an input `Tree` of type `Initialize[T]`, `Initialize[Task[T]]`, or `Task[T]` into a `Tree` of type `Initialize[Task[T]]`.*/ object FullConvert extends Convert { - import InputWrapper._ def apply[T: c.WeakTypeTag](c: blackbox.Context)(nme: String, in: c.Tree): Converted[c.type] = nme match { - case WrapInitTaskName => Converted.Success[c.type](in) - case WrapPreviousName => Converted.Success[c.type](in) - case WrapInitName => wrapInit[T](c)(in) - case WrapTaskName => wrapTask[T](c)(in) - case _ => Converted.NotApplicable[c.type] + case InputWrapper.WrapInitTaskName => Converted.Success[c.type](in) + case InputWrapper.WrapPreviousName => Converted.Success[c.type](in) + case InputWrapper.WrapInitName => wrapInit[T](c)(in) + case InputWrapper.WrapTaskName => wrapTask[T](c)(in) + case _ => Converted.NotApplicable[c.type] } private def wrapInit[T: c.WeakTypeTag](c: blackbox.Context)(tree: c.Tree): Converted[c.type] = { From a73aa97b2cce19f3cd68aca0ee7f4e0bdd43d881 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 27 Jan 2018 09:40:47 +0000 Subject: [PATCH 135/356] Cleanup Extracted --- main/src/main/scala/sbt/Extracted.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/Extracted.scala b/main/src/main/scala/sbt/Extracted.scala index 1cf02b8b7..c35b6021d 100644 --- a/main/src/main/scala/sbt/Extracted.scala +++ b/main/src/main/scala/sbt/Extracted.scala @@ -8,7 +8,6 @@ package sbt import sbt.internal.{ Load, BuildStructure, Act, Aggregation, SessionSettings } -import Project._ import Scope.GlobalScope import Def.{ ScopedKey, Setting } import sbt.internal.util.complete.Parser @@ -43,7 +42,7 @@ final case class Extracted(structure: BuildStructure, structure.data.get(inCurrent(key.scope), key.key) private[this] def inCurrent[T](scope: Scope): Scope = - if (scope.project == This) scope.copy(project = Select(currentRef)) else scope + if (scope.project == This) scope in currentRef else scope /** * Runs the task specified by `key` and returns the transformed State and the resulting value of the task. From d26085155d0ce7acb82981e1fd975277eb04fcd6 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 29 Jan 2018 11:31:27 +0000 Subject: [PATCH 136/356] Cleanup InputWrapper imports --- main-settings/src/main/scala/sbt/std/InputWrapper.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/main-settings/src/main/scala/sbt/std/InputWrapper.scala b/main-settings/src/main/scala/sbt/std/InputWrapper.scala index fb6b6bf70..be34721ae 100644 --- a/main-settings/src/main/scala/sbt/std/InputWrapper.scala +++ b/main-settings/src/main/scala/sbt/std/InputWrapper.scala @@ -8,9 +8,10 @@ package sbt package std -import language.experimental.macros -import reflect.macros._ -import reflect.internal.annotations.compileTimeOnly +import scala.language.experimental.macros + +import scala.annotation.compileTimeOnly +import scala.reflect.macros._ import Def.Initialize import sbt.internal.util.appmacro.ContextUtil From b9e3d6aa2d49499fb9c0d11dc82d8a8b036a66c4 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 17 Jan 2018 15:18:37 +0000 Subject: [PATCH 137/356] Define local Scripted plugin keys in Global scope --- build.sbt | 1 - project/Scripted.scala | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index d1e5d425a..910f9dbec 100644 --- a/build.sbt +++ b/build.sbt @@ -624,7 +624,6 @@ def otherRootSettings = aggregate in bintrayRelease := false ) ++ inConfig(Scripted.RepoOverrideTest)( Seq( - scriptedPrescripted := (_ => ()), scriptedLaunchOpts := List( "-Xmx1500M", "-Xms512M", diff --git a/project/Scripted.scala b/project/Scripted.scala index f584dc3e4..3d2d53528 100644 --- a/project/Scripted.scala +++ b/project/Scripted.scala @@ -13,11 +13,10 @@ object ScriptedPlugin extends sbt.AutoPlugin { } import autoImport._ - import Scripted._ - override def projectSettings = Seq( + + override def globalSettings = super.globalSettings ++ Seq( scriptedBufferLog := true, - scriptedPrescripted := { _ => - } + scriptedPrescripted := { _ => }, ) } From 41249f707a808e17a772a49697939e6d337ea183 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 17 Jan 2018 15:19:02 +0000 Subject: [PATCH 138/356] Define local Scripted plugin keys with macros --- project/Scripted.scala | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/project/Scripted.scala b/project/Scripted.scala index 3d2d53528..6991003a7 100644 --- a/project/Scripted.scala +++ b/project/Scripted.scala @@ -21,18 +21,15 @@ object ScriptedPlugin extends sbt.AutoPlugin { } trait ScriptedKeys { - lazy val publishAll = TaskKey[Unit]("publish-all") - lazy val publishLocalBinAll = taskKey[Unit]("") - lazy val scripted = InputKey[Unit]("scripted") - lazy val scriptedUnpublished = InputKey[Unit]( - "scripted-unpublished", - "Execute scripted without publishing SBT first. Saves you some time when only your test has changed.") - lazy val scriptedSource = SettingKey[File]("scripted-source") - lazy val scriptedPrescripted = TaskKey[File => Unit]("scripted-prescripted") - lazy val scriptedBufferLog = SettingKey[Boolean]("scripted-buffer-log") - lazy val scriptedLaunchOpts = SettingKey[Seq[String]]( - "scripted-launch-opts", - "options to pass to jvm launching scripted tasks") + val publishAll = taskKey[Unit]("") + val publishLocalBinAll = taskKey[Unit]("") + val scripted = inputKey[Unit]("") + val scriptedUnpublished = inputKey[Unit]("Execute scripted without publishing sbt first. " + + "Saves you some time when only your test has changed") + val scriptedSource = settingKey[File]("") + val scriptedPrescripted = taskKey[File => Unit]("") + val scriptedBufferLog = settingKey[Boolean]("") + val scriptedLaunchOpts = settingKey[Seq[String]]("options to pass to jvm launching scripted tasks") } object Scripted { From 8187a51d315f67084034e4d27a25e98fdc9c6a0e Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 24 Jan 2018 13:42:18 +0000 Subject: [PATCH 139/356] Cleanup local Scripted plugin --- build.sbt | 6 +-- project/Scripted.scala | 98 ++++++++++++++++++++++++------------------ 2 files changed, 58 insertions(+), 46 deletions(-) diff --git a/build.sbt b/build.sbt index 910f9dbec..fbfc0ce51 100644 --- a/build.sbt +++ b/build.sbt @@ -557,7 +557,6 @@ lazy val vscodePlugin = (project in file("vscode-sbt-scala")) ) def scriptedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask { - val result = scriptedSource(dir => (s: State) => Scripted.scriptedParser(dir)).parsed // publishLocalBinAll.value // TODO: Restore scripted needing only binary jars. publishAll.value (sbtProj / Test / compile).value // make sure sbt.RunFromSourceMain is compiled @@ -567,21 +566,20 @@ def scriptedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask { (scalaInstance in scriptedSbtProj).value, scriptedSource.value, scriptedBufferLog.value, - result, + Def.setting(Scripted.scriptedParser(scriptedSource.value)).parsed, scriptedPrescripted.value, scriptedLaunchOpts.value ) } def scriptedUnpublishedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask { - val result = scriptedSource(dir => (s: State) => Scripted.scriptedParser(dir)).parsed Scripted.doScripted( (sbtLaunchJar in bundledLauncherProj).value, (fullClasspath in scriptedSbtProj in Test).value, (scalaInstance in scriptedSbtProj).value, scriptedSource.value, scriptedBufferLog.value, - result, + Def.setting(Scripted.scriptedParser(scriptedSource.value)).parsed, scriptedPrescripted.value, scriptedLaunchOpts.value ) diff --git a/project/Scripted.scala b/project/Scripted.scala index 6991003a7..7a46149fc 100644 --- a/project/Scripted.scala +++ b/project/Scripted.scala @@ -1,13 +1,12 @@ +import java.lang.reflect.InvocationTargetException + import sbt._ -import Keys._ -import Def.Initialize import sbt.internal.inc.ScalaInstance -import sbt.internal.inc.classpath +import sbt.internal.inc.classpath.{ ClasspathUtilities, FilteredLoader } -import scala.language.reflectiveCalls - -object ScriptedPlugin extends sbt.AutoPlugin { +object ScriptedPlugin extends AutoPlugin { override def requires = plugins.JvmPlugin + object autoImport extends ScriptedKeys { def scriptedPath = file("scripted") } @@ -39,27 +38,31 @@ object Scripted { val RepoOverrideTest = config("repoOverrideTest") extend Compile import sbt.complete._ - import DefaultParsers._ + // Paging, 1-index based. - case class ScriptedTestPage(page: Int, total: Int) + final case class ScriptedTestPage(page: Int, total: Int) + // FIXME: Duplicated with ScriptedPlugin.scriptedParser, this can be // avoided once we upgrade build.properties to 0.13.14 def scriptedParser(scriptedBase: File): Parser[Seq[String]] = { + import DefaultParsers._ + val scriptedFiles: NameFilter = ("test": NameFilter) | "pending" val pairs = (scriptedBase * AllPassFilter * AllPassFilter * scriptedFiles).get map { (f: File) => val p = f.getParentFile (p.getParentFile.getName, p.getName) } - val pairMap = pairs.groupBy(_._1).mapValues(_.map(_._2).toSet); + val pairMap = pairs.groupBy(_._1).mapValues(_.map(_._2).toSet) val id = charClass(c => !c.isWhitespace && c != '/').+.string - val groupP = token(id.examples(pairMap.keySet.toSet)) <~ token('/') + val groupP = token(id.examples(pairMap.keySet)) <~ token('/') // A parser for page definitions val pageP: Parser[ScriptedTestPage] = ("*" ~ NatBasic ~ "of" ~ NatBasic) map { case _ ~ page ~ _ ~ total => ScriptedTestPage(page, total) } + // Grabs the filenames from a given test group in the current page definition. def pagedFilenames(group: String, page: ScriptedTestPage): Seq[String] = { val files = pairMap(group).toSeq.sortBy(_.toLowerCase) @@ -69,9 +72,11 @@ object Scripted { if (page.page == page.total) dropped else dropped.take(pageSize) } + def nameP(group: String) = { token("*".id | id.examples(pairMap.getOrElse(group, Set.empty[String]))) } + val PagedIds: Parser[Seq[String]] = for { group <- groupP @@ -79,55 +84,64 @@ object Scripted { files = pagedFilenames(group, page) // TODO - Fail the parser if we don't have enough files for the given page size //if !files.isEmpty - } yield files map (f => group + '/' + f) + } yield files map (f => s"$group/$f") val testID = (for (group <- groupP; name <- nameP(group)) yield (group, name)) val testIdAsGroup = matched(testID) map (test => Seq(test)) + //(token(Space) ~> matched(testID)).* (token(Space) ~> (PagedIds | testIdAsGroup)).* map (_.flatten) } - // Interface to cross class loader - type SbtScriptedRunner = { - def runInParallel(resourceBaseDirectory: File, - bufferLog: Boolean, - tests: Array[String], - bootProperties: File, - launchOpts: Array[String], - prescripted: java.util.List[File]): Unit - } - - def doScripted(launcher: File, - scriptedSbtClasspath: Seq[Attributed[File]], - scriptedSbtInstance: ScalaInstance, - sourcePath: File, - bufferLog: Boolean, - args: Seq[String], - prescripted: File => Unit, - launchOpts: Seq[String]): Unit = { + def doScripted( + launcher: File, + scriptedSbtClasspath: Seq[Attributed[File]], + scriptedSbtInstance: ScalaInstance, + sourcePath: File, + bufferLog: Boolean, + args: Seq[String], + prescripted: File => Unit, + launchOpts: Seq[String], + ): Unit = { System.err.println(s"About to run tests: ${args.mkString("\n * ", "\n * ", "\n")}") + // Force Log4J to not use a thread context classloader otherwise it throws a CCE sys.props(org.apache.logging.log4j.util.LoaderUtil.IGNORE_TCCL_PROPERTY) = "true" - val noJLine = new classpath.FilteredLoader(scriptedSbtInstance.loader, "jline." :: Nil) - val loader = classpath.ClasspathUtilities.toLoader(scriptedSbtClasspath.files, noJLine) + + val noJLine = new FilteredLoader(scriptedSbtInstance.loader, "jline." :: Nil) + val loader = ClasspathUtilities.toLoader(scriptedSbtClasspath.files, noJLine) val bridgeClass = Class.forName("sbt.scriptedtest.ScriptedRunner", true, loader) + + // Interface to cross class loader + type SbtScriptedRunner = { + def runInParallel( + resourceBaseDirectory: File, + bufferLog: Boolean, + tests: Array[String], + bootProperties: File, + launchOpts: Array[String], + prescripted: java.util.List[File], + ): Unit + } + val bridge = bridgeClass.getDeclaredConstructor().newInstance().asInstanceOf[SbtScriptedRunner] + try { // Using java.util.List to encode File => Unit. val callback = new java.util.AbstractList[File] { - override def add(x: File): Boolean = { - prescripted(x) - false - } + override def add(x: File): Boolean = { prescripted(x); false } def get(x: Int): sbt.File = ??? def size(): Int = 0 } - bridge.runInParallel(sourcePath, - bufferLog, - args.toArray, - launcher, - launchOpts.toArray, - callback) - } catch { case ite: java.lang.reflect.InvocationTargetException => throw ite.getCause } + import scala.language.reflectiveCalls + bridge.runInParallel( + sourcePath, + bufferLog, + args.toArray, + launcher, + launchOpts.toArray, + callback, + ) + } catch { case ite: InvocationTargetException => throw ite.getCause } } } From 6e83d408da275f0cbbc952d0f5f32c433b9d57b3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 17 Jan 2018 15:20:22 +0000 Subject: [PATCH 140/356] Define ScriptedPlugin keys with macros --- main/src/main/scala/sbt/ScriptedPlugin.scala | 29 ++++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/main/src/main/scala/sbt/ScriptedPlugin.scala b/main/src/main/scala/sbt/ScriptedPlugin.scala index 538a851ef..29414556c 100644 --- a/main/src/main/scala/sbt/ScriptedPlugin.scala +++ b/main/src/main/scala/sbt/ScriptedPlugin.scala @@ -27,23 +27,22 @@ object ScriptedPlugin extends AutoPlugin { object autoImport { val ScriptedConf = Configurations.config("scripted-sbt") hide val ScriptedLaunchConf = Configurations.config("scripted-sbt-launch") hide - val scriptedSbt = SettingKey[String]("scripted-sbt") - val sbtLauncher = TaskKey[File]("sbt-launcher") - val sbtTestDirectory = SettingKey[File]("sbt-test-directory") - val scriptedBufferLog = SettingKey[Boolean]("scripted-buffer-log") - val scriptedClasspath = TaskKey[PathFinder]("scripted-classpath") - val scriptedTests = TaskKey[AnyRef]("scripted-tests") + + val scriptedSbt = settingKey[String]("") + val sbtLauncher = taskKey[File]("") + val sbtTestDirectory = settingKey[File]("") + val scriptedBufferLog = settingKey[Boolean]("") + val scriptedClasspath = taskKey[PathFinder]("") + val scriptedTests = taskKey[AnyRef]("") val scriptedBatchExecution = settingKey[Boolean]("Enables or disables batch execution for scripted.") - val scriptedParallelInstances = - settingKey[Int]( - "Configures the number of scripted instances for parallel testing, only used in batch mode.") - val scriptedRun = TaskKey[Method]("scripted-run") - val scriptedLaunchOpts = SettingKey[Seq[String]]( - "scripted-launch-opts", - "options to pass to jvm launching scripted tasks") - val scriptedDependencies = TaskKey[Unit]("scripted-dependencies") - val scripted = InputKey[Unit]("scripted") + val scriptedParallelInstances = settingKey[Int]( + "Configures the number of scripted instances for parallel testing, only used in batch mode.") + val scriptedRun = taskKey[Method]("") + val scriptedLaunchOpts = + settingKey[Seq[String]]("options to pass to jvm launching scripted tasks") + val scriptedDependencies = taskKey[Unit]("") + val scripted = inputKey[Unit]("") } import autoImport._ From 9006abe9be99d5ce070e91aa5f840900caa5d542 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 17 Jan 2018 15:21:50 +0000 Subject: [PATCH 141/356] Cleanup ScriptedPlugin --- main/src/main/scala/sbt/ScriptedPlugin.scala | 41 +++++++++++--------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/main/src/main/scala/sbt/ScriptedPlugin.scala b/main/src/main/scala/sbt/ScriptedPlugin.scala index 29414556c..7a3128c89 100644 --- a/main/src/main/scala/sbt/ScriptedPlugin.scala +++ b/main/src/main/scala/sbt/ScriptedPlugin.scala @@ -8,22 +8,24 @@ package sbt import java.io.File -import Def.Initialize -import Keys._ -import sbt.internal.util.complete.{ Parser, DefaultParsers } -import sbt.internal.inc.classpath.ClasspathUtilities -import sbt.internal.inc.ModuleUtilities import java.lang.reflect.Method -import sbt.librarymanagement._ -import sbt.librarymanagement.syntax._ + import sbt.io._ import sbt.io.syntax._ -import Project._ + +import sbt.internal.util.complete.{ Parser, DefaultParsers } + +import sbt.librarymanagement._ +import sbt.librarymanagement.syntax._ + +import sbt.internal.inc.classpath.ClasspathUtilities +import sbt.internal.inc.ModuleUtilities + import Def._ +import Keys._ +import Project._ object ScriptedPlugin extends AutoPlugin { - override def requires = plugins.JvmPlugin - object autoImport { val ScriptedConf = Configurations.config("scripted-sbt") hide val ScriptedLaunchConf = Configurations.config("scripted-sbt-launch") hide @@ -44,7 +46,6 @@ object ScriptedPlugin extends AutoPlugin { val scriptedDependencies = taskKey[Unit]("") val scripted = inputKey[Unit]("") } - import autoImport._ override lazy val globalSettings = Seq( @@ -113,10 +114,10 @@ object ScriptedPlugin extends AutoPlugin { Def.task(method) } - import DefaultParsers._ - private[sbt] case class ScriptedTestPage(page: Int, total: Int) + private[sbt] final case class ScriptedTestPage(page: Int, total: Int) private[sbt] def scriptedParser(scriptedBase: File): Parser[Seq[String]] = { + import DefaultParsers._ val scriptedFiles: NameFilter = ("test": NameFilter) | "pending" val pairs = (scriptedBase * AllPassFilter * AllPassFilter * scriptedFiles).get map { @@ -124,15 +125,16 @@ object ScriptedPlugin extends AutoPlugin { val p = f.getParentFile (p.getParentFile.getName, p.getName) } - val pairMap = pairs.groupBy(_._1).mapValues(_.map(_._2).toSet); + val pairMap = pairs.groupBy(_._1).mapValues(_.map(_._2).toSet) val id = charClass(c => !c.isWhitespace && c != '/').+.string - val groupP = token(id.examples(pairMap.keySet.toSet)) <~ token('/') + val groupP = token(id.examples(pairMap.keySet)) <~ token('/') // A parser for page definitions val pageP: Parser[ScriptedTestPage] = ("*" ~ NatBasic ~ "of" ~ NatBasic) map { case _ ~ page ~ _ ~ total => ScriptedTestPage(page, total) } + // Grabs the filenames from a given test group in the current page definition. def pagedFilenames(group: String, page: ScriptedTestPage): Seq[String] = { val files = pairMap(group).toSeq.sortBy(_.toLowerCase) @@ -142,9 +144,11 @@ object ScriptedPlugin extends AutoPlugin { if (page.page == page.total) dropped else dropped.take(pageSize) } + def nameP(group: String) = { token("*".id | id.examples(pairMap.getOrElse(group, Set.empty[String]))) } + val PagedIds: Parser[Seq[String]] = for { group <- groupP @@ -152,10 +156,11 @@ object ScriptedPlugin extends AutoPlugin { files = pagedFilenames(group, page) // TODO - Fail the parser if we don't have enough files for the given page size //if !files.isEmpty - } yield files map (f => group + '/' + f) + } yield files map (f => s"$group/$f") val testID = (for (group <- groupP; name <- nameP(group)) yield (group, name)) val testIdAsGroup = matched(testID) map (test => Seq(test)) + //(token(Space) ~> matched(testID)).* (token(Space) ~> (PagedIds | testIdAsGroup)).* map (_.flatten) } @@ -167,11 +172,11 @@ object ScriptedPlugin extends AutoPlugin { val method = scriptedRun.value val scriptedInstance = scriptedTests.value val dir = sbtTestDirectory.value - val log: java.lang.Boolean = scriptedBufferLog.value + val log = Boolean box scriptedBufferLog.value val launcher = sbtLauncher.value val opts = scriptedLaunchOpts.value.toArray val empty = new java.util.ArrayList[File]() - val instances: java.lang.Integer = scriptedParallelInstances.value + val instances = Int box scriptedParallelInstances.value if (scriptedBatchExecution.value) method.invoke(scriptedInstance, dir, log, args.toArray, launcher, opts, empty, instances) From 21eb1f0f1219368b38bc0a65c0446103bb6959d2 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 24 Jan 2018 12:58:52 +0000 Subject: [PATCH 142/356] Cleanup Attributes --- .../src/main/scala/sbt/internal/util/Attributes.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/Attributes.scala b/internal/util-collection/src/main/scala/sbt/internal/util/Attributes.scala index 0dfbea73e..3e4832f1e 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/Attributes.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/Attributes.scala @@ -31,7 +31,8 @@ sealed trait AttributeKey[T] { def description: Option[String] /** - * In environments that support delegation, looking up this key when it has no associated value will delegate to the values associated with these keys. + * In environments that support delegation, looking up this key when it has no associated value + * will delegate to the values associated with these keys. * The delegation proceeds in order the keys are returned here. */ def extend: Seq[AttributeKey[_]] From 6038ec51c3fd3451443a0d9632223c288ac2444c Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 24 Jan 2018 12:59:22 +0000 Subject: [PATCH 143/356] Cleanup & simplify actions/run-task --- sbt/src/sbt-test/actions/run-task/build.sbt | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/sbt/src/sbt-test/actions/run-task/build.sbt b/sbt/src/sbt-test/actions/run-task/build.sbt index 0166f62ed..69fb3e613 100644 --- a/sbt/src/sbt-test/actions/run-task/build.sbt +++ b/sbt/src/sbt-test/actions/run-task/build.sbt @@ -1,13 +1,7 @@ -lazy val root = (project in file(".")). - settings( - myRun, - fork in demo := true, - javaOptions in demo := "-Dsbt.check.forked=true" :: Nil, - myIn - ) +val demo = taskKey[Unit]("Demo run task") +fullRunTask(demo, Compile, "A", "1", "1") +fork in demo := true +javaOptions in demo := "-Dsbt.check.forked=true" :: Nil -lazy val demoIn = InputKey[Unit]("demoIn", "Demo run input task", demo) -lazy val demo = taskKey[Unit]("Demo run task") - -def myRun = fullRunTask(demo, Compile, "A", "1", "1") -def myIn = fullRunInputTask(demoIn, Compile, "A", "1") +val demoIn = InputKey[Unit]("demoIn", "Demo run input task", demo) +fullRunInputTask(demoIn, Compile, "A", "1") From 705eeddf5cb19d32ebc1bc7dd91ee5ff89951f0b Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 24 Jan 2018 13:10:39 +0000 Subject: [PATCH 144/356] Cleanup & simplify actions/add-alias --- sbt/src/sbt-test/actions/add-alias/A.scala | 6 ++++-- sbt/src/sbt-test/actions/add-alias/build.sbt | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sbt/src/sbt-test/actions/add-alias/A.scala b/sbt/src/sbt-test/actions/add-alias/A.scala index dbae0c48a..5b2ba478c 100644 --- a/sbt/src/sbt-test/actions/add-alias/A.scala +++ b/sbt/src/sbt-test/actions/add-alias/A.scala @@ -1,3 +1,5 @@ -object A extends App { - if(args(0).toBoolean) () else sys.error("Fail") +object A { + def main(args: Array[String]): Unit = { + if (args(0).toBoolean) () else sys.error("Fail") + } } diff --git a/sbt/src/sbt-test/actions/add-alias/build.sbt b/sbt/src/sbt-test/actions/add-alias/build.sbt index 451b72246..cb5461c6b 100644 --- a/sbt/src/sbt-test/actions/add-alias/build.sbt +++ b/sbt/src/sbt-test/actions/add-alias/build.sbt @@ -1,2 +1,2 @@ -addCommandAlias("demo-success", "run true") ++ +addCommandAlias("demo-success", "run true") addCommandAlias("demo-failure", "run false") From 78f4f56d1c7602921019dcf568d388a3409d12b6 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 24 Jan 2018 13:36:26 +0000 Subject: [PATCH 145/356] Fix 2 "discarded non-Unit value" warnings in ScriptedPlugin --- main/src/main/scala/sbt/ScriptedPlugin.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/main/src/main/scala/sbt/ScriptedPlugin.scala b/main/src/main/scala/sbt/ScriptedPlugin.scala index 7a3128c89..6e2544426 100644 --- a/main/src/main/scala/sbt/ScriptedPlugin.scala +++ b/main/src/main/scala/sbt/ScriptedPlugin.scala @@ -181,6 +181,7 @@ object ScriptedPlugin extends AutoPlugin { if (scriptedBatchExecution.value) method.invoke(scriptedInstance, dir, log, args.toArray, launcher, opts, empty, instances) else method.invoke(scriptedInstance, dir, log, args.toArray, launcher, opts, empty) + () } catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause } } From 6f52437e95ca512b40d72b5dfc0ddc3b03f2a778 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 1 Feb 2018 15:21:34 +0000 Subject: [PATCH 146/356] Fix a "discarded non-Unit value" warning in FileExamplesTest --- .../src/test/scala/sbt/complete/FileExamplesTest.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/util-complete/src/test/scala/sbt/complete/FileExamplesTest.scala b/internal/util-complete/src/test/scala/sbt/complete/FileExamplesTest.scala index 173b276fc..f7b79573c 100644 --- a/internal/util-complete/src/test/scala/sbt/complete/FileExamplesTest.scala +++ b/internal/util-complete/src/test/scala/sbt/complete/FileExamplesTest.scala @@ -9,6 +9,7 @@ package sbt.internal.util package complete import java.io.File +import org.scalatest.Assertion import sbt.io.IO class FileExamplesTest extends UnitSpec { @@ -57,7 +58,8 @@ class FileExamplesTest extends UnitSpec { } def withDirectoryStructure[A](withCompletionPrefix: String = "")( - thunk: DirectoryStructure => A): Unit = { + thunk: DirectoryStructure => Assertion + ): Assertion = { IO.withTemporaryDirectory { tempDir => val ds = new DirectoryStructure(withCompletionPrefix) ds.createSampleDirStructure(tempDir) From 82c39ec96f58ea190c2fbaba103539d6bef64f7d Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 1 Feb 2018 15:21:47 +0000 Subject: [PATCH 147/356] Fix a "discarded non-Unit value" warning in TaskRunnerFork --- tasks-standard/src/test/scala/TaskRunnerFork.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks-standard/src/test/scala/TaskRunnerFork.scala b/tasks-standard/src/test/scala/TaskRunnerFork.scala index 105f4a2de..639963454 100644 --- a/tasks-standard/src/test/scala/TaskRunnerFork.scala +++ b/tasks-standard/src/test/scala/TaskRunnerFork.scala @@ -33,6 +33,7 @@ object TaskRunnerForkTest extends Properties("TaskRunner Fork") { def runDoubleJoin(a: Int, b: Int, workers: Int): Unit = { def inner = List.range(0, b).map(j => task(j).named(j.toString)).join tryRun(List.range(0, a).map(_ => inner).join, false, workers) + () } property("fork and reduce") = forAll(TaskListGen, MaxWorkersGen) { (m: List[Int], workers: Int) => m.nonEmpty ==> { From dbbba67d36813adae76cd6e9ed066406a61a2c55 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 24 Jan 2018 13:36:55 +0000 Subject: [PATCH 148/356] Fix a Scaladoc error in BuildStructure --- main/src/main/scala/sbt/internal/BuildStructure.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/BuildStructure.scala b/main/src/main/scala/sbt/internal/BuildStructure.scala index 626123638..65b5eea6a 100644 --- a/main/src/main/scala/sbt/internal/BuildStructure.scala +++ b/main/src/main/scala/sbt/internal/BuildStructure.scala @@ -154,7 +154,8 @@ case class DetectedAutoPlugin(name: String, value: AutoPlugin, hasAutoImport: Bo * Auto-discovered modules for the build definition project. These include modules defined in build definition sources * as well as modules in binary dependencies. * - * @param builds The [[Build]]s detected in the build definition. This does not include the default [[Build]] that sbt creates if none is defined. + * @param builds The [[BuildDef]]s detected in the build definition. + * This does not include the default [[BuildDef]] that sbt creates if none is defined. */ final class DetectedPlugins(val autoPlugins: Seq[DetectedAutoPlugin], val builds: DetectedModules[BuildDef]) { From 9d808a389c557355c8e8d0ad4bf4bc2be53248d2 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 24 Jan 2018 13:53:11 +0000 Subject: [PATCH 149/356] Update PublishBinPlugin to current sbt --- project/PublishBinPlugin.scala | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/project/PublishBinPlugin.scala b/project/PublishBinPlugin.scala index a54d9958e..c013e78d6 100644 --- a/project/PublishBinPlugin.scala +++ b/project/PublishBinPlugin.scala @@ -5,14 +5,12 @@ import sbt.librarymanagement.ConfigRef /** This local plugin provides ways of publishing just the binary jar. */ object PublishBinPlugin extends AutoPlugin { - override def requires = plugins.JvmPlugin override def trigger = allRequirements object autoImport { val publishLocalBin = taskKey[Unit]("") val publishLocalBinConfig = taskKey[PublishConfiguration]("") } - import autoImport._ override def globalSettings = Seq(publishLocalBin := (())) @@ -20,22 +18,17 @@ object PublishBinPlugin extends AutoPlugin { override def projectSettings = Def settings ( publishLocalBin := Classpaths.publishTask(publishLocalBinConfig, deliverLocal).value, publishLocalBinConfig := { - val _ = deliverLocal.value Classpaths.publishConfig( - publishMavenStyle.value, - deliverPattern(crossTarget.value), + false, // publishMavenStyle.value, + Classpaths.deliverPattern(crossTarget.value), if (isSnapshot.value) "integration" else "release", ivyConfigurations.value.map(c => ConfigRef(c.name)).toVector, (packagedArtifacts in publishLocalBin).value.toVector, (checksums in publishLocalBin).value.toVector, - resolverName = "local", logging = ivyLoggingLevel.value, overwrite = isSnapshot.value ) }, packagedArtifacts in publishLocalBin := Classpaths.packaged(Seq(packageBin in Compile)).value ) - - def deliverPattern(outputPath: File): String = - (outputPath / "[artifact]-[revision](-[classifier]).[ext]").absolutePath } From b8abf4285f2ca160d62f1c69c488ff3e0a741e9a Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 24 Jan 2018 14:46:41 +0000 Subject: [PATCH 150/356] Make BatchScriptRunner.States hold StatementHandler#State --- .../scala/sbt/scriptedtest/BatchScriptRunner.scala | 13 +++++++------ .../main/scala/sbt/scriptedtest/ScriptedTests.scala | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala index fe9d35712..2d23a0604 100644 --- a/scripted/sbt/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala @@ -8,11 +8,17 @@ package sbt package scriptedtest +import scala.collection.mutable + import sbt.internal.scripted._ -import sbt.scriptedtest.BatchScriptRunner.States + +private[sbt] object BatchScriptRunner { + type States = mutable.HashMap[StatementHandler, StatementHandler#State] +} /** Defines an alternative script runner that allows batch execution. */ private[sbt] class BatchScriptRunner extends ScriptRunner { + import BatchScriptRunner.States /** Defines a method to run batched execution. * @@ -58,8 +64,3 @@ private[sbt] class BatchScriptRunner extends ScriptRunner { } } } - -private[sbt] object BatchScriptRunner { - import scala.collection.mutable - type States = mutable.HashMap[StatementHandler, Any] -} diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index 88c28d01c..e9c26281b 100644 --- a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -60,7 +60,7 @@ final class ScriptedTests(resourceBaseDirectory: File, val handlers = createScriptedHandlers(testDirectory, buffer, RemoteSbtCreatorKind.LauncherBased) val runner = new BatchScriptRunner - val states = new mutable.HashMap[StatementHandler, Any]() + val states = new mutable.HashMap[StatementHandler, StatementHandler#State]() commonRunTest(label, testDirectory, prescripted, handlers, runner, states, buffer) } runOrHandleDisabled(label, testDirectory, singleTestRunner, buffer) From cb2e4e67fd7fb63cfe343c81aaaaad4eff3fec7d Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 30 Jan 2018 15:53:05 +0000 Subject: [PATCH 151/356] Cleanup BatchScriptRunner --- .../src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala index 2d23a0604..805059518 100644 --- a/scripted/sbt/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala @@ -43,9 +43,8 @@ private[sbt] class BatchScriptRunner extends ScriptRunner { def processStatement(handler: StatementHandler, statement: Statement, states: States): Unit = { val state = states(handler).asInstanceOf[handler.State] val nextState = - try { Right(handler(statement.command, statement.arguments, state)) } catch { - case e: Exception => Left(e) - } + try Right(handler(statement.command, statement.arguments, state)) + catch { case e: Exception => Left(e) } nextState match { case Left(err) => if (statement.successExpected) { From 4debec00536c565f9bade4d6051d48275cf90362 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 24 Jan 2018 14:11:11 +0000 Subject: [PATCH 152/356] Cleanup ScriptedTests --- .../sbt/scriptedtest/ScriptedTests.scala | 241 +++++++++--------- 1 file changed, 124 insertions(+), 117 deletions(-) diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index e9c26281b..69a856d89 100644 --- a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -9,27 +9,31 @@ package sbt package scriptedtest import java.io.File +import java.net.SocketException import java.util.Properties +import java.util.concurrent.ForkJoinPool -import scala.util.control.NonFatal -import sbt.internal.scripted._ -import sbt.io.{ DirectoryFilter, HiddenFileFilter, IO } -import sbt.io.IO.wrapNull -import sbt.io.FileFilter._ -import sbt.internal.io.Resources -import sbt.internal.util.{ BufferedLogger, ConsoleLogger, FullLogger } -import sbt.util.{ AbstractLogger, Logger } - +import scala.collection.GenSeq import scala.collection.mutable import scala.collection.parallel.ForkJoinTaskSupport -import scala.collection.parallel.mutable.ParSeq +import scala.util.control.NonFatal + +import sbt.internal.scripted._ +import sbt.internal.io.Resources +import sbt.internal.util.{ BufferedLogger, ConsoleLogger, FullLogger } +import sbt.io.syntax._ +import sbt.io.{ DirectoryFilter, HiddenFileFilter, IO } +import sbt.io.FileFilter._ +import sbt.util.{ AbstractLogger, Logger } + +final class ScriptedTests( + resourceBaseDirectory: File, + bufferLog: Boolean, + launcher: File, + launchOpts: Seq[String], +) { + import ScriptedTests.{ TestRunner, emptyCallback } -final class ScriptedTests(resourceBaseDirectory: File, - bufferLog: Boolean, - launcher: File, - launchOpts: Seq[String]) { - import sbt.io.syntax._ - import ScriptedTests._ private val testResources = new Resources(resourceBaseDirectory) val ScriptFilename = "test" @@ -37,14 +41,17 @@ final class ScriptedTests(resourceBaseDirectory: File, def scriptedTest(group: String, name: String, log: xsbti.Logger): Seq[TestRunner] = scriptedTest(group, name, Logger.xlog2Log(log)) + def scriptedTest(group: String, name: String, log: Logger): Seq[TestRunner] = singleScriptedTest(group, name, emptyCallback, log) /** Returns a sequence of test runners that have to be applied in the call site. */ - def singleScriptedTest(group: String, - name: String, - prescripted: File => Unit, - log: Logger): Seq[TestRunner] = { + def singleScriptedTest( + group: String, + name: String, + prescripted: File => Unit, + log: Logger, + ): Seq[TestRunner] = { // Test group and names may be file filters (like '*') for (groupDir <- (resourceBaseDirectory * group).get; nme <- (groupDir * name).get) yield { @@ -97,8 +104,8 @@ final class ScriptedTests(resourceBaseDirectory: File, val groupAndNameDirs = { for { (group, name) <- testGroupAndNames - groupDir <- resourceBaseDirectory.*(group).get - testDir <- groupDir.*(name).get + groupDir <- (resourceBaseDirectory * group).get + testDir <- (groupDir * name).get } yield (groupDir, testDir) } @@ -318,8 +325,8 @@ final class ScriptedTests(resourceBaseDirectory: File, // Reload and initialize (to reload contents of .sbtrc files) val pluginImplementation = createAutoPlugin(name) IO.write(tempTestDir / "project" / "InstrumentScripted.scala", pluginImplementation) - val sbtHandlerError = "Missing sbt handler. Scripted is misconfigured." - val sbtHandler = handlers.getOrElse('>', sbtHandlerError).asInstanceOf[SbtHandler] + def sbtHandlerError = sys error "Missing sbt handler. Scripted is misconfigured." + val sbtHandler = handlers.getOrElse('>', sbtHandlerError) val commandsToRun = ";reload;setUpScripted" val statement = Statement(commandsToRun, Nil, successExpected = true, line = -1) @@ -368,7 +375,7 @@ final class ScriptedTests(resourceBaseDirectory: File, label: String, testDirectory: File, preScriptedHook: File => Unit, - createHandlers: Map[Char, StatementHandler], + handlers: Map[Char, StatementHandler], runner: BatchScriptRunner, states: BatchScriptRunner.States, log: BufferedLogger @@ -382,15 +389,15 @@ final class ScriptedTests(resourceBaseDirectory: File, } val pendingMark = if (pending) PendingLabel else "" + def testFailed(t: Throwable): Option[String] = { if (pending) log.clear() else log.stop() log.error(s"x $label $pendingMark") if (!NonFatal(t)) throw t // We make sure fatal errors are rethrown if (t.isInstanceOf[TestException]) { t.getCause match { - case null | _: java.net.SocketException => - log.error(" Cause of test exception: " + t.getMessage) - case _ => t.printStackTrace() + case null | _: SocketException => log.error(s" Cause of test exception: ${t.getMessage}") + case _ => t.printStackTrace() } } if (pending) None else Some(label) @@ -399,7 +406,6 @@ final class ScriptedTests(resourceBaseDirectory: File, import scala.util.control.Exception.catching catching(classOf[TestException]).withApply(testFailed).andFinally(log.clear).apply { preScriptedHook(testDirectory) - val handlers = createHandlers val parser = new TestScriptParser(handlers) val handlersAndStatements = parser.parse(file) runner.apply(handlersAndStatements, states) @@ -421,6 +427,7 @@ object ScriptedTests extends ScriptedRunner { type TestRunner = () => Seq[Option[String]] val emptyCallback: File => Unit = _ => () + def main(args: Array[String]): Unit = { val directory = new File(args(0)) val buffer = args(1).toBoolean @@ -432,48 +439,52 @@ object ScriptedTests extends ScriptedRunner { val logger = ConsoleLogger() run(directory, buffer, tests, logger, bootProperties, Array(), emptyCallback) } + } +/** Runner for `scripted`. Not be confused with ScriptRunner. */ class ScriptedRunner { // This is called by project/Scripted.scala // Using java.util.List[File] to encode File => Unit - def run(resourceBaseDirectory: File, - bufferLog: Boolean, - tests: Array[String], - bootProperties: File, - launchOpts: Array[String], - prescripted: java.util.List[File]): Unit = { - - // Force Log4J to not use a thread context classloader otherwise it throws a CCE - sys.props(org.apache.logging.log4j.util.LoaderUtil.IGNORE_TCCL_PROPERTY) = "true" - - run(resourceBaseDirectory, bufferLog, tests, ConsoleLogger(), bootProperties, launchOpts, { - f: File => - prescripted.add(f); () - }) //new FullLogger(Logger.xlog2Log(log))) + def run( + resourceBaseDirectory: File, + bufferLog: Boolean, + tests: Array[String], + bootProperties: File, + launchOpts: Array[String], + prescripted: java.util.List[File], + ): Unit = { + val logger = ConsoleLogger() + val addTestFile = (f: File) => { prescripted.add(f); () } + run(resourceBaseDirectory, bufferLog, tests, logger, bootProperties, launchOpts, addTestFile) + //new FullLogger(Logger.xlog2Log(log))) } // This is called by sbt-scripted 0.13.x and 1.x (see https://github.com/sbt/sbt/issues/3245) - def run(resourceBaseDirectory: File, - bufferLog: Boolean, - tests: Array[String], - bootProperties: File, - launchOpts: Array[String]): Unit = - run(resourceBaseDirectory, - bufferLog, - tests, - ConsoleLogger(), - bootProperties, - launchOpts, - ScriptedTests.emptyCallback) + def run( + resourceBaseDirectory: File, + bufferLog: Boolean, + tests: Array[String], + bootProperties: File, + launchOpts: Array[String], + ): Unit = { + val logger = ConsoleLogger() + val prescripted = ScriptedTests.emptyCallback + run(resourceBaseDirectory, bufferLog, tests, logger, bootProperties, launchOpts, prescripted) + } + + def run( + resourceBaseDirectory: File, + bufferLog: Boolean, + tests: Array[String], + logger: AbstractLogger, + bootProperties: File, + launchOpts: Array[String], + prescripted: File => Unit, + ): Unit = { + // Force Log4J to not use a thread context classloader otherwise it throws a CCE + sys.props(org.apache.logging.log4j.util.LoaderUtil.IGNORE_TCCL_PROPERTY) = "true" - def run(resourceBaseDirectory: File, - bufferLog: Boolean, - tests: Array[String], - logger: AbstractLogger, - bootProperties: File, - launchOpts: Array[String], - prescripted: File => Unit): Unit = { val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, bootProperties, launchOpts) val sbtVersion = bootProperties.getName.dropWhile(!_.isDigit).dropRight(".jar".length) val accept = isTestCompatible(resourceBaseDirectory, sbtVersion) _ @@ -484,22 +495,15 @@ class ScriptedRunner { runAll(allTests) } - def runInParallel(resourceBaseDirectory: File, - bufferLog: Boolean, - tests: Array[String], - bootProperties: File, - launchOpts: Array[String], - prescripted: java.util.List[File]): Unit = { - val logger = ConsoleLogger() - val addTestFile = (f: File) => { prescripted.add(f); () } - runInParallel(resourceBaseDirectory, - bufferLog, - tests, - logger, - bootProperties, - launchOpts, - addTestFile, - 1) + def runInParallel( + baseDir: File, + bufferLog: Boolean, + tests: Array[String], + bootProps: File, + launchOpts: Array[String], + prescripted: java.util.List[File], + ): Unit = { + runInParallel(baseDir, bufferLog, tests, bootProps, launchOpts, prescripted, 1) } // This is used by sbt-scripted sbt 1.x @@ -518,54 +522,54 @@ class ScriptedRunner { } def runInParallel( - resourceBaseDirectory: File, + baseDir: File, bufferLog: Boolean, tests: Array[String], logger: AbstractLogger, - bootProperties: File, + bootProps: File, launchOpts: Array[String], prescripted: File => Unit, instances: Int ): Unit = { - val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, bootProperties, launchOpts) - val sbtVersion = bootProperties.getName.dropWhile(!_.isDigit).dropRight(".jar".length) - val accept = isTestCompatible(resourceBaseDirectory, sbtVersion) _ + val runner = new ScriptedTests(baseDir, bufferLog, bootProps, launchOpts) + val sbtVersion = bootProps.getName.dropWhile(!_.isDigit).dropRight(".jar".length) + val accept = isTestCompatible(baseDir, sbtVersion) _ // The scripted tests mapped to the inputs that the user wrote after `scripted`. val scriptedTests = - get(tests, resourceBaseDirectory, accept, logger).map(st => (st.group, st.name)) + get(tests, baseDir, accept, logger).map(st => (st.group, st.name)) val scriptedRunners = runner.batchScriptedRunner(scriptedTests, prescripted, instances, logger) val parallelRunners = scriptedRunners.toParArray - val pool = new java.util.concurrent.ForkJoinPool(instances) - parallelRunners.tasksupport = new ForkJoinTaskSupport(pool) - runAllInParallel(parallelRunners) + parallelRunners.tasksupport = new ForkJoinTaskSupport(new ForkJoinPool(instances)) + runAll(parallelRunners) } - private def reportErrors(errors: Seq[String]): Unit = + private def reportErrors(errors: GenSeq[String]): Unit = if (errors.nonEmpty) sys.error(errors.mkString("Failed tests:\n\t", "\n\t", "\n")) else () - def runAll(toRun: Seq[ScriptedTests.TestRunner]): Unit = - reportErrors(toRun.flatMap(test => test.apply().flatten.toSeq)) - - // We cannot reuse `runAll` because parallel collections != collections - def runAllInParallel(tests: ParSeq[ScriptedTests.TestRunner]): Unit = { - reportErrors(tests.flatMap(test => test.apply().flatten.toSeq).toList) - } + def runAll(toRun: GenSeq[ScriptedTests.TestRunner]): Unit = + reportErrors(toRun.flatMap(test => test.apply().flatten)) @deprecated("No longer used", "1.1.0") def get(tests: Seq[String], baseDirectory: File, log: Logger): Seq[ScriptedTest] = get(tests, baseDirectory, _ => true, log) - def get(tests: Seq[String], - baseDirectory: File, - accept: ScriptedTest => Boolean, - log: Logger): Seq[ScriptedTest] = + + def get( + tests: Seq[String], + baseDirectory: File, + accept: ScriptedTest => Boolean, + log: Logger, + ): Seq[ScriptedTest] = if (tests.isEmpty) listTests(baseDirectory, accept, log) else parseTests(tests) @deprecated("No longer used", "1.1.0") def listTests(baseDirectory: File, log: Logger): Seq[ScriptedTest] = listTests(baseDirectory, _ => true, log) - def listTests(baseDirectory: File, - accept: ScriptedTest => Boolean, - log: Logger): Seq[ScriptedTest] = + + def listTests( + baseDirectory: File, + accept: ScriptedTest => Boolean, + log: Logger, + ): Seq[ScriptedTest] = (new ListTests(baseDirectory, accept, log)).listTests def parseTests(in: Seq[String]): Seq[ScriptedTest] = @@ -575,7 +579,8 @@ class ScriptedRunner { } private def isTestCompatible(resourceBaseDirectory: File, sbtVersion: String)( - test: ScriptedTest): Boolean = { + test: ScriptedTest + ): Boolean = { import sbt.internal.librarymanagement.cross.CrossVersionUtil.binarySbtVersion val buildProperties = new Properties() val testDir = new File(new File(resourceBaseDirectory, test.group), test.name) @@ -592,36 +597,38 @@ class ScriptedRunner { } final case class ScriptedTest(group: String, name: String) { - override def toString = group + "/" + name + override def toString = s"$group/$name" } -private[sbt] object ListTests { - def list(directory: File, filter: java.io.FileFilter) = wrapNull(directory.listFiles(filter)) -} -import ListTests._ -private[sbt] final class ListTests(baseDirectory: File, - accept: ScriptedTest => Boolean, - log: Logger) { + +private[sbt] final class ListTests( + baseDirectory: File, + accept: ScriptedTest => Boolean, + log: Logger, +) { + def filter = DirectoryFilter -- HiddenFileFilter + def listTests: Seq[ScriptedTest] = { - list(baseDirectory, filter) flatMap { group => + IO.listFiles(baseDirectory, filter) flatMap { group => val groupName = group.getName listTests(group).map(ScriptedTest(groupName, _)) } } + private[this] def listTests(group: File): Set[String] = { val groupName = group.getName - val allTests = list(group, filter) + val allTests = IO.listFiles(group, filter) if (allTests.isEmpty) { - log.warn("No tests in test group " + groupName) + log.warn(s"No tests in test group $groupName") Set.empty } else { val (included, skipped) = allTests.toList.partition(test => accept(ScriptedTest(groupName, test.getName))) if (included.isEmpty) - log.warn("Test group " + groupName + " skipped.") + log.warn(s"Test group $groupName skipped.") else if (skipped.nonEmpty) { - log.warn("Tests skipped in group " + group.getName + ":") - skipped.foreach(testName => log.warn(" " + testName.getName)) + log.warn(s"Tests skipped in group $groupName:") + skipped.foreach(testName => log.warn(s" ${testName.getName}")) } Set(included.map(_.getName): _*) } From 89e501443c40802f6825aa20860fa3404f34e762 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 25 Jan 2018 12:09:21 +0000 Subject: [PATCH 153/356] Cleanup SbtHandler --- .../scala/sbt/scriptedtest/SbtHandler.scala | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/SbtHandler.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/SbtHandler.scala index 9ba5be673..bf7174731 100644 --- a/scripted/sbt/src/main/scala/sbt/scriptedtest/SbtHandler.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/SbtHandler.scala @@ -22,41 +22,40 @@ final case class SbtInstance(process: Process, server: IPC.Server) final class SbtHandler(remoteSbtCreator: RemoteSbtCreator) extends StatementHandler { type State = Option[SbtInstance] + def initialState = None def apply(command: String, arguments: List[String], i: Option[SbtInstance]): Option[SbtInstance] = - onSbtInstance(i) { (process, server) => + onSbtInstance(i) { (_, server) => send((command :: arguments.map(escape)).mkString(" "), server) - receive(command + " failed", server) + receive(s"$command failed", server) } def onSbtInstance(i: Option[SbtInstance])(f: (Process, IPC.Server) => Unit): Option[SbtInstance] = i match { - case Some(SbtInstance(_, server)) if server.isClosed => - finish(i) - onNewSbtInstance(f) - case Some(SbtInstance(process, server)) => - f(process, server) - i - case None => - onNewSbtInstance(f) + case Some(SbtInstance(_, server)) if server.isClosed => finish(i); onNewSbtInstance(f) + case Some(SbtInstance(process, server)) => f(process, server); i + case None => onNewSbtInstance(f) } private[this] def onNewSbtInstance(f: (Process, IPC.Server) => Unit): Option[SbtInstance] = { val server = IPC.unmanagedServer - val p = try newRemote(server) - catch { case e: Throwable => server.close(); throw e } - val ai = Some(SbtInstance(p, server)) + val p = + try newRemote(server) + catch { case e: Throwable => server.close(); throw e } + val i = Some(SbtInstance(p, server)) try f(p, server) catch { case e: Throwable => // TODO: closing is necessary only because StatementHandler uses exceptions for signaling errors - finish(ai); throw e + finish(i) + throw e } - ai + i } - def finish(state: Option[SbtInstance]) = state match { + def finish(state: State) = state match { + case None => case Some(SbtInstance(process, server)) => try { send("exit", server) @@ -65,24 +64,28 @@ final class SbtHandler(remoteSbtCreator: RemoteSbtCreator) extends StatementHand } catch { case _: IOException => process.destroy() } - case None => } - def send(message: String, server: IPC.Server) = server.connection { _.send(message) } + + def send(message: String, server: IPC.Server) = server.connection(_.send(message)) + def receive(errorMessage: String, server: IPC.Server) = server.connection { ipc => val resultMessage = ipc.receive if (!resultMessage.toBoolean) throw new TestFailed(errorMessage) } + def newRemote(server: IPC.Server): Process = { val p = remoteSbtCreator.newRemote(server) try receive("Remote sbt initialization failed", server) catch { case _: SocketException => throw new TestFailed("Remote sbt initialization failed") } p } - import java.util.regex.Pattern.{ quote => q } + // if the argument contains spaces, enclose it in quotes, quoting backslashes and quotes - def escape(argument: String) = + def escape(argument: String) = { + import java.util.regex.Pattern.{ quote => q } if (argument.contains(" ")) "\"" + argument.replaceAll(q("""\"""), """\\""").replaceAll(q("\""), "\\\"") + "\"" else argument + } } From 2effe0845fb871be4877a6bfd57ee1347c1747f3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 25 Jan 2018 16:26:31 +0000 Subject: [PATCH 154/356] Cleanup IPC --- main-command/src/main/scala/xsbt/IPC.scala | 35 ++++++++++++++-------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/main-command/src/main/scala/xsbt/IPC.scala b/main-command/src/main/scala/xsbt/IPC.scala index 2b9750438..c964bca7b 100644 --- a/main-command/src/main/scala/xsbt/IPC.scala +++ b/main-command/src/main/scala/xsbt/IPC.scala @@ -10,47 +10,57 @@ package xsbt import java.io.{ BufferedReader, BufferedWriter, InputStreamReader, OutputStreamWriter } import java.net.{ InetAddress, ServerSocket, Socket } +import scala.annotation.tailrec import scala.util.control.NonFatal object IPC { private val portMin = 1025 private val portMax = 65536 - private val loopback = InetAddress.getByName(null) // loopback + private val loopback = InetAddress.getByName(null) - def client[T](port: Int)(f: IPC => T): T = - ipc(new Socket(loopback, port))(f) + def client[T](port: Int)(f: IPC => T): T = ipc(new Socket(loopback, port))(f) def pullServer[T](f: Server => T): T = { val server = makeServer - try { f(new Server(server)) } finally { server.close() } + try f(new Server(server)) + finally server.close() } + def unmanagedServer: Server = new Server(makeServer) + def makeServer: ServerSocket = { val random = new java.util.Random def nextPort = random.nextInt(portMax - portMin + 1) + portMin + def createServer(attempts: Int): ServerSocket = - if (attempts > 0) - try { new ServerSocket(nextPort, 1, loopback) } catch { - case NonFatal(_) => createServer(attempts - 1) - } else - sys.error("Could not connect to socket: maximum attempts exceeded") + if (attempts > 0) { + try new ServerSocket(nextPort, 1, loopback) + catch { case NonFatal(_) => createServer(attempts - 1) } + } else sys.error("Could not connect to socket: maximum attempts exceeded") + createServer(10) } + def server[T](f: IPC => Option[T]): T = serverImpl(makeServer, f) + def server[T](port: Int)(f: IPC => Option[T]): T = serverImpl(new ServerSocket(port, 1, loopback), f) + private def serverImpl[T](server: ServerSocket, f: IPC => Option[T]): T = { - def listen(): T = { + @tailrec def listen(): T = { ipc(server.accept())(f) match { case Some(done) => done case None => listen() } } - try { listen() } finally { server.close() } + try listen() + finally server.close() } + private def ipc[T](s: Socket)(f: IPC => T): T = - try { f(new IPC(s)) } finally { s.close() } + try f(new IPC(s)) + finally s.close() final class Server private[IPC] (s: ServerSocket) { def port = s.getLocalPort @@ -59,6 +69,7 @@ object IPC { def connection[T](f: IPC => T): T = IPC.ipc(s.accept())(f) } } + final class IPC private (s: Socket) { def port = s.getLocalPort private val in = new BufferedReader(new InputStreamReader(s.getInputStream)) From 685b416b8e5ecdab0d62c2751fcf0eeb68e88e12 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 26 Jan 2018 12:01:24 +0000 Subject: [PATCH 155/356] Cleanup Main --- main/src/main/scala/sbt/Main.scala | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 75401eacd..4f4ef57d6 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -52,7 +52,6 @@ import xsbti.compile.CompilerCache import scala.annotation.tailrec import sbt.io.IO import sbt.io.syntax._ -import StandardMain._ import java.io.{ File, IOException } import java.net.URI @@ -69,34 +68,35 @@ final class xMain extends xsbti.AppMain { import BasicCommandStrings.runEarly import BuiltinCommands.defaults import sbt.internal.CommandStrings.{ BootCommand, DefaultsCommand, InitCommand } - val state = initialState( + val state = StandardMain.initialState( configuration, Seq(defaults, early), runEarly(DefaultsCommand) :: runEarly(InitCommand) :: BootCommand :: Nil) - runManaged(state) + StandardMain.runManaged(state) } } final class ScriptMain extends xsbti.AppMain { def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = { import BasicCommandStrings.runEarly - runManaged( - initialState( - configuration, - BuiltinCommands.ScriptCommands, - runEarly(Level.Error.toString) :: Script.Name :: Nil - )) + val state = StandardMain.initialState( + configuration, + BuiltinCommands.ScriptCommands, + runEarly(Level.Error.toString) :: Script.Name :: Nil + ) + StandardMain.runManaged(state) } } final class ConsoleMain extends xsbti.AppMain { - def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = - runManaged( - initialState( - configuration, - BuiltinCommands.ConsoleCommands, - IvyConsole.Name :: Nil - )) + def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = { + val state = StandardMain.initialState( + configuration, + BuiltinCommands.ConsoleCommands, + IvyConsole.Name :: Nil + ) + StandardMain.runManaged(state) + } } object StandardMain { From 7b11729f8c625c9f0b689034eaedc73dbca1c692 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 26 Jan 2018 12:58:58 +0000 Subject: [PATCH 156/356] Cleanup State#process --- main-command/src/main/scala/sbt/State.scala | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/main-command/src/main/scala/sbt/State.scala b/main-command/src/main/scala/sbt/State.scala index 9d0e401c6..eb15168fe 100644 --- a/main-command/src/main/scala/sbt/State.scala +++ b/main-command/src/main/scala/sbt/State.scala @@ -238,14 +238,16 @@ object State { def process(f: (Exec, State) => State): State = { def runCmd(cmd: Exec, remainingCommands: List[Exec]) = { log.debug(s"> $cmd") - f(cmd, - s.copy(remainingCommands = remainingCommands, - currentCommand = Some(cmd), - history = cmd :: s.history)) + val s1 = s.copy( + remainingCommands = remainingCommands, + currentCommand = Some(cmd), + history = cmd :: s.history, + ) + f(cmd, s1) } s.remainingCommands match { - case List() => exit(true) - case List(x, xs @ _*) => runCmd(x, xs.toList) + case Nil => exit(true) + case x :: xs => runCmd(x, xs) } } def :::(newCommands: List[String]): State = ++:(newCommands map { Exec(_, s.source) }) From 621ad2b5532b3fcc2347178cf366ff25e986fb8f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 26 Jan 2018 14:54:39 +0000 Subject: [PATCH 157/356] Cleanup TaskNegSpec --- .../src/test/scala/sbt/std/neg/TaskNegSpec.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/main-settings/src/test/scala/sbt/std/neg/TaskNegSpec.scala b/main-settings/src/test/scala/sbt/std/neg/TaskNegSpec.scala index 75549783e..e3819d9eb 100644 --- a/main-settings/src/test/scala/sbt/std/neg/TaskNegSpec.scala +++ b/main-settings/src/test/scala/sbt/std/neg/TaskNegSpec.scala @@ -7,15 +7,19 @@ package sbt.std.neg +import scala.tools.reflect.ToolBoxError + import org.scalatest.FunSuite + import sbt.std.TaskLinterDSLFeedback import sbt.std.TestUtil._ class TaskNegSpec extends FunSuite { - import tools.reflect.ToolBoxError - def expectError(errorSnippet: String, - compileOptions: String = "", - baseCompileOptions: String = s"-cp $toolboxClasspath")(code: String) = { + def expectError( + errorSnippet: String, + compileOptions: String = "", + baseCompileOptions: String = s"-cp $toolboxClasspath", + )(code: String) = { val errorMessage = intercept[ToolBoxError] { eval(code, s"$compileOptions $baseCompileOptions") println(s"Test failed -- compilation was successful! Expected:\n$errorSnippet") From a69c3d6e13ce4a41e9fb6edc5dfdabdd89b7249b Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 26 Jan 2018 14:19:14 +0000 Subject: [PATCH 158/356] Format build.sbt --- build.sbt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index fbfc0ce51..9b39f5aaf 100644 --- a/build.sbt +++ b/build.sbt @@ -24,10 +24,12 @@ def buildLevelSettings: Seq[Setting[_]] = Developer("eed3si9n", "Eugene Yokota", "@eed3si9n", url("https://github.com/eed3si9n")), Developer("jsuereth", "Josh Suereth", "@jsuereth", url("https://github.com/jsuereth")), Developer("dwijnand", "Dale Wijnand", "@dwijnand", url("https://github.com/dwijnand")), - Developer("gkossakowski", - "Grzegorz Kossakowski", - "@gkossakowski", - url("https://github.com/gkossakowski")), + Developer( + "gkossakowski", + "Grzegorz Kossakowski", + "@gkossakowski", + url("https://github.com/gkossakowski") + ), Developer("Duhemm", "Martin Duhem", "@Duhemm", url("https://github.com/Duhemm")) ), homepage := Some(url("https://github.com/sbt/sbt")), From 3791e489b232e9e936d07077fdd6a216fc9392e9 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 1 Feb 2018 14:53:05 +0000 Subject: [PATCH 159/356] Use Def.settings in build.sbt --- build.sbt | 51 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/build.sbt b/build.sbt index 9b39f5aaf..7f08059b4 100644 --- a/build.sbt +++ b/build.sbt @@ -40,32 +40,31 @@ def buildLevelSettings: Seq[Setting[_]] = scalafmtVersion := "1.3.0", )) -def commonSettings: Seq[Setting[_]] = - Seq[SettingsDefinition]( - headerLicense := Some(HeaderLicense.Custom( - """|sbt - |Copyright 2011 - 2017, Lightbend, Inc. - |Copyright 2008 - 2010, Mark Harrah - |Licensed under BSD-3-Clause license (see LICENSE) - |""".stripMargin - )), - scalaVersion := baseScalaVersion, - componentID := None, - resolvers += Resolver.typesafeIvyRepo("releases"), - resolvers += Resolver.sonatypeRepo("snapshots"), - resolvers += "bintray-sbt-maven-releases" at "https://dl.bintray.com/sbt/maven-releases/", - addCompilerPlugin("org.spire-math" % "kind-projector" % "0.9.4" cross CrossVersion.binary), - concurrentRestrictions in Global += Util.testExclusiveRestriction, - testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"), - testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "2"), - javacOptions in compile ++= Seq("-Xlint", "-Xlint:-serial"), - crossScalaVersions := Seq(baseScalaVersion), - bintrayPackage := (bintrayPackage in ThisBuild).value, - bintrayRepository := (bintrayRepository in ThisBuild).value, - publishArtifact in Test := false, - fork in compile := true, - fork in run := true - ) flatMap (_.settings) +def commonSettings: Seq[Setting[_]] = Def settings ( + headerLicense := Some(HeaderLicense.Custom( + """|sbt + |Copyright 2011 - 2017, Lightbend, Inc. + |Copyright 2008 - 2010, Mark Harrah + |Licensed under BSD-3-Clause license (see LICENSE) + |""".stripMargin + )), + scalaVersion := baseScalaVersion, + componentID := None, + resolvers += Resolver.typesafeIvyRepo("releases"), + resolvers += Resolver.sonatypeRepo("snapshots"), + resolvers += "bintray-sbt-maven-releases" at "https://dl.bintray.com/sbt/maven-releases/", + addCompilerPlugin("org.spire-math" % "kind-projector" % "0.9.4" cross CrossVersion.binary), + concurrentRestrictions in Global += Util.testExclusiveRestriction, + testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"), + testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "2"), + javacOptions in compile ++= Seq("-Xlint", "-Xlint:-serial"), + crossScalaVersions := Seq(baseScalaVersion), + bintrayPackage := (bintrayPackage in ThisBuild).value, + bintrayRepository := (bintrayRepository in ThisBuild).value, + publishArtifact in Test := false, + fork in compile := true, + fork in run := true +) def minimalSettings: Seq[Setting[_]] = commonSettings ++ customCommands ++ From 359a0109fd5b5b87f90f4e9ef916914cb25cabdb Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 1 Feb 2018 17:00:45 +0000 Subject: [PATCH 160/356] Cleanup & simplify actions/generator --- .../actions/doc-scala-instance/build.sbt | 4 ++-- sbt/src/sbt-test/actions/generator/build.sbt | 22 +++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/sbt/src/sbt-test/actions/doc-scala-instance/build.sbt b/sbt/src/sbt-test/actions/doc-scala-instance/build.sbt index 68d4abb6e..497ab5d39 100644 --- a/sbt/src/sbt-test/actions/doc-scala-instance/build.sbt +++ b/sbt/src/sbt-test/actions/doc-scala-instance/build.sbt @@ -1,8 +1,8 @@ lazy val a = project.settings( scalaVersion := "2.12.2", - scalaInstance in (Compile,doc) := (scalaInstance in b).value, + scalaInstance in (Compile, doc) := (scalaInstance in b).value, // 2.10.1-only, so this will only succeed if `doc` recognizes the more specific scalaInstance scoped to `doc` - scalacOptions in (Compile,doc) += "-implicits" + scalacOptions in (Compile, doc) += "-implicits" ) lazy val b = project.settings( diff --git a/sbt/src/sbt-test/actions/generator/build.sbt b/sbt/src/sbt-test/actions/generator/build.sbt index c347b414e..04b2a1b48 100644 --- a/sbt/src/sbt-test/actions/generator/build.sbt +++ b/sbt/src/sbt-test/actions/generator/build.sbt @@ -1,13 +1,11 @@ -lazy val buildInfo = taskKey[Seq[File]]("The task that generates the build info.") +scalaVersion := "2.11.8" -lazy val root = (project in file(".")) - .settings( - scalaVersion := "2.11.8", - buildInfo := { - val x = sourceManaged.value / "BuildInfo.scala" - IO.write(x, """object BuildInfo""") - x :: Nil - }, - sourceGenerators in Compile += buildInfo, - sourceGenerators in Compile += Def.task { Nil } - ) +val buildInfo = taskKey[Seq[File]]("generates the build info") +buildInfo := { + val file = sourceManaged.value / "BuildInfo.scala" + IO.write(file, "object BuildInfo") + file :: Nil +} + +sourceGenerators in Compile += buildInfo +sourceGenerators in Compile += Def.task { Nil } From 4b36bca5ec6e37ac86655edf643b86f12ce6fb37 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 2 Feb 2018 14:35:47 +0000 Subject: [PATCH 161/356] Fix label so it's copy-and-paste-able --- .../sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index 69a856d89..dd9de5bc1 100644 --- a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -316,7 +316,7 @@ final class ScriptedTests( def runBatchTests = { groupedTests.map { case ((group, name), originalDir) => - val label = s"$group / $name" + val label = s"$group/$name" println(s"Running $label") // Copy test's contents and reload the sbt instance to pick them up IO.copyDirectory(originalDir, tempTestDir) From 26014a527ef681e5362d97d7eb7255ec71d85578 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 6 Mar 2018 19:20:31 +0000 Subject: [PATCH 162/356] Use Def.settings (bikeshed) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 7f08059b4..f313fb9fe 100644 --- a/build.sbt +++ b/build.sbt @@ -40,7 +40,7 @@ def buildLevelSettings: Seq[Setting[_]] = scalafmtVersion := "1.3.0", )) -def commonSettings: Seq[Setting[_]] = Def settings ( +def commonSettings: Seq[Setting[_]] = Def.settings( headerLicense := Some(HeaderLicense.Custom( """|sbt |Copyright 2011 - 2017, Lightbend, Inc. From 15f449849463a3e4e774877e51989be922988936 Mon Sep 17 00:00:00 2001 From: Maksym Fedorov Date: Tue, 6 Mar 2018 23:01:31 +0100 Subject: [PATCH 163/356] Add more configuration axis ScopeFilter factory methods --- main/src/main/scala/sbt/Defaults.scala | 11 +++++++++++ main/src/main/scala/sbt/ScopeFilter.scala | 12 +++++++++++- .../in_configurations_scope_filter_factories.md | 10 ++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 notes/1.1.2/in_configurations_scope_filter_factories.md diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index b9ab939ef..b67e62f52 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2705,6 +2705,17 @@ object Classpaths { visit(projectRef, conf) visited.toSeq } + + def interSortConfigurations( + projectRef: ProjectRef, + conf: Configuration, + data: Settings[Scope], + deps: BuildDependencies + ): Seq[(ProjectRef, ConfigRef)] = + interSort(projectRef, conf, data, deps).map { + case (projectRef, configName) => (projectRef, ConfigRef(configName)) + } + private[sbt] def unmanagedDependencies0(projectRef: ProjectRef, conf: Configuration, data: Settings[Scope], diff --git a/main/src/main/scala/sbt/ScopeFilter.scala b/main/src/main/scala/sbt/ScopeFilter.scala index a303ca09a..1139a176c 100644 --- a/main/src/main/scala/sbt/ScopeFilter.scala +++ b/main/src/main/scala/sbt/ScopeFilter.scala @@ -10,7 +10,7 @@ package sbt import sbt.internal.{ Load, LoadedBuildUnit } import sbt.internal.util.{ AttributeKey, Dag, Types } -import sbt.librarymanagement.Configuration +import sbt.librarymanagement.{ Configuration, ConfigRef } import Types.const import Def.Initialize @@ -154,6 +154,16 @@ object ScopeFilter { selectAxis[ConfigKey](const(c => cs(c.name))) } + def inConfigurationsByKeys(keys: ConfigKey*): ConfigurationFilter = { + val cs = keys.toSet + selectAxis[ConfigKey](const(cs)) + } + + def inConfigurationsByRefs(refs: ConfigRef*): ConfigurationFilter = { + val cs = refs.map(r => ConfigKey(r.name)).toSet + selectAxis[ConfigKey](const(cs)) + } + implicit def settingKeyAll[T](key: Initialize[T]): SettingKeyAll[T] = new SettingKeyAll[T](key) implicit def taskKeyAll[T](key: Initialize[Task[T]]): TaskKeyAll[T] = new TaskKeyAll[T](key) } diff --git a/notes/1.1.2/in_configurations_scope_filter_factories.md b/notes/1.1.2/in_configurations_scope_filter_factories.md new file mode 100644 index 000000000..3effa6ac7 --- /dev/null +++ b/notes/1.1.2/in_configurations_scope_filter_factories.md @@ -0,0 +1,10 @@ +### Improvements + +- Adds factory methods for Configuration axis scope filters + +### More ways to create ScopeFilter for Configuration axis + +To create configuration axis `ScopeFilter` one has to provide actual configurations +to filter by. However it's not always possible to get hold of one. For example +`Classpaths.interSort` returns configuration names. +For cases like that there are now `inConfigurationsByKeys` and `inConfigurationsByRefs` to create `ScopeFilter`'s From 251e5ab26eba540548a4104ffcf15ecd3718afb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Gon=C3=A7alves?= Date: Wed, 7 Mar 2018 00:27:22 +0000 Subject: [PATCH 164/356] Filter incompatible aggregates in cross switch commands --- main/src/main/scala/sbt/Cross.scala | 26 ++++++++++++++++--- notes/1.2.0/cross-strict-aggregation.md | 8 ++++++ .../cross-strict-aggregation/build.sbt | 13 ++++++++++ .../cross-strict-aggregation/core/A.scala | 5 ++++ .../cross-strict-aggregation/module/B.scala | 5 ++++ .../actions/cross-strict-aggregation/test | 5 ++++ 6 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 notes/1.2.0/cross-strict-aggregation.md create mode 100644 sbt/src/sbt-test/actions/cross-strict-aggregation/build.sbt create mode 100644 sbt/src/sbt-test/actions/cross-strict-aggregation/core/A.scala create mode 100644 sbt/src/sbt-test/actions/cross-strict-aggregation/module/B.scala create mode 100644 sbt/src/sbt-test/actions/cross-strict-aggregation/test diff --git a/main/src/main/scala/sbt/Cross.scala b/main/src/main/scala/sbt/Cross.scala index 5d3de17c0..dcff5d449 100644 --- a/main/src/main/scala/sbt/Cross.scala +++ b/main/src/main/scala/sbt/Cross.scala @@ -216,12 +216,30 @@ object Cross { Command.arb(requireSession(switchParser), switchHelp)(switchCommandImpl) private def switchCommandImpl(state: State, args: Switch): State = { - val switchedState = switchScalaVersion(args, state) + val x = Project.extract(state) + val (switchedState, affectedRefs) = switchScalaVersion(args, state) - args.command.toList ::: switchedState + val strictCmd = + if (args.version.force) { + // The Scala version was forced on the whole build, run as is + args.command + } else { + args.command.map { rawCmd => + parseCommand(rawCmd) match { + case Right(_) => rawCmd // A project is specified, run as is + case Left(cmd) => + resolveAggregates(x) + .intersect(affectedRefs) + .collect { case ProjectRef(_, proj) => s"$proj/$cmd" } + .mkString("all ", " ", "") + } + } + } + + strictCmd.toList ::: switchedState } - private def switchScalaVersion(switch: Switch, state: State): State = { + private def switchScalaVersion(switch: Switch, state: State): (State, Seq[ResolvedReference]) = { val extracted = Project.extract(state) import extracted._ @@ -291,7 +309,7 @@ object Cross { } } - setScalaVersionForProjects(version, instance, projects, state, extracted) + (setScalaVersionForProjects(version, instance, projects, state, extracted), projects.map(_._1)) } private def setScalaVersionForProjects( diff --git a/notes/1.2.0/cross-strict-aggregation.md b/notes/1.2.0/cross-strict-aggregation.md new file mode 100644 index 000000000..170207536 --- /dev/null +++ b/notes/1.2.0/cross-strict-aggregation.md @@ -0,0 +1,8 @@ +[@ruippeixotog]: https://github.com/ruippeixotog + +[#3698]: https://github.com/sbt/sbt/issues/3698 +[#3995]: https://github.com/sbt/sbt/pull/3995 + +### Improvements + +- Make `++ ` run `` only on compatible subprojects. [#3698][]/[#3995][] by [@ruippeixotog][] diff --git a/sbt/src/sbt-test/actions/cross-strict-aggregation/build.sbt b/sbt/src/sbt-test/actions/cross-strict-aggregation/build.sbt new file mode 100644 index 000000000..6c9759d4b --- /dev/null +++ b/sbt/src/sbt-test/actions/cross-strict-aggregation/build.sbt @@ -0,0 +1,13 @@ +lazy val root = (project in file(".")) + .aggregate(core, module) + +lazy val core = (project in file("core")) + .settings( + scalaVersion := "2.12.4", + crossScalaVersions := Seq("2.11.11", "2.12.4")) + +lazy val module = (project in file("module")) + .dependsOn(core) + .settings( + scalaVersion := "2.12.4", + crossScalaVersions := Seq("2.12.4")) diff --git a/sbt/src/sbt-test/actions/cross-strict-aggregation/core/A.scala b/sbt/src/sbt-test/actions/cross-strict-aggregation/core/A.scala new file mode 100644 index 000000000..6c542aa30 --- /dev/null +++ b/sbt/src/sbt-test/actions/cross-strict-aggregation/core/A.scala @@ -0,0 +1,5 @@ +package foo + +object Foo { + +} diff --git a/sbt/src/sbt-test/actions/cross-strict-aggregation/module/B.scala b/sbt/src/sbt-test/actions/cross-strict-aggregation/module/B.scala new file mode 100644 index 000000000..d262f7e5d --- /dev/null +++ b/sbt/src/sbt-test/actions/cross-strict-aggregation/module/B.scala @@ -0,0 +1,5 @@ +package foo.module + +object FooModule { + +} diff --git a/sbt/src/sbt-test/actions/cross-strict-aggregation/test b/sbt/src/sbt-test/actions/cross-strict-aggregation/test new file mode 100644 index 000000000..fb3a4d9cb --- /dev/null +++ b/sbt/src/sbt-test/actions/cross-strict-aggregation/test @@ -0,0 +1,5 @@ +> ++2.11.11 compile + +$ exists core/target/scala-2.11 +-$ exists module/target/scala-2.11 +-$ exists module/target/scala-2.12 From 1fef1a5a6a29605e2a0c15f4fac35749ee210936 Mon Sep 17 00:00:00 2001 From: Hiroshi Ito Date: Wed, 7 Mar 2018 11:14:49 +0900 Subject: [PATCH 165/356] Fix #3990: Add CLA instruction to CONTRIBUTING.md --- CONTRIBUTING.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 190d5a0cc..0aceadc45 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -246,3 +246,12 @@ cd vscode-sbt-scala/client $ vsce package $ vsce publish ``` + +## Signing the CLA + +Contributing to sbt requires you or your employer to sign the +[Lightbend Contributor License Agreement](https://www.lightbend.com/contribute/cla). + +To make it easier to respect our license agreements, we have added an sbt task +that takes care of adding the LICENSE headers to new files. Run `headerCreate` +and sbt will put a copyright notice into it. From 49b4c3a3e0c46266401ac1dd7e050f2f9b06cbc8 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 7 Mar 2018 15:31:24 -0500 Subject: [PATCH 166/356] mima --- build.sbt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.sbt b/build.sbt index bb6df2c9d..85e006027 100644 --- a/build.sbt +++ b/build.sbt @@ -203,6 +203,11 @@ lazy val testingProj = (project in file("testing")) sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats, mimaSettings, + mimaBinaryIssueFilters ++= Seq( + // private[sbt] + ProblemFilters.exclude[IncompatibleMethTypeProblem]("sbt.TestStatus.write"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("sbt.TestStatus.read"), + ), ) .configure(addSbtIO, addSbtCompilerClasspath, addSbtUtilLogging) From f24e5f21363f5dfaf1d92c3499c574071c9078d5 Mon Sep 17 00:00:00 2001 From: jvican Date: Wed, 7 Mar 2018 16:13:23 -0500 Subject: [PATCH 167/356] Add test to confirm leak is gone The following commit adds the test case used to report #3143 in a big projects with lots of cross versions and modules. The execution of the test is instant! --- sbt/src/sbt-test/project/cross-source/build.sbt | 8 ++++++++ .../project/cross-source/p1/src/main/scala-2.10/B.scala | 3 +++ .../project/cross-source/p1/src/main/scala/A.scala | 3 +++ .../project/cross-source/p2/src/main/scala-2.10/B.scala | 3 +++ .../project/cross-source/p2/src/main/scala/A.scala | 3 +++ .../project/cross-source/p3/src/main/scala-2.10/B.scala | 3 +++ .../project/cross-source/p3/src/main/scala/A.scala | 3 +++ .../project/cross-source/p4/src/main/scala-2.10/B.scala | 3 +++ .../project/cross-source/p4/src/main/scala/A.scala | 3 +++ sbt/src/sbt-test/project/cross-source/test | 3 +++ 10 files changed, 35 insertions(+) create mode 100644 sbt/src/sbt-test/project/cross-source/build.sbt create mode 100644 sbt/src/sbt-test/project/cross-source/p1/src/main/scala-2.10/B.scala create mode 100644 sbt/src/sbt-test/project/cross-source/p1/src/main/scala/A.scala create mode 100644 sbt/src/sbt-test/project/cross-source/p2/src/main/scala-2.10/B.scala create mode 100644 sbt/src/sbt-test/project/cross-source/p2/src/main/scala/A.scala create mode 100644 sbt/src/sbt-test/project/cross-source/p3/src/main/scala-2.10/B.scala create mode 100644 sbt/src/sbt-test/project/cross-source/p3/src/main/scala/A.scala create mode 100644 sbt/src/sbt-test/project/cross-source/p4/src/main/scala-2.10/B.scala create mode 100644 sbt/src/sbt-test/project/cross-source/p4/src/main/scala/A.scala create mode 100644 sbt/src/sbt-test/project/cross-source/test diff --git a/sbt/src/sbt-test/project/cross-source/build.sbt b/sbt/src/sbt-test/project/cross-source/build.sbt new file mode 100644 index 000000000..2f3894078 --- /dev/null +++ b/sbt/src/sbt-test/project/cross-source/build.sbt @@ -0,0 +1,8 @@ +val commonSettings = Seq( + crossScalaVersions := (0 to 6).map(i => s"2.10.$i") ++ (0 to 11).map(i => s"2.11.$i") ++ (0 to 2).map(i => s"2.12.$i") +) + +val p1 = project.in(file("p1")).settings(commonSettings) +val p2 = project.in(file("p2")).settings(commonSettings) +val p3 = project.in(file("p3")).settings(commonSettings) +val p4 = project.in(file("p4")).settings(commonSettings) diff --git a/sbt/src/sbt-test/project/cross-source/p1/src/main/scala-2.10/B.scala b/sbt/src/sbt-test/project/cross-source/p1/src/main/scala-2.10/B.scala new file mode 100644 index 000000000..fa8ad30ea --- /dev/null +++ b/sbt/src/sbt-test/project/cross-source/p1/src/main/scala-2.10/B.scala @@ -0,0 +1,3 @@ +object B { + def show(what: String): String = s"String interpolation is ${what.toUpperCase}!" +} diff --git a/sbt/src/sbt-test/project/cross-source/p1/src/main/scala/A.scala b/sbt/src/sbt-test/project/cross-source/p1/src/main/scala/A.scala new file mode 100644 index 000000000..8b55ead57 --- /dev/null +++ b/sbt/src/sbt-test/project/cross-source/p1/src/main/scala/A.scala @@ -0,0 +1,3 @@ +class A { + def show(what: String): Unit = println(what) +} diff --git a/sbt/src/sbt-test/project/cross-source/p2/src/main/scala-2.10/B.scala b/sbt/src/sbt-test/project/cross-source/p2/src/main/scala-2.10/B.scala new file mode 100644 index 000000000..fa8ad30ea --- /dev/null +++ b/sbt/src/sbt-test/project/cross-source/p2/src/main/scala-2.10/B.scala @@ -0,0 +1,3 @@ +object B { + def show(what: String): String = s"String interpolation is ${what.toUpperCase}!" +} diff --git a/sbt/src/sbt-test/project/cross-source/p2/src/main/scala/A.scala b/sbt/src/sbt-test/project/cross-source/p2/src/main/scala/A.scala new file mode 100644 index 000000000..8b55ead57 --- /dev/null +++ b/sbt/src/sbt-test/project/cross-source/p2/src/main/scala/A.scala @@ -0,0 +1,3 @@ +class A { + def show(what: String): Unit = println(what) +} diff --git a/sbt/src/sbt-test/project/cross-source/p3/src/main/scala-2.10/B.scala b/sbt/src/sbt-test/project/cross-source/p3/src/main/scala-2.10/B.scala new file mode 100644 index 000000000..fa8ad30ea --- /dev/null +++ b/sbt/src/sbt-test/project/cross-source/p3/src/main/scala-2.10/B.scala @@ -0,0 +1,3 @@ +object B { + def show(what: String): String = s"String interpolation is ${what.toUpperCase}!" +} diff --git a/sbt/src/sbt-test/project/cross-source/p3/src/main/scala/A.scala b/sbt/src/sbt-test/project/cross-source/p3/src/main/scala/A.scala new file mode 100644 index 000000000..8b55ead57 --- /dev/null +++ b/sbt/src/sbt-test/project/cross-source/p3/src/main/scala/A.scala @@ -0,0 +1,3 @@ +class A { + def show(what: String): Unit = println(what) +} diff --git a/sbt/src/sbt-test/project/cross-source/p4/src/main/scala-2.10/B.scala b/sbt/src/sbt-test/project/cross-source/p4/src/main/scala-2.10/B.scala new file mode 100644 index 000000000..fa8ad30ea --- /dev/null +++ b/sbt/src/sbt-test/project/cross-source/p4/src/main/scala-2.10/B.scala @@ -0,0 +1,3 @@ +object B { + def show(what: String): String = s"String interpolation is ${what.toUpperCase}!" +} diff --git a/sbt/src/sbt-test/project/cross-source/p4/src/main/scala/A.scala b/sbt/src/sbt-test/project/cross-source/p4/src/main/scala/A.scala new file mode 100644 index 000000000..8b55ead57 --- /dev/null +++ b/sbt/src/sbt-test/project/cross-source/p4/src/main/scala/A.scala @@ -0,0 +1,3 @@ +class A { + def show(what: String): Unit = println(what) +} diff --git a/sbt/src/sbt-test/project/cross-source/test b/sbt/src/sbt-test/project/cross-source/test new file mode 100644 index 000000000..17db5c8da --- /dev/null +++ b/sbt/src/sbt-test/project/cross-source/test @@ -0,0 +1,3 @@ +# https://github.com/sbt/sbt/issues/3143 +> crossScalaVersions +> +version From dc4f93a7c3ecf2e2491aceaaecdbcec8fc8f7df9 Mon Sep 17 00:00:00 2001 From: exoego Date: Thu, 8 Mar 2018 08:14:39 +0900 Subject: [PATCH 168/356] Remove unnecessary GlobalScope --- main/src/main/scala/sbt/Defaults.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 2ab1b4259..b48c007f2 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1999,7 +1999,7 @@ object Classpaths { ConflictWarning(conflictWarning.value, report, log) report }, - evictionWarningOptions in update := (evictionWarningOptions in GlobalScope).value, + evictionWarningOptions in update := evictionWarningOptions.value, evictionWarningOptions in evicted := EvictionWarningOptions.full, evicted := { import ShowLines._ From ed5a8c118b23b2df9b13eb5687c3956439c1aa6a Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 8 Mar 2018 12:39:43 +0000 Subject: [PATCH 169/356] Upgrade to contraband 0.3.3 --- main-command/src/main/contraband-scala/sbt/Exec.scala | 4 ++-- project/plugins.sbt | 2 +- .../src/main/contraband-scala/sbt/protocol/ExecCommand.scala | 2 +- run/src/main/contraband-scala/sbt/ForkOptions.scala | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/main-command/src/main/contraband-scala/sbt/Exec.scala b/main-command/src/main/contraband-scala/sbt/Exec.scala index 829c885ae..a3a6a38e7 100644 --- a/main-command/src/main/contraband-scala/sbt/Exec.scala +++ b/main-command/src/main/contraband-scala/sbt/Exec.scala @@ -42,8 +42,8 @@ final class Exec private ( } object Exec { def newExecId: String = java.util.UUID.randomUUID.toString - def apply(commandLine: String, source: Option[sbt.CommandSource]): Exec = new Exec(commandLine, None, source) - def apply(commandLine: String, source: sbt.CommandSource): Exec = new Exec(commandLine, None, Option(source)) + def apply(commandLine: String, source: Option[sbt.CommandSource]): Exec = new Exec(commandLine, source) + def apply(commandLine: String, source: sbt.CommandSource): Exec = new Exec(commandLine, Option(source)) def apply(commandLine: String, execId: Option[String], source: Option[sbt.CommandSource]): Exec = new Exec(commandLine, execId, source) def apply(commandLine: String, execId: String, source: sbt.CommandSource): Exec = new Exec(commandLine, Option(execId), Option(source)) } diff --git a/project/plugins.sbt b/project/plugins.sbt index 8988c3d05..5ed8cbf57 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,6 +2,6 @@ scalaVersion := "2.12.4" scalacOptions ++= Seq("-feature", "-language:postfixOps") addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.5") -addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.2") +addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.3") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.8.0") diff --git a/protocol/src/main/contraband-scala/sbt/protocol/ExecCommand.scala b/protocol/src/main/contraband-scala/sbt/protocol/ExecCommand.scala index df022897d..142e7282b 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/ExecCommand.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/ExecCommand.scala @@ -36,7 +36,7 @@ final class ExecCommand private ( } object ExecCommand { - def apply(commandLine: String): ExecCommand = new ExecCommand(commandLine, None) + def apply(commandLine: String): ExecCommand = new ExecCommand(commandLine) def apply(commandLine: String, execId: Option[String]): ExecCommand = new ExecCommand(commandLine, execId) def apply(commandLine: String, execId: String): ExecCommand = new ExecCommand(commandLine, Option(execId)) } diff --git a/run/src/main/contraband-scala/sbt/ForkOptions.scala b/run/src/main/contraband-scala/sbt/ForkOptions.scala index ece8ca699..e20277b72 100644 --- a/run/src/main/contraband-scala/sbt/ForkOptions.scala +++ b/run/src/main/contraband-scala/sbt/ForkOptions.scala @@ -79,7 +79,7 @@ final class ForkOptions private ( } object ForkOptions { - def apply(): ForkOptions = new ForkOptions(None, None, Vector(), None, Vector(), false, Map()) + def apply(): ForkOptions = new ForkOptions() def apply(javaHome: Option[java.io.File], outputStrategy: Option[sbt.OutputStrategy], bootJars: Vector[java.io.File], workingDirectory: Option[java.io.File], runJVMOptions: Vector[String], connectInput: Boolean, envVars: scala.collection.immutable.Map[String, String]): ForkOptions = new ForkOptions(javaHome, outputStrategy, bootJars, workingDirectory, runJVMOptions, connectInput, envVars) def apply(javaHome: java.io.File, outputStrategy: sbt.OutputStrategy, bootJars: Vector[java.io.File], workingDirectory: java.io.File, runJVMOptions: Vector[String], connectInput: Boolean, envVars: scala.collection.immutable.Map[String, String]): ForkOptions = new ForkOptions(Option(javaHome), Option(outputStrategy), bootJars, Option(workingDirectory), runJVMOptions, connectInput, envVars) } From 5df1d8e23f8283fe56786784339d1ff1fcce6f8e Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Fri, 1 Dec 2017 12:39:59 -0800 Subject: [PATCH 170/356] Cache watch service I noticed that my custom WatchService was never cleaned up by sbt and realized that after every build we were making a new WatchService. At the same time, we were reusing the WatchState from the previous run, which was using the original WatchService. This was particularly problematic because it prevented us from registering any paths with the new watch service. This may have prevented some of the file updates from being seen by the watch service. Moreover, because we lost the reference to the original WatchService, there was no way to clean it up, which was a resource leak. May be related to #3775, #3695 --- main-command/src/main/scala/sbt/Watched.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index fa1dee03f..8b8385b61 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -94,7 +94,7 @@ object Watched { @tailrec def shouldTerminate: Boolean = (System.in.available > 0) && (watched.terminateWatch(System.in.read()) || shouldTerminate) val sources = watched.watchSources(s) - val service = watched.watchService() + val service = s get ContinuousWatchService getOrElse watched.watchService() val watchState = s get ContinuousState getOrElse WatchState.empty(service, sources) if (watchState.count > 0) @@ -115,15 +115,21 @@ object Watched { if (triggered) { printIfDefined(watched triggeredMessage newWatchState) - (ClearOnFailure :: next :: FailureWall :: repeat :: s).put(ContinuousState, newWatchState) + (ClearOnFailure :: next :: FailureWall :: repeat :: s) + .put(ContinuousState, newWatchState) + .put(ContinuousWatchService, service) } else { while (System.in.available() > 0) System.in.read() service.close() - s.remove(ContinuousState) + s.remove(ContinuousState).remove(ContinuousWatchService) } } val ContinuousState = AttributeKey[WatchState]("watch state", "Internal: tracks state for continuous execution.") + + val ContinuousWatchService = + AttributeKey[WatchService]("watch service", + "Internal: tracks watch service for continuous execution.") val Configuration = AttributeKey[Watched]("watched-configuration", "Configures continuous execution.") From b82a1870a7783b57a75d79c6162120c05cb38c54 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 10 Mar 2018 16:50:03 -0500 Subject: [PATCH 171/356] add tests around scope delegation --- main/src/test/scala/Delegates.scala | 51 +++++++++++++++- .../test/scala/sbt/internal/TestBuild.scala | 59 ++++++++++--------- 2 files changed, 81 insertions(+), 29 deletions(-) diff --git a/main/src/test/scala/Delegates.scala b/main/src/test/scala/Delegates.scala index 492cdcdd9..11a511ad4 100644 --- a/main/src/test/scala/Delegates.scala +++ b/main/src/test/scala/Delegates.scala @@ -16,10 +16,10 @@ import Prop._ import Gen._ object Delegates extends Properties("delegates") { - property("generate non-empty configs") = forAll { (c: Seq[Configuration]) => + property("generate non-empty configs") = forAll { (c: Vector[Configuration]) => c.nonEmpty } - property("generate non-empty tasks") = forAll { (t: Seq[Taskk]) => + property("generate non-empty tasks") = forAll { (t: Vector[Taskk]) => t.nonEmpty } @@ -46,6 +46,7 @@ object Delegates extends Properties("delegates") { global forall { _ == Zero } } } + property("Initial scope present with all combinations of Global axes") = allAxes( globalCombinations) @@ -54,12 +55,58 @@ object Delegates extends Properties("delegates") { ds.head == scope } } + property("global scope last") = forAll { (keys: Keys) => allDelegates(keys) { (_, ds) => ds.last == Scope.GlobalScope } } + property("Project axis delegates to BuildRef then Zero") = forAll { (keys: Keys) => + allDelegates(keys) { (key, ds) => + key.project match { + case Zero => true // filtering out of testing + case Select(ProjectRef(uri, _)) => + val buildScoped = key.copy(project = Select(BuildRef(uri))) + val idxKey = ds.indexOf(key) + val idxB = ds.indexOf(buildScoped) + val z = key.copy(project = Zero) + val idxZ = ds.indexOf(z) + if (z == Scope.GlobalScope) true + else { + (s"idxKey = $idxKey; idxB = $idxB; idxZ = $idxZ") |: + (idxKey < idxB) && (idxB < idxZ) + } + case Select(BuildRef(_)) => + ds.indexOf(key) < ds.indexOf(key.copy(project = Zero)) + } + } + } + + property("Config axis delegates to parent configuration") = forAll { (keys: Keys) => + allDelegates(keys) { (key, ds) => + key.config match { + case Zero => true + case Select(config) if key.project.isSelect => + val p = key.project.toOption.get + val r = keys.env.resolve(p) + val proj = keys.env.projectFor(r) + val inh: Vector[ConfigKey] = keys.env.inheritConfig(r, config) + val conf = proj.confMap(config.name) + if (inh.isEmpty) true + else { + val idxKey = ds.indexOf(key) + val parent = inh.head + val a = key.copy(config = Select(parent)) + val idxA = ds.indexOf(a) + (s"idxKey = $idxKey; a = $a; idxA = $idxA") |: + idxKey < idxA + } + case _ => true + } + } + } + def allAxes(f: (Scope, Seq[Scope], Scope => ScopeAxis[_]) => Prop): Prop = forAll { (keys: Keys) => allDelegates(keys) { (s, ds) => diff --git a/main/src/test/scala/sbt/internal/TestBuild.scala b/main/src/test/scala/sbt/internal/TestBuild.scala index 7b8a70db5..7b53b1c7c 100644 --- a/main/src/test/scala/sbt/internal/TestBuild.scala +++ b/main/src/test/scala/sbt/internal/TestBuild.scala @@ -113,7 +113,7 @@ abstract class TestBuild { (taskAxes, zero.toSet, single.toSet, multi.toSet) } } - final class Env(val builds: Seq[Build], val tasks: Seq[Taskk]) { + final class Env(val builds: Vector[Build], val tasks: Vector[Taskk]) { override def toString = "Env:\n " + " Tasks:\n " + tasks.mkString("\n ") + "\n" + builds.mkString("\n ") val root = builds.head @@ -129,20 +129,21 @@ abstract class TestBuild { def inheritConfig(ref: ResolvedReference, config: ConfigKey) = projectFor(ref).confMap(config.name).extendsConfigs map toConfigKey def inheritTask(task: AttributeKey[_]) = taskMap.get(task) match { - case None => Nil; case Some(t) => t.delegates map getKey + case None => Vector() + case Some(t) => t.delegates.toVector map getKey } - def inheritProject(ref: ProjectRef) = project(ref).delegates + def inheritProject(ref: ProjectRef) = project(ref).delegates.toVector def resolve(ref: Reference) = Scope.resolveReference(root.uri, rootProject, ref) lazy val delegates: Scope => Seq[Scope] = Scope.delegates( allProjects, - (_: Proj).configurations.map(toConfigKey), + (_: Proj).configurations.toVector.map(toConfigKey), resolve, uri => buildMap(uri).root.id, inheritProject, inheritConfig, inheritTask, - (ref, mp) => Nil + (ref, mp) => Vector() ) lazy val allFullScopes: Seq[Scope] = for { @@ -262,13 +263,14 @@ abstract class TestBuild { implicit lazy val uriGen: Gen[URI] = for (sch <- idGen; ssp <- idGen; frag <- optIDGen) yield new URI(sch, ssp, frag.orNull) - implicit def envGen(implicit bGen: Gen[Build], tasks: Gen[Seq[Taskk]]): Gen[Env] = - for (i <- MaxBuildsGen; bs <- listOfN(i, bGen); ts <- tasks) yield new Env(bs, ts) + implicit def envGen(implicit bGen: Gen[Build], tasks: Gen[Vector[Taskk]]): Gen[Env] = + for (i <- MaxBuildsGen; bs <- containerOfN[Vector, Build](i, bGen); ts <- tasks) + yield new Env(bs, ts) implicit def buildGen(implicit uGen: Gen[URI], pGen: URI => Gen[Seq[Proj]]): Gen[Build] = for (u <- uGen; ps <- pGen(u)) yield new Build(u, ps) - def nGen[T](igen: Gen[Int])(implicit g: Gen[T]): Gen[List[T]] = igen flatMap { ig => - listOfN(ig, g) + def nGen[T](igen: Gen[Int])(implicit g: Gen[T]): Gen[Vector[T]] = igen flatMap { ig => + containerOfN[Vector, T](ig, g) } implicit def genProjects(build: URI)(implicit genID: Gen[String], @@ -285,34 +287,37 @@ abstract class TestBuild { def genConfigs(implicit genName: Gen[String], maxDeps: Gen[Int], - count: Gen[Int]): Gen[Seq[Configuration]] = + count: Gen[Int]): Gen[Vector[Configuration]] = genAcyclicDirect[Configuration, String](maxDeps, genName, count)( (key, deps) => Configuration .of(key.capitalize, key) .withExtendsConfigs(deps.toVector)) - def genTasks(implicit genName: Gen[String], maxDeps: Gen[Int], count: Gen[Int]): Gen[Seq[Taskk]] = + def genTasks(implicit genName: Gen[String], + maxDeps: Gen[Int], + count: Gen[Int]): Gen[Vector[Taskk]] = genAcyclicDirect[Taskk, String](maxDeps, genName, count)((key, deps) => new Taskk(AttributeKey[String](key), deps)) def genAcyclicDirect[A, T](maxDeps: Gen[Int], keyGen: Gen[T], max: Gen[Int])( - make: (T, Seq[A]) => A): Gen[Seq[A]] = + make: (T, Vector[A]) => A): Gen[Vector[A]] = genAcyclic[A, T](maxDeps, keyGen, max) { t => Gen.const { deps => - make(t, deps) + make(t, deps.toVector) } } def genAcyclic[A, T](maxDeps: Gen[Int], keyGen: Gen[T], max: Gen[Int])( - make: T => Gen[Seq[A] => A]): Gen[Seq[A]] = + make: T => Gen[Vector[A] => A]): Gen[Vector[A]] = max flatMap { count => - listOfN(count, keyGen) flatMap { keys => + containerOfN[Vector, T](count, keyGen) flatMap { keys => genAcyclic(maxDeps, keys.distinct)(make) } } - def genAcyclic[A, T](maxDeps: Gen[Int], keys: List[T])(make: T => Gen[Seq[A] => A]): Gen[Seq[A]] = - genAcyclic(maxDeps, keys, Nil) flatMap { pairs => + def genAcyclic[A, T](maxDeps: Gen[Int], keys: Vector[T])( + make: T => Gen[Vector[A] => A]): Gen[Vector[A]] = + genAcyclic(maxDeps, keys, Vector()) flatMap { pairs => sequence(pairs.map { case (key, deps) => mapMake(key, deps, make) }) flatMap { inputs => val made = new collection.mutable.HashMap[T, A] for ((key, deps, mk) <- inputs) @@ -321,25 +326,25 @@ abstract class TestBuild { } } - def mapMake[A, T](key: T, deps: Seq[T], make: T => Gen[Seq[A] => A]): Gen[Inputs[A, T]] = - make(key) map { (mk: Seq[A] => A) => + def mapMake[A, T](key: T, deps: Vector[T], make: T => Gen[Vector[A] => A]): Gen[Inputs[A, T]] = + make(key) map { (mk: Vector[A] => A) => (key, deps, mk) } def genAcyclic[T](maxDeps: Gen[Int], - names: List[T], - acc: List[Gen[(T, Seq[T])]]): Gen[Seq[(T, Seq[T])]] = + names: Vector[T], + acc: Vector[Gen[(T, Vector[T])]]): Gen[Vector[(T, Vector[T])]] = names match { - case Nil => sequence(acc) - case x :: xs => + case Vector() => sequence(acc) + case Vector(x, xs @ _*) => val next = for (depCount <- maxDeps; d <- pick(depCount min xs.size, xs)) - yield (x, d.toList) - genAcyclic(maxDeps, xs, next :: acc) + yield (x, d.toVector) + genAcyclic(maxDeps, xs.toVector, next +: acc) } - def sequence[T](gs: Seq[Gen[T]]): Gen[Seq[T]] = Gen.parameterized { prms => + def sequence[T](gs: Vector[Gen[T]]): Gen[Vector[T]] = Gen.parameterized { prms => delay(gs map { g => g(prms, seed) getOrElse sys.error("failed generator") }) } - type Inputs[A, T] = (T, Seq[T], Seq[A] => A) + type Inputs[A, T] = (T, Vector[T], Vector[A] => A) } From d2f2a90d5e24d8bb99baf5dea944f7c3a17267be Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 9 Mar 2018 23:46:42 -0500 Subject: [PATCH 172/356] handroll for-expression Ref https://github.com/sbt/sbt/pull/3979 Based on #3979 this handrolls the for-express using while. --- main-settings/src/main/scala/sbt/Scope.scala | 59 +++++++++++++++----- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/main-settings/src/main/scala/sbt/Scope.scala b/main-settings/src/main/scala/sbt/Scope.scala index 0bd3e27c6..10d49dfaf 100644 --- a/main-settings/src/main/scala/sbt/Scope.scala +++ b/main-settings/src/main/scala/sbt/Scope.scala @@ -266,20 +266,47 @@ object Scope { )(rawScope: Scope): Seq[Scope] = { val scope = Scope.replaceThis(GlobalScope)(rawScope) - def nonProjectScopes(resolvedProj: ResolvedReference)(px: ScopeAxis[ResolvedReference]) = { - val p = px.toOption getOrElse resolvedProj - val configProj = p match { - case pr: ProjectRef => pr; case br: BuildRef => ProjectRef(br.build, rootProject(br.build)) - } - val cLin = scope.config match { - case Select(conf) => index.config(configProj, conf); case _ => withZeroAxis(scope.config) - } + // This is a hot method that gets called many times + def exandDelegateScopes(resolvedProj: ResolvedReference)( + pLin: Seq[ScopeAxis[ResolvedReference]]): Vector[Scope] = { val tLin = scope.task match { - case t @ Select(_) => linearize(t)(taskInherit); case _ => withZeroAxis(scope.task) + case t @ Select(_) => linearize(t)(taskInherit) + case _ => withZeroAxis(scope.task) } - val eLin = withZeroAxis(scope.extra) - for (c <- cLin; t <- tLin; e <- eLin) yield Scope(px, c, t, e) + // val eLin = withZeroAxis(scope.extra) + // The following while loops handroll the nested for-expression + flatMap + // projAxes flatMap nonProjectScopes(resolvedProj) + // ... + // for (c <- cLin; t <- tLin; e <- eLin) yield Scope(px, c, t, e) + val res = Vector.newBuilder[Scope] + val pIt = pLin.iterator + while (pIt.hasNext) { + val px = pIt.next() + val p = px.toOption getOrElse resolvedProj + val configProj = p match { + case pr: ProjectRef => pr + case br: BuildRef => ProjectRef(br.build, rootProject(br.build)) + } + val cLin = scope.config match { + case Select(conf) => index.config(configProj, conf) + case _ => withZeroAxis(scope.config) + } + val cLinIt = cLin.iterator + while (cLinIt.hasNext) { + val c = cLinIt.next() + val tLinIt = tLin.iterator + while (tLinIt.hasNext) { + val t = tLinIt.next() + if (scope.extra.isSelect) { + res += Scope(px, c, t, scope.extra) + } + res += Scope(px, c, t, Zero) + } + } + } + res.result() } + scope.project match { case Zero | This => globalProjectDelegates(scope) case Select(proj) => @@ -287,15 +314,17 @@ object Scope { val projAxes: Seq[ScopeAxis[ResolvedReference]] = resolvedProj match { case pr: ProjectRef => index.project(pr) - case br: BuildRef => Select(br) :: Zero :: Nil + case br: BuildRef => List(Select(br), Zero) } - projAxes flatMap nonProjectScopes(resolvedProj) + exandDelegateScopes(resolvedProj)(projAxes) } } + private val zeroL = List(Zero) def withZeroAxis[T](base: ScopeAxis[T]): Seq[ScopeAxis[T]] = - if (base.isSelect) base :: Zero :: Nil - else Zero :: Nil + if (base.isSelect) List(base, Zero) + else zeroL + def withGlobalScope(base: Scope): Seq[Scope] = if (base == GlobalScope) GlobalScope :: Nil else base :: GlobalScope :: Nil def withRawBuilds(ps: Seq[ScopeAxis[ProjectRef]]): Seq[ScopeAxis[ResolvedReference]] = From 07e26a1dcfb3cc8431576f18369c090c4db855e3 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 10 Mar 2018 18:40:24 -0500 Subject: [PATCH 173/356] don't use toStream Ref #3979 toStream doesn't help performance. --- .../src/main/scala/sbt/internal/util/Settings.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/Settings.scala b/internal/util-collection/src/main/scala/sbt/internal/util/Settings.scala index 910e1c089..0070f966b 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/Settings.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/Settings.scala @@ -35,10 +35,10 @@ private final class Settings0[Scope]( data.flatMap { case (scope, map) => map.keys.map(k => f(scope, k)) }.toSeq def get[T](scope: Scope, key: AttributeKey[T]): Option[T] = - delegates(scope).toStream.flatMap(sc => getDirect(sc, key)).headOption + delegates(scope).flatMap(sc => getDirect(sc, key)).headOption def definingScope(scope: Scope, key: AttributeKey[_]): Option[Scope] = - delegates(scope).toStream.find(sc => getDirect(sc, key).isDefined) + delegates(scope).find(sc => getDirect(sc, key).isDefined) def getDirect[T](scope: Scope, key: AttributeKey[T]): Option[T] = (data get scope).flatMap(_ get key) From dd4de14593f1a68258d10a1fb2d8a9c1d1e5f6c3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 12 Mar 2018 15:18:42 +0000 Subject: [PATCH 174/356] Upgrade to contraband 0.4.0 --- build.sbt | 60 +++++++++++++++++++ .../contraband-scala/sbt/CommandSource.scala | 2 +- .../src/main/contraband-scala/sbt/Exec.scala | 2 +- .../scala/sbt/internal/server/Server.scala | 3 +- project/plugins.sbt | 2 +- .../langserver/ClientCapabilities.scala | 2 +- .../sbt/internal/langserver/Diagnostic.scala | 2 +- .../langserver/InitializeParams.scala | 2 +- .../langserver/InitializeResult.scala | 2 +- .../sbt/internal/langserver/Location.scala | 2 +- .../langserver/LogMessageParams.scala | 2 +- .../sbt/internal/langserver/Position.scala | 2 +- .../langserver/PublishDiagnosticsParams.scala | 2 +- .../sbt/internal/langserver/Range.scala | 2 +- .../sbt/internal/langserver/SaveOptions.scala | 2 +- .../internal/langserver/SbtExecParams.scala | 2 +- .../langserver/ServerCapabilities.scala | 2 +- .../langserver/TextDocumentIdentifier.scala | 2 +- .../TextDocumentPositionParams.scala | 2 +- .../langserver/TextDocumentSyncOptions.scala | 2 +- .../internal/protocol/InitializeOption.scala | 2 +- .../protocol/JsonRpcNotificationMessage.scala | 2 +- .../protocol/JsonRpcRequestMessage.scala | 2 +- .../protocol/JsonRpcResponseError.scala | 2 +- .../protocol/JsonRpcResponseMessage.scala | 2 +- .../sbt/internal/protocol/PortFile.scala | 2 +- .../sbt/internal/protocol/TokenFile.scala | 2 +- .../sbt/protocol/ChannelAcceptedEvent.scala | 2 +- .../sbt/protocol/ExecCommand.scala | 2 +- .../sbt/protocol/ExecStatusEvent.scala | 2 +- .../sbt/protocol/ExecutionEvent.scala | 2 +- .../sbt/protocol/InitCommand.scala | 2 +- .../sbt/protocol/LogEvent.scala | 2 +- .../sbt/protocol/SettingQuery.scala | 2 +- .../sbt/protocol/SettingQueryFailure.scala | 2 +- .../sbt/protocol/SettingQuerySuccess.scala | 2 +- .../contraband-scala/sbt/ForkOptions.scala | 2 +- run/src/main/scala/sbt/OutputStrategy.scala | 6 +- .../testing/EndTestGroupErrorEvent.scala | 2 +- .../protocol/testing/EndTestGroupEvent.scala | 2 +- .../testing/StartTestGroupEvent.scala | 2 +- .../protocol/testing/TestCompleteEvent.scala | 2 +- .../sbt/protocol/testing/TestInitEvent.scala | 2 +- .../sbt/protocol/testing/TestItemDetail.scala | 2 +- .../sbt/protocol/testing/TestItemEvent.scala | 2 +- .../protocol/testing/TestStringEvent.scala | 2 +- 46 files changed, 108 insertions(+), 47 deletions(-) diff --git a/build.sbt b/build.sbt index bb6df2c9d..5e50c04aa 100644 --- a/build.sbt +++ b/build.sbt @@ -203,6 +203,24 @@ lazy val testingProj = (project in file("testing")) sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats, mimaSettings, + mimaBinaryIssueFilters ++= Seq( + // copy method was never meant to be public + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.EndTestGroupErrorEvent.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.EndTestGroupErrorEvent.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.EndTestGroupEvent.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.EndTestGroupEvent.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.StartTestGroupEvent.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.StartTestGroupEvent.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestCompleteEvent.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestCompleteEvent.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestInitEvent.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestItemDetail.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestItemDetail.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestItemEvent.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestItemEvent.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestStringEvent.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestStringEvent.copy$default$1"), + ) ) .configure(addSbtIO, addSbtCompilerClasspath, addSbtUtilLogging) @@ -251,6 +269,17 @@ lazy val runProj = (project in file("run")) baseDirectory.value / "src" / "main" / "contraband-scala", sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", mimaSettings, + mimaBinaryIssueFilters ++= Seq( + // copy method was never meant to be public + exclude[DirectMissingMethodProblem]("sbt.ForkOptions.copy"), + exclude[DirectMissingMethodProblem]("sbt.ForkOptions.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.OutputStrategy#BufferedOutput.copy"), + exclude[DirectMissingMethodProblem]("sbt.OutputStrategy#BufferedOutput.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.OutputStrategy#CustomOutput.copy"), + exclude[DirectMissingMethodProblem]("sbt.OutputStrategy#CustomOutput.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.OutputStrategy#LoggedOutput.copy"), + exclude[DirectMissingMethodProblem]("sbt.OutputStrategy#LoggedOutput.copy$default$*"), + ) ) .configure(addSbtIO, addSbtUtilLogging, addSbtCompilerClasspath) @@ -307,6 +336,31 @@ lazy val protocolProj = (project in file("protocol")) sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats, mimaSettings, + mimaBinaryIssueFilters ++= Seq( + // copy method was never meant to be public + exclude[DirectMissingMethodProblem]("sbt.protocol.ChannelAcceptedEvent.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.ChannelAcceptedEvent.copy$default$1"), + exclude[DirectMissingMethodProblem]("sbt.protocol.ExecCommand.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.ExecCommand.copy$default$1"), + exclude[DirectMissingMethodProblem]("sbt.protocol.ExecCommand.copy$default$2"), + exclude[DirectMissingMethodProblem]("sbt.protocol.ExecStatusEvent.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.ExecStatusEvent.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.protocol.ExecutionEvent.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.ExecutionEvent.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.protocol.InitCommand.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.InitCommand.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.protocol.LogEvent.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.LogEvent.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.protocol.SettingQuery.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.SettingQuery.copy$default$1"), + exclude[DirectMissingMethodProblem]("sbt.protocol.SettingQueryFailure.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.SettingQueryFailure.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.protocol.SettingQuerySuccess.copy"), + exclude[DirectMissingMethodProblem]("sbt.protocol.SettingQuerySuccess.copy$default$*"), + + // ignore missing methods in sbt.internal + exclude[DirectMissingMethodProblem]("sbt.internal.*"), + ) ) .configure(addSbtUtilLogging) @@ -336,6 +390,12 @@ lazy val commandProj = (project in file("main-command")) // Replace nailgun socket stuff exclude[MissingClassProblem]("sbt.internal.NG*"), exclude[MissingClassProblem]("sbt.internal.ReferenceCountedFileDescriptor"), + + // copy method was never meant to be public + exclude[DirectMissingMethodProblem]("sbt.CommandSource.copy"), + exclude[DirectMissingMethodProblem]("sbt.CommandSource.copy$default$*"), + exclude[DirectMissingMethodProblem]("sbt.Exec.copy"), + exclude[DirectMissingMethodProblem]("sbt.Exec.copy$default$*"), ), unmanagedSources in (Compile, headerCreate) := { val old = (unmanagedSources in (Compile, headerCreate)).value diff --git a/main-command/src/main/contraband-scala/sbt/CommandSource.scala b/main-command/src/main/contraband-scala/sbt/CommandSource.scala index e57e91857..b15193ab1 100644 --- a/main-command/src/main/contraband-scala/sbt/CommandSource.scala +++ b/main-command/src/main/contraband-scala/sbt/CommandSource.scala @@ -19,7 +19,7 @@ final class CommandSource private ( override def toString: String = { "CommandSource(" + channelName + ")" } - protected[this] def copy(channelName: String = channelName): CommandSource = { + private[this] def copy(channelName: String = channelName): CommandSource = { new CommandSource(channelName) } def withChannelName(channelName: String): CommandSource = { diff --git a/main-command/src/main/contraband-scala/sbt/Exec.scala b/main-command/src/main/contraband-scala/sbt/Exec.scala index a3a6a38e7..5b5398bff 100644 --- a/main-command/src/main/contraband-scala/sbt/Exec.scala +++ b/main-command/src/main/contraband-scala/sbt/Exec.scala @@ -21,7 +21,7 @@ final class Exec private ( override def toString: String = { "Exec(" + commandLine + ", " + execId + ", " + source + ")" } - protected[this] def copy(commandLine: String = commandLine, execId: Option[String] = execId, source: Option[sbt.CommandSource] = source): Exec = { + private[this] def copy(commandLine: String = commandLine, execId: Option[String] = execId, source: Option[sbt.CommandSource] = source): Exec = { new Exec(commandLine, execId, source) } def withCommandLine(commandLine: String): Exec = { diff --git a/main-command/src/main/scala/sbt/internal/server/Server.scala b/main-command/src/main/scala/sbt/internal/server/Server.scala index 05b86c48a..c2bccb886 100644 --- a/main-command/src/main/scala/sbt/internal/server/Server.scala +++ b/main-command/src/main/scala/sbt/internal/server/Server.scala @@ -63,7 +63,8 @@ private[sbt] object Server { val maxSocketLength = new UnixDomainSocketLibrary.SockaddrUn().sunPath.length - 1 val path = socketfile.getAbsolutePath if (path.length > maxSocketLength) - sys.error("socket file absolute path too long; " + + sys.error( + "socket file absolute path too long; " + "either switch to another connection type " + "or define a short \"SBT_GLOBAL_SERVER_DIR\" value. " + s"Current path: ${path}") diff --git a/project/plugins.sbt b/project/plugins.sbt index 5ed8cbf57..c91a3bc6f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,6 +2,6 @@ scalaVersion := "2.12.4" scalacOptions ++= Seq("-feature", "-language:postfixOps") addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.5") -addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.3") +addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.4.0") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.8.0") diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/ClientCapabilities.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/ClientCapabilities.scala index 8e046ede9..7b6e32313 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/ClientCapabilities.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/ClientCapabilities.scala @@ -18,7 +18,7 @@ override def hashCode: Int = { override def toString: String = { "ClientCapabilities()" } -protected[this] def copy(): ClientCapabilities = { +private[this] def copy(): ClientCapabilities = { new ClientCapabilities() } diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/Diagnostic.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/Diagnostic.scala index c28bc9b38..033fae3cf 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/Diagnostic.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/Diagnostic.scala @@ -38,7 +38,7 @@ final class Diagnostic private ( override def toString: String = { "Diagnostic(" + range + ", " + severity + ", " + code + ", " + source + ", " + message + ")" } - protected[this] def copy(range: sbt.internal.langserver.Range = range, severity: Option[Long] = severity, code: Option[String] = code, source: Option[String] = source, message: String = message): Diagnostic = { + private[this] def copy(range: sbt.internal.langserver.Range = range, severity: Option[Long] = severity, code: Option[String] = code, source: Option[String] = source, message: String = message): Diagnostic = { new Diagnostic(range, severity, code, source, message) } def withRange(range: sbt.internal.langserver.Range): Diagnostic = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/InitializeParams.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/InitializeParams.scala index b96dc9478..7c53991c0 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/InitializeParams.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/InitializeParams.scala @@ -25,7 +25,7 @@ final class InitializeParams private ( override def toString: String = { "InitializeParams(" + processId + ", " + rootPath + ", " + rootUri + ", " + initializationOptions + ", " + capabilities + ", " + trace + ")" } - protected[this] def copy(processId: Option[Long] = processId, rootPath: Option[String] = rootPath, rootUri: Option[String] = rootUri, initializationOptions: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = initializationOptions, capabilities: Option[sbt.internal.langserver.ClientCapabilities] = capabilities, trace: Option[String] = trace): InitializeParams = { + private[this] def copy(processId: Option[Long] = processId, rootPath: Option[String] = rootPath, rootUri: Option[String] = rootUri, initializationOptions: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = initializationOptions, capabilities: Option[sbt.internal.langserver.ClientCapabilities] = capabilities, trace: Option[String] = trace): InitializeParams = { new InitializeParams(processId, rootPath, rootUri, initializationOptions, capabilities, trace) } def withProcessId(processId: Option[Long]): InitializeParams = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/InitializeResult.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/InitializeResult.scala index 440844dd6..a7c797870 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/InitializeResult.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/InitializeResult.scala @@ -20,7 +20,7 @@ final class InitializeResult private ( override def toString: String = { "InitializeResult(" + capabilities + ")" } - protected[this] def copy(capabilities: sbt.internal.langserver.ServerCapabilities = capabilities): InitializeResult = { + private[this] def copy(capabilities: sbt.internal.langserver.ServerCapabilities = capabilities): InitializeResult = { new InitializeResult(capabilities) } def withCapabilities(capabilities: sbt.internal.langserver.ServerCapabilities): InitializeResult = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/Location.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/Location.scala index 900a426d4..0fdb66043 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/Location.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/Location.scala @@ -21,7 +21,7 @@ final class Location private ( override def toString: String = { "Location(" + uri + ", " + range + ")" } - protected[this] def copy(uri: String = uri, range: sbt.internal.langserver.Range = range): Location = { + private[this] def copy(uri: String = uri, range: sbt.internal.langserver.Range = range): Location = { new Location(uri, range) } def withUri(uri: String): Location = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/LogMessageParams.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/LogMessageParams.scala index a13045afa..3755c84d0 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/LogMessageParams.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/LogMessageParams.scala @@ -22,7 +22,7 @@ final class LogMessageParams private ( override def toString: String = { "LogMessageParams(" + `type` + ", " + message + ")" } - protected[this] def copy(`type`: Long = `type`, message: String = message): LogMessageParams = { + private[this] def copy(`type`: Long = `type`, message: String = message): LogMessageParams = { new LogMessageParams(`type`, message) } def withType(`type`: Long): LogMessageParams = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/Position.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/Position.scala index f36a278f1..caf248768 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/Position.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/Position.scala @@ -26,7 +26,7 @@ final class Position private ( override def toString: String = { "Position(" + line + ", " + character + ")" } - protected[this] def copy(line: Long = line, character: Long = character): Position = { + private[this] def copy(line: Long = line, character: Long = character): Position = { new Position(line, character) } def withLine(line: Long): Position = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/PublishDiagnosticsParams.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/PublishDiagnosticsParams.scala index 25f7ab729..3c18fbbab 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/PublishDiagnosticsParams.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/PublishDiagnosticsParams.scala @@ -23,7 +23,7 @@ final class PublishDiagnosticsParams private ( override def toString: String = { "PublishDiagnosticsParams(" + uri + ", " + diagnostics + ")" } - protected[this] def copy(uri: String = uri, diagnostics: Vector[sbt.internal.langserver.Diagnostic] = diagnostics): PublishDiagnosticsParams = { + private[this] def copy(uri: String = uri, diagnostics: Vector[sbt.internal.langserver.Diagnostic] = diagnostics): PublishDiagnosticsParams = { new PublishDiagnosticsParams(uri, diagnostics) } def withUri(uri: String): PublishDiagnosticsParams = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/Range.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/Range.scala index 929bbec5b..fab914ef5 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/Range.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/Range.scala @@ -26,7 +26,7 @@ final class Range private ( override def toString: String = { "Range(" + start + ", " + end + ")" } - protected[this] def copy(start: sbt.internal.langserver.Position = start, end: sbt.internal.langserver.Position = end): Range = { + private[this] def copy(start: sbt.internal.langserver.Position = start, end: sbt.internal.langserver.Position = end): Range = { new Range(start, end) } def withStart(start: sbt.internal.langserver.Position): Range = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/SaveOptions.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/SaveOptions.scala index 347dfca25..7f66646de 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/SaveOptions.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/SaveOptions.scala @@ -20,7 +20,7 @@ final class SaveOptions private ( override def toString: String = { "SaveOptions(" + includeText + ")" } - protected[this] def copy(includeText: Option[Boolean] = includeText): SaveOptions = { + private[this] def copy(includeText: Option[Boolean] = includeText): SaveOptions = { new SaveOptions(includeText) } def withIncludeText(includeText: Option[Boolean]): SaveOptions = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/SbtExecParams.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/SbtExecParams.scala index d60cc441b..97f4c5aae 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/SbtExecParams.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/SbtExecParams.scala @@ -20,7 +20,7 @@ final class SbtExecParams private ( override def toString: String = { "SbtExecParams(" + commandLine + ")" } - protected[this] def copy(commandLine: String = commandLine): SbtExecParams = { + private[this] def copy(commandLine: String = commandLine): SbtExecParams = { new SbtExecParams(commandLine) } def withCommandLine(commandLine: String): SbtExecParams = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/ServerCapabilities.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/ServerCapabilities.scala index 802428214..4aa6c698d 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/ServerCapabilities.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/ServerCapabilities.scala @@ -23,7 +23,7 @@ final class ServerCapabilities private ( override def toString: String = { "ServerCapabilities(" + textDocumentSync + ", " + hoverProvider + ", " + definitionProvider + ")" } - protected[this] def copy(textDocumentSync: Option[sbt.internal.langserver.TextDocumentSyncOptions] = textDocumentSync, hoverProvider: Option[Boolean] = hoverProvider, definitionProvider: Option[Boolean] = definitionProvider): ServerCapabilities = { + private[this] def copy(textDocumentSync: Option[sbt.internal.langserver.TextDocumentSyncOptions] = textDocumentSync, hoverProvider: Option[Boolean] = hoverProvider, definitionProvider: Option[Boolean] = definitionProvider): ServerCapabilities = { new ServerCapabilities(textDocumentSync, hoverProvider, definitionProvider) } def withTextDocumentSync(textDocumentSync: Option[sbt.internal.langserver.TextDocumentSyncOptions]): ServerCapabilities = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/TextDocumentIdentifier.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/TextDocumentIdentifier.scala index bb29b3d7f..dd5a2887f 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/TextDocumentIdentifier.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/TextDocumentIdentifier.scala @@ -21,7 +21,7 @@ final class TextDocumentIdentifier private ( override def toString: String = { "TextDocumentIdentifier(" + uri + ")" } - protected[this] def copy(uri: String = uri): TextDocumentIdentifier = { + private[this] def copy(uri: String = uri): TextDocumentIdentifier = { new TextDocumentIdentifier(uri) } def withUri(uri: String): TextDocumentIdentifier = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/TextDocumentPositionParams.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/TextDocumentPositionParams.scala index 5d7d3edd8..10717f0fc 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/TextDocumentPositionParams.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/TextDocumentPositionParams.scala @@ -23,7 +23,7 @@ final class TextDocumentPositionParams private ( override def toString: String = { "TextDocumentPositionParams(" + textDocument + ", " + position + ")" } - protected[this] def copy(textDocument: sbt.internal.langserver.TextDocumentIdentifier = textDocument, position: sbt.internal.langserver.Position = position): TextDocumentPositionParams = { + private[this] def copy(textDocument: sbt.internal.langserver.TextDocumentIdentifier = textDocument, position: sbt.internal.langserver.Position = position): TextDocumentPositionParams = { new TextDocumentPositionParams(textDocument, position) } def withTextDocument(textDocument: sbt.internal.langserver.TextDocumentIdentifier): TextDocumentPositionParams = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/TextDocumentSyncOptions.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/TextDocumentSyncOptions.scala index d94f50428..4822a9aec 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/TextDocumentSyncOptions.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/TextDocumentSyncOptions.scala @@ -23,7 +23,7 @@ final class TextDocumentSyncOptions private ( override def toString: String = { "TextDocumentSyncOptions(" + openClose + ", " + change + ", " + willSave + ", " + willSaveWaitUntil + ", " + save + ")" } - protected[this] def copy(openClose: Option[Boolean] = openClose, change: Option[Long] = change, willSave: Option[Boolean] = willSave, willSaveWaitUntil: Option[Boolean] = willSaveWaitUntil, save: Option[sbt.internal.langserver.SaveOptions] = save): TextDocumentSyncOptions = { + private[this] def copy(openClose: Option[Boolean] = openClose, change: Option[Long] = change, willSave: Option[Boolean] = willSave, willSaveWaitUntil: Option[Boolean] = willSaveWaitUntil, save: Option[sbt.internal.langserver.SaveOptions] = save): TextDocumentSyncOptions = { new TextDocumentSyncOptions(openClose, change, willSave, willSaveWaitUntil, save) } def withOpenClose(openClose: Option[Boolean]): TextDocumentSyncOptions = { 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 eeda8fa2c..ff2f27c9c 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/protocol/InitializeOption.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/protocol/InitializeOption.scala @@ -19,7 +19,7 @@ final class InitializeOption private ( override def toString: String = { "InitializeOption(" + token + ")" } - protected[this] def copy(token: Option[String] = token): InitializeOption = { + private[this] def copy(token: Option[String] = token): InitializeOption = { new InitializeOption(token) } def withToken(token: Option[String]): InitializeOption = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcNotificationMessage.scala b/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcNotificationMessage.scala index aa55a3639..b3edb6b9d 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcNotificationMessage.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcNotificationMessage.scala @@ -23,7 +23,7 @@ final class JsonRpcNotificationMessage private ( override def toString: String = { s"""JsonRpcNotificationMessage($jsonrpc, $method, ${sbt.protocol.Serialization.compactPrintJsonOpt(params)})""" } - protected[this] def copy(jsonrpc: String = jsonrpc, method: String = method, params: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = params): JsonRpcNotificationMessage = { + private[this] def copy(jsonrpc: String = jsonrpc, method: String = method, params: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = params): JsonRpcNotificationMessage = { new JsonRpcNotificationMessage(jsonrpc, method, params) } def withJsonrpc(jsonrpc: String): JsonRpcNotificationMessage = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcRequestMessage.scala b/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcRequestMessage.scala index f5943e1f7..d40611379 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcRequestMessage.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcRequestMessage.scala @@ -25,7 +25,7 @@ final class JsonRpcRequestMessage private ( override def toString: String = { s"""JsonRpcRequestMessage($jsonrpc, $id, $method, ${sbt.protocol.Serialization.compactPrintJsonOpt(params)}})""" } - protected[this] def copy(jsonrpc: String = jsonrpc, id: String = id, method: String = method, params: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = params): JsonRpcRequestMessage = { + private[this] def copy(jsonrpc: String = jsonrpc, id: String = id, method: String = method, params: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = params): JsonRpcRequestMessage = { new JsonRpcRequestMessage(jsonrpc, id, method, params) } def withJsonrpc(jsonrpc: String): JsonRpcRequestMessage = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcResponseError.scala b/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcResponseError.scala index 9ece99852..68541873e 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcResponseError.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcResponseError.scala @@ -27,7 +27,7 @@ final class JsonRpcResponseError private ( override def toString: String = { s"""JsonRpcResponseError($code, $message, ${sbt.protocol.Serialization.compactPrintJsonOpt(data)})""" } - protected[this] def copy(code: Long = code, message: String = message, data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = data): JsonRpcResponseError = { + private[this] def copy(code: Long = code, message: String = message, data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = data): JsonRpcResponseError = { new JsonRpcResponseError(code, message, data) } def withCode(code: Long): JsonRpcResponseError = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcResponseMessage.scala b/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcResponseMessage.scala index dd01ae0d3..b2d1596b4 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcResponseMessage.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/protocol/JsonRpcResponseMessage.scala @@ -28,7 +28,7 @@ final class JsonRpcResponseMessage private ( override def toString: String = { s"""JsonRpcResponseMessage($jsonrpc, $id, ${sbt.protocol.Serialization.compactPrintJsonOpt(result)}, $error)""" } - protected[this] def copy(jsonrpc: String = jsonrpc, id: Option[String] = id, result: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = result, error: Option[sbt.internal.protocol.JsonRpcResponseError] = error): JsonRpcResponseMessage = { + private[this] def copy(jsonrpc: String = jsonrpc, id: Option[String] = id, result: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = result, error: Option[sbt.internal.protocol.JsonRpcResponseError] = error): JsonRpcResponseMessage = { new JsonRpcResponseMessage(jsonrpc, id, result, error) } def withJsonrpc(jsonrpc: String): JsonRpcResponseMessage = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/protocol/PortFile.scala b/protocol/src/main/contraband-scala/sbt/internal/protocol/PortFile.scala index 218aefdfb..8354bc5b4 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/protocol/PortFile.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/protocol/PortFile.scala @@ -26,7 +26,7 @@ final class PortFile private ( override def toString: String = { "PortFile(" + uri + ", " + tokenfilePath + ", " + tokenfileUri + ")" } - protected[this] def copy(uri: String = uri, tokenfilePath: Option[String] = tokenfilePath, tokenfileUri: Option[String] = tokenfileUri): PortFile = { + private[this] def copy(uri: String = uri, tokenfilePath: Option[String] = tokenfilePath, tokenfileUri: Option[String] = tokenfileUri): PortFile = { new PortFile(uri, tokenfilePath, tokenfileUri) } def withUri(uri: String): PortFile = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/protocol/TokenFile.scala b/protocol/src/main/contraband-scala/sbt/internal/protocol/TokenFile.scala index e2019147e..88c218a7d 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/protocol/TokenFile.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/protocol/TokenFile.scala @@ -20,7 +20,7 @@ final class TokenFile private ( override def toString: String = { "TokenFile(" + uri + ", " + token + ")" } - protected[this] def copy(uri: String = uri, token: String = token): TokenFile = { + private[this] def copy(uri: String = uri, token: String = token): TokenFile = { new TokenFile(uri, token) } def withUri(uri: String): TokenFile = { diff --git a/protocol/src/main/contraband-scala/sbt/protocol/ChannelAcceptedEvent.scala b/protocol/src/main/contraband-scala/sbt/protocol/ChannelAcceptedEvent.scala index 8bb6dffcb..65191d029 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/ChannelAcceptedEvent.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/ChannelAcceptedEvent.scala @@ -19,7 +19,7 @@ final class ChannelAcceptedEvent private ( override def toString: String = { "ChannelAcceptedEvent(" + channelName + ")" } - protected[this] def copy(channelName: String = channelName): ChannelAcceptedEvent = { + private[this] def copy(channelName: String = channelName): ChannelAcceptedEvent = { new ChannelAcceptedEvent(channelName) } def withChannelName(channelName: String): ChannelAcceptedEvent = { diff --git a/protocol/src/main/contraband-scala/sbt/protocol/ExecCommand.scala b/protocol/src/main/contraband-scala/sbt/protocol/ExecCommand.scala index 142e7282b..142d44975 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/ExecCommand.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/ExecCommand.scala @@ -21,7 +21,7 @@ final class ExecCommand private ( override def toString: String = { "ExecCommand(" + commandLine + ", " + execId + ")" } - protected[this] def copy(commandLine: String = commandLine, execId: Option[String] = execId): ExecCommand = { + private[this] def copy(commandLine: String = commandLine, execId: Option[String] = execId): ExecCommand = { new ExecCommand(commandLine, execId) } def withCommandLine(commandLine: String): ExecCommand = { diff --git a/protocol/src/main/contraband-scala/sbt/protocol/ExecStatusEvent.scala b/protocol/src/main/contraband-scala/sbt/protocol/ExecStatusEvent.scala index 5ce579397..9af91314f 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/ExecStatusEvent.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/ExecStatusEvent.scala @@ -23,7 +23,7 @@ final class ExecStatusEvent private ( override def toString: String = { "ExecStatusEvent(" + status + ", " + channelName + ", " + execId + ", " + commandQueue + ")" } - protected[this] def copy(status: String = status, channelName: Option[String] = channelName, execId: Option[String] = execId, commandQueue: Vector[String] = commandQueue): ExecStatusEvent = { + private[this] def copy(status: String = status, channelName: Option[String] = channelName, execId: Option[String] = execId, commandQueue: Vector[String] = commandQueue): ExecStatusEvent = { new ExecStatusEvent(status, channelName, execId, commandQueue) } def withStatus(status: String): ExecStatusEvent = { diff --git a/protocol/src/main/contraband-scala/sbt/protocol/ExecutionEvent.scala b/protocol/src/main/contraband-scala/sbt/protocol/ExecutionEvent.scala index f47c3b741..fdcac9975 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/ExecutionEvent.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/ExecutionEvent.scala @@ -21,7 +21,7 @@ final class ExecutionEvent private ( override def toString: String = { "ExecutionEvent(" + success + ", " + commandLine + ")" } - protected[this] def copy(success: String = success, commandLine: String = commandLine): ExecutionEvent = { + private[this] def copy(success: String = success, commandLine: String = commandLine): ExecutionEvent = { new ExecutionEvent(success, commandLine) } def withSuccess(success: String): ExecutionEvent = { diff --git a/protocol/src/main/contraband-scala/sbt/protocol/InitCommand.scala b/protocol/src/main/contraband-scala/sbt/protocol/InitCommand.scala index e45b25c84..b454552d5 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/InitCommand.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/InitCommand.scala @@ -20,7 +20,7 @@ final class InitCommand private ( override def toString: String = { "InitCommand(" + token + ", " + execId + ")" } - protected[this] def copy(token: Option[String] = token, execId: Option[String] = execId): InitCommand = { + private[this] def copy(token: Option[String] = token, execId: Option[String] = execId): InitCommand = { new InitCommand(token, execId) } def withToken(token: Option[String]): InitCommand = { diff --git a/protocol/src/main/contraband-scala/sbt/protocol/LogEvent.scala b/protocol/src/main/contraband-scala/sbt/protocol/LogEvent.scala index 694635252..27acd0656 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/LogEvent.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/LogEvent.scala @@ -21,7 +21,7 @@ final class LogEvent private ( override def toString: String = { "LogEvent(" + level + ", " + message + ")" } - protected[this] def copy(level: String = level, message: String = message): LogEvent = { + private[this] def copy(level: String = level, message: String = message): LogEvent = { new LogEvent(level, message) } def withLevel(level: String): LogEvent = { diff --git a/protocol/src/main/contraband-scala/sbt/protocol/SettingQuery.scala b/protocol/src/main/contraband-scala/sbt/protocol/SettingQuery.scala index a00fd05d6..2846234ef 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/SettingQuery.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/SettingQuery.scala @@ -19,7 +19,7 @@ final class SettingQuery private ( override def toString: String = { "SettingQuery(" + setting + ")" } - protected[this] def copy(setting: String = setting): SettingQuery = { + private[this] def copy(setting: String = setting): SettingQuery = { new SettingQuery(setting) } def withSetting(setting: String): SettingQuery = { diff --git a/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryFailure.scala b/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryFailure.scala index 9c5305b96..16102225d 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryFailure.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/SettingQueryFailure.scala @@ -19,7 +19,7 @@ final class SettingQueryFailure private ( override def toString: String = { "SettingQueryFailure(" + message + ")" } - protected[this] def copy(message: String = message): SettingQueryFailure = { + private[this] def copy(message: String = message): SettingQueryFailure = { new SettingQueryFailure(message) } def withMessage(message: String): SettingQueryFailure = { diff --git a/protocol/src/main/contraband-scala/sbt/protocol/SettingQuerySuccess.scala b/protocol/src/main/contraband-scala/sbt/protocol/SettingQuerySuccess.scala index be6af35fc..ab9c6d2ee 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/SettingQuerySuccess.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/SettingQuerySuccess.scala @@ -20,7 +20,7 @@ final class SettingQuerySuccess private ( override def toString: String = { "SettingQuerySuccess(" + value + ", " + contentType + ")" } - protected[this] def copy(value: sjsonnew.shaded.scalajson.ast.unsafe.JValue = value, contentType: String = contentType): SettingQuerySuccess = { + private[this] def copy(value: sjsonnew.shaded.scalajson.ast.unsafe.JValue = value, contentType: String = contentType): SettingQuerySuccess = { new SettingQuerySuccess(value, contentType) } def withValue(value: sjsonnew.shaded.scalajson.ast.unsafe.JValue): SettingQuerySuccess = { diff --git a/run/src/main/contraband-scala/sbt/ForkOptions.scala b/run/src/main/contraband-scala/sbt/ForkOptions.scala index e20277b72..b8f30ab15 100644 --- a/run/src/main/contraband-scala/sbt/ForkOptions.scala +++ b/run/src/main/contraband-scala/sbt/ForkOptions.scala @@ -43,7 +43,7 @@ final class ForkOptions private ( override def toString: String = { "ForkOptions(" + javaHome + ", " + outputStrategy + ", " + bootJars + ", " + workingDirectory + ", " + runJVMOptions + ", " + connectInput + ", " + envVars + ")" } - protected[this] def copy(javaHome: Option[java.io.File] = javaHome, outputStrategy: Option[sbt.OutputStrategy] = outputStrategy, bootJars: Vector[java.io.File] = bootJars, workingDirectory: Option[java.io.File] = workingDirectory, runJVMOptions: Vector[String] = runJVMOptions, connectInput: Boolean = connectInput, envVars: scala.collection.immutable.Map[String, String] = envVars): ForkOptions = { + private[this] def copy(javaHome: Option[java.io.File] = javaHome, outputStrategy: Option[sbt.OutputStrategy] = outputStrategy, bootJars: Vector[java.io.File] = bootJars, workingDirectory: Option[java.io.File] = workingDirectory, runJVMOptions: Vector[String] = runJVMOptions, connectInput: Boolean = connectInput, envVars: scala.collection.immutable.Map[String, String] = envVars): ForkOptions = { new ForkOptions(javaHome, outputStrategy, bootJars, workingDirectory, runJVMOptions, connectInput, envVars) } def withJavaHome(javaHome: Option[java.io.File]): ForkOptions = { diff --git a/run/src/main/scala/sbt/OutputStrategy.scala b/run/src/main/scala/sbt/OutputStrategy.scala index be1031e30..1f6d819cf 100644 --- a/run/src/main/scala/sbt/OutputStrategy.scala +++ b/run/src/main/scala/sbt/OutputStrategy.scala @@ -37,7 +37,7 @@ object OutputStrategy { override def toString: String = { "BufferedOutput(" + logger + ")" } - protected[this] def copy(logger: Logger = logger): BufferedOutput = { + private[this] def copy(logger: Logger = logger): BufferedOutput = { new BufferedOutput(logger) } def withLogger(logger: Logger): BufferedOutput = { @@ -63,7 +63,7 @@ object OutputStrategy { override def toString: String = { "LoggedOutput(" + logger + ")" } - protected[this] def copy(logger: Logger = logger): LoggedOutput = { + private[this] def copy(logger: Logger = logger): LoggedOutput = { new LoggedOutput(logger) } def withLogger(logger: Logger): LoggedOutput = { @@ -91,7 +91,7 @@ object OutputStrategy { override def toString: String = { "CustomOutput(" + output + ")" } - protected[this] def copy(output: OutputStream = output): CustomOutput = { + private[this] def copy(output: OutputStream = output): CustomOutput = { new CustomOutput(output) } def withOutput(output: OutputStream): CustomOutput = { diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupErrorEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupErrorEvent.scala index afeeef6ac..b6cf91afa 100644 --- a/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupErrorEvent.scala +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupErrorEvent.scala @@ -21,7 +21,7 @@ final class EndTestGroupErrorEvent private ( override def toString: String = { "EndTestGroupErrorEvent(" + name + ", " + error + ")" } - protected[this] def copy(name: String = name, error: String = error): EndTestGroupErrorEvent = { + private[this] def copy(name: String = name, error: String = error): EndTestGroupErrorEvent = { new EndTestGroupErrorEvent(name, error) } def withName(name: String): EndTestGroupErrorEvent = { diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupEvent.scala index db3893a84..802a64843 100644 --- a/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupEvent.scala +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupEvent.scala @@ -21,7 +21,7 @@ final class EndTestGroupEvent private ( override def toString: String = { "EndTestGroupEvent(" + name + ", " + result + ")" } - protected[this] def copy(name: String = name, result: sbt.protocol.testing.TestResult = result): EndTestGroupEvent = { + private[this] def copy(name: String = name, result: sbt.protocol.testing.TestResult = result): EndTestGroupEvent = { new EndTestGroupEvent(name, result) } def withName(name: String): EndTestGroupEvent = { diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/StartTestGroupEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/StartTestGroupEvent.scala index 156c4ba57..ed1ca2f9a 100644 --- a/testing/src/main/contraband-scala/sbt/protocol/testing/StartTestGroupEvent.scala +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/StartTestGroupEvent.scala @@ -20,7 +20,7 @@ final class StartTestGroupEvent private ( override def toString: String = { "StartTestGroupEvent(" + name + ")" } - protected[this] def copy(name: String = name): StartTestGroupEvent = { + private[this] def copy(name: String = name): StartTestGroupEvent = { new StartTestGroupEvent(name) } def withName(name: String): StartTestGroupEvent = { diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestCompleteEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestCompleteEvent.scala index e44188084..c3d0fb4f6 100644 --- a/testing/src/main/contraband-scala/sbt/protocol/testing/TestCompleteEvent.scala +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestCompleteEvent.scala @@ -20,7 +20,7 @@ final class TestCompleteEvent private ( override def toString: String = { "TestCompleteEvent(" + result + ")" } - protected[this] def copy(result: sbt.protocol.testing.TestResult = result): TestCompleteEvent = { + private[this] def copy(result: sbt.protocol.testing.TestResult = result): TestCompleteEvent = { new TestCompleteEvent(result) } def withResult(result: sbt.protocol.testing.TestResult): TestCompleteEvent = { diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestInitEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestInitEvent.scala index b6178c40f..cf96623d9 100644 --- a/testing/src/main/contraband-scala/sbt/protocol/testing/TestInitEvent.scala +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestInitEvent.scala @@ -19,7 +19,7 @@ override def hashCode: Int = { override def toString: String = { "TestInitEvent()" } -protected[this] def copy(): TestInitEvent = { +private[this] def copy(): TestInitEvent = { new TestInitEvent() } diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemDetail.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemDetail.scala index 50762dcb5..904cdc258 100644 --- a/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemDetail.scala +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemDetail.scala @@ -31,7 +31,7 @@ final class TestItemDetail private ( override def toString: String = { "TestItemDetail(" + fullyQualifiedName + ", " + status + ", " + duration + ")" } - protected[this] def copy(fullyQualifiedName: String = fullyQualifiedName, status: sbt.testing.Status = status, duration: Option[Long] = duration): TestItemDetail = { + private[this] def copy(fullyQualifiedName: String = fullyQualifiedName, status: sbt.testing.Status = status, duration: Option[Long] = duration): TestItemDetail = { new TestItemDetail(fullyQualifiedName, status, duration) } def withFullyQualifiedName(fullyQualifiedName: String): TestItemDetail = { diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemEvent.scala index d7f343f7b..6910d1b16 100644 --- a/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemEvent.scala +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemEvent.scala @@ -21,7 +21,7 @@ final class TestItemEvent private ( override def toString: String = { "TestItemEvent(" + result + ", " + detail + ")" } - protected[this] def copy(result: Option[sbt.protocol.testing.TestResult] = result, detail: Vector[sbt.protocol.testing.TestItemDetail] = detail): TestItemEvent = { + private[this] def copy(result: Option[sbt.protocol.testing.TestResult] = result, detail: Vector[sbt.protocol.testing.TestItemDetail] = detail): TestItemEvent = { new TestItemEvent(result, detail) } def withResult(result: Option[sbt.protocol.testing.TestResult]): TestItemEvent = { diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestStringEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestStringEvent.scala index 618faf180..6b3f5d973 100644 --- a/testing/src/main/contraband-scala/sbt/protocol/testing/TestStringEvent.scala +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestStringEvent.scala @@ -19,7 +19,7 @@ final class TestStringEvent private ( override def toString: String = { value } - protected[this] def copy(value: String = value): TestStringEvent = { + private[this] def copy(value: String = value): TestStringEvent = { new TestStringEvent(value) } def withValue(value: String): TestStringEvent = { From d4cdb11b53a7db3e8b93fccb2be1bec76150ee20 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 13 Mar 2018 17:47:11 +0900 Subject: [PATCH 175/356] typo fix --- main-settings/src/main/scala/sbt/Scope.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main-settings/src/main/scala/sbt/Scope.scala b/main-settings/src/main/scala/sbt/Scope.scala index 10d49dfaf..ff74ebbfc 100644 --- a/main-settings/src/main/scala/sbt/Scope.scala +++ b/main-settings/src/main/scala/sbt/Scope.scala @@ -267,7 +267,7 @@ object Scope { val scope = Scope.replaceThis(GlobalScope)(rawScope) // This is a hot method that gets called many times - def exandDelegateScopes(resolvedProj: ResolvedReference)( + def expandDelegateScopes(resolvedProj: ResolvedReference)( pLin: Seq[ScopeAxis[ResolvedReference]]): Vector[Scope] = { val tLin = scope.task match { case t @ Select(_) => linearize(t)(taskInherit) @@ -316,7 +316,7 @@ object Scope { case pr: ProjectRef => index.project(pr) case br: BuildRef => List(Select(br), Zero) } - exandDelegateScopes(resolvedProj)(projAxes) + expandDelegateScopes(resolvedProj)(projAxes) } } From cd9f0d271171b3cc8554ba36001f8f2c070b2c1d Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 28 Feb 2018 05:39:06 -0500 Subject: [PATCH 176/356] make sbt server extensible Fixes #3890 Here's an example: ```scala Global / serverHandlers += ServerHandler({ callback => import callback._ import sjsonnew.BasicJsonProtocol._ import sbt.internal.protocol.JsonRpcRequestMessage ServerIntent( { case r: JsonRpcRequestMessage if r.method == "lunar/helo" => jsonRpcNotify("lunar/oleh", "") () }, PartialFunction.empty ) ``` --- build.sbt | 1 + .../src/main/scala/sbt/BasicKeys.scala | 5 + .../src/main/scala/sbt/ServerHandler.scala | 67 ++++++++ main/src/main/scala/sbt/Defaults.scala | 8 +- main/src/main/scala/sbt/Keys.scala | 2 + main/src/main/scala/sbt/Project.scala | 3 + .../scala/sbt/internal/CommandExchange.scala | 6 +- .../server/LanguageServerProtocol.scala | 155 ++++++++++-------- .../sbt/internal/server/NetworkChannel.scala | 31 +++- sbt/src/server-test/handshake/build.sbt | 15 ++ sbt/src/test/scala/sbt/ServerSpec.scala | 37 +++-- 11 files changed, 244 insertions(+), 86 deletions(-) create mode 100644 main-command/src/main/scala/sbt/ServerHandler.scala diff --git a/build.sbt b/build.sbt index 8de457067..a5f333fed 100644 --- a/build.sbt +++ b/build.sbt @@ -498,6 +498,7 @@ lazy val sbtProj = (project in file("sbt")) connectInput in run in Test := true, outputStrategy in run in Test := Some(StdoutOutput), fork in Test := true, + parallelExecution in Test := false, ) .configure(addSbtCompilerBridge) diff --git a/main-command/src/main/scala/sbt/BasicKeys.scala b/main-command/src/main/scala/sbt/BasicKeys.scala index 1570d392b..393397f3f 100644 --- a/main-command/src/main/scala/sbt/BasicKeys.scala +++ b/main-command/src/main/scala/sbt/BasicKeys.scala @@ -39,6 +39,11 @@ object BasicKeys { "The wire protocol for the server command.", 10000) + val fullServerHandlers = + AttributeKey[Seq[ServerHandler]]("fullServerHandlers", + "Combines default server handlers and user-defined handlers.", + 10000) + val autoStartServer = AttributeKey[Boolean]( "autoStartServer", diff --git a/main-command/src/main/scala/sbt/ServerHandler.scala b/main-command/src/main/scala/sbt/ServerHandler.scala new file mode 100644 index 000000000..07fe8616e --- /dev/null +++ b/main-command/src/main/scala/sbt/ServerHandler.scala @@ -0,0 +1,67 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt + +import sjsonnew.JsonFormat +import sbt.internal.protocol._ +import sbt.util.Logger +import sbt.protocol.{ SettingQuery => Q } + +/** + * ServerHandler allows plugins to extend sbt server. + * It's a wrapper around curried function ServerCallback => JsonRpcRequestMessage => Unit. + */ +final class ServerHandler(val handler: ServerCallback => ServerIntent) { + override def toString: String = s"Serverhandler(...)" +} + +object ServerHandler { + def apply(handler: ServerCallback => ServerIntent): ServerHandler = + new ServerHandler(handler) + + lazy val fallback: ServerHandler = ServerHandler({ handler => + ServerIntent( + { case x => handler.log.debug(s"Unhandled notification received: ${x.method}") }, + { case x => handler.log.debug(s"Unhandled request received: ${x.method}") } + ) + }) +} + +final class ServerIntent(val onRequest: PartialFunction[JsonRpcRequestMessage, Unit], + val onNotification: PartialFunction[JsonRpcNotificationMessage, Unit]) { + override def toString: String = s"ServerIntent(...)" +} + +object ServerIntent { + def apply(onRequest: PartialFunction[JsonRpcRequestMessage, Unit], + onNotification: PartialFunction[JsonRpcNotificationMessage, Unit]): ServerIntent = + new ServerIntent(onRequest, onNotification) + + def request(onRequest: PartialFunction[JsonRpcRequestMessage, Unit]): ServerIntent = + new ServerIntent(onRequest, PartialFunction.empty) + + def notify(onNotification: PartialFunction[JsonRpcNotificationMessage, Unit]): ServerIntent = + new ServerIntent(PartialFunction.empty, onNotification) +} + +/** + * Interface to invoke JSON-RPC response. + */ +trait ServerCallback { + def jsonRpcRespond[A: JsonFormat](event: A, execId: Option[String]): Unit + def jsonRpcRespondError(execId: Option[String], code: Long, message: String): Unit + def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit + def appendExec(exec: Exec): Boolean + def log: Logger + def name: String + + private[sbt] def authOptions: Set[ServerAuthentication] + private[sbt] def authenticate(token: String): Boolean + private[sbt] def setInitialized(value: Boolean): Unit + private[sbt] def onSettingQuery(execId: Option[String], req: Q): Unit +} diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 55376c343..1bdee43ff 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -26,7 +26,7 @@ import sbt.internal.librarymanagement.mavenint.{ PomExtraDependencyAttributes, SbtPomExtraProperties } -import sbt.internal.server.{ LanguageServerReporter, Definition } +import sbt.internal.server.{ LanguageServerReporter, Definition, LanguageServerProtocol } import sbt.internal.testing.TestLogger import sbt.internal.util._ import sbt.internal.util.Attributed.data @@ -278,6 +278,12 @@ object Defaults extends BuildCommon { if (serverConnectionType.value == ConnectionType.Tcp) Set(ServerAuthentication.Token) else Set() }, + serverHandlers :== Nil, + fullServerHandlers := { + (Vector(LanguageServerProtocol.handler) + ++ serverHandlers.value + ++ Vector(ServerHandler.fallback)) + }, insideCI :== sys.env.contains("BUILD_NUMBER") || sys.env.contains("CI"), )) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index d90311607..df551cdd7 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -136,6 +136,8 @@ object Keys { val serverHost = SettingKey(BasicKeys.serverHost) val serverAuthentication = SettingKey(BasicKeys.serverAuthentication) val serverConnectionType = SettingKey(BasicKeys.serverConnectionType) + val fullServerHandlers = SettingKey(BasicKeys.fullServerHandlers) + val serverHandlers = settingKey[Seq[ServerHandler]]("User-defined server handlers.") val analysis = AttributeKey[CompileAnalysis]("analysis", "Analysis of compilation, including dependencies and generated outputs.", DSetting) val watch = SettingKey(BasicKeys.watch) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index b3da0452a..38124e5a5 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -27,6 +27,7 @@ import Keys.{ serverPort, serverAuthentication, serverConnectionType, + fullServerHandlers, logLevel, watch } @@ -475,6 +476,7 @@ object Project extends ProjectExtra { val authentication: Option[Set[ServerAuthentication]] = get(serverAuthentication) val connectionType: Option[ConnectionType] = get(serverConnectionType) val srvLogLevel: Option[Level.Value] = (logLevel in (ref, serverLog)).get(structure.data) + val hs: Option[Seq[ServerHandler]] = get(fullServerHandlers) val commandDefs = allCommands.distinct.flatten[Command].map(_ tag (projectCommand, true)) val newDefinedCommands = commandDefs ++ BasicCommands.removeTagged(s.definedCommands, projectCommand) @@ -491,6 +493,7 @@ object Project extends ProjectExtra { .put(templateResolverInfos.key, trs) .setCond(shellPrompt.key, prompt) .setCond(serverLogLevel, srvLogLevel) + .setCond(fullServerHandlers.key, hs) s.copy( attributes = newAttrs, definedCommands = newDefinedCommands diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 1daab4db2..2a4e1c105 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -20,6 +20,7 @@ import BasicKeys.{ serverAuthentication, serverConnectionType, serverLogLevel, + fullServerHandlers, logLevel } import java.net.Socket @@ -102,6 +103,7 @@ private[sbt] final class CommandExchange { s.get(serverAuthentication).getOrElse(Set(ServerAuthentication.Token)) lazy val connectionType = s.get(serverConnectionType).getOrElse(ConnectionType.Tcp) lazy val level = s.get(serverLogLevel).orElse(s.get(logLevel)).getOrElse(Level.Warn) + lazy val handlers = s.get(fullServerHandlers).getOrElse(Nil) def onIncomingSocket(socket: Socket, instance: ServerInstance): Unit = { val name = newNetworkName @@ -114,7 +116,7 @@ private[sbt] final class CommandExchange { log } val channel = - new NetworkChannel(name, socket, Project structure s, auth, instance, logger) + new NetworkChannel(name, socket, Project structure s, auth, instance, handlers, logger) subscribe(channel) } if (server.isEmpty && firstInstance.get) { @@ -210,7 +212,7 @@ private[sbt] final class CommandExchange { // in case we have a better client that can utilize the knowledge. import sbt.internal.langserver.codec.JsonProtocol._ if (broadcastStringMessage || (entry.channelName contains c.name)) - c.langNotify("window/logMessage", params) + c.jsonRpcNotify("window/logMessage", params) } catch { case _: IOException => toDel += c } } case _ => diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index 637bd97d0..761073591 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -21,10 +21,70 @@ import sbt.util.Logger private[sbt] case class LangServerError(code: Long, message: String) extends Throwable(message) +private[sbt] object LanguageServerProtocol { + lazy val internalJsonProtocol = new InitializeOptionFormats with sjsonnew.BasicJsonProtocol {} + + lazy val serverCapabilities: ServerCapabilities = { + ServerCapabilities(textDocumentSync = + TextDocumentSyncOptions(true, 0, false, false, SaveOptions(false)), + hoverProvider = false, + definitionProvider = true) + } + + lazy val handler: ServerHandler = ServerHandler({ + case callback: ServerCallback => + import callback._ + ServerIntent( + { + import sbt.internal.langserver.codec.JsonProtocol._ + import internalJsonProtocol._ + def json(r: JsonRpcRequestMessage) = + r.params.getOrElse( + throw LangServerError(ErrorCodes.InvalidParams, + s"param is expected on '${r.method}' method.")) + + { + case r: JsonRpcRequestMessage if r.method == "initialize" => + if (authOptions(ServerAuthentication.Token)) { + val param = Converter.fromJson[InitializeParams](json(r)).get + val optionJson = param.initializationOptions.getOrElse( + throw LangServerError(ErrorCodes.InvalidParams, + "initializationOptions is expected on 'initialize' param.")) + val opt = Converter.fromJson[InitializeOption](optionJson).get + val token = opt.token.getOrElse(sys.error("'token' is missing.")) + if (authenticate(token)) () + else throw LangServerError(ErrorCodes.InvalidRequest, "invalid token") + } else () + setInitialized(true) + appendExec(Exec(s"collectAnalyses", Some(r.id), Some(CommandSource(name)))) + jsonRpcRespond(InitializeResult(serverCapabilities), Option(r.id)) + + case r: JsonRpcRequestMessage if r.method == "textDocument/definition" => + import scala.concurrent.ExecutionContext.Implicits.global + Definition.lspDefinition(json(r), r.id, CommandSource(name), log) + () + case r: JsonRpcRequestMessage if r.method == "sbt/exec" => + val param = Converter.fromJson[SbtExecParams](json(r)).get + appendExec(Exec(param.commandLine, Some(r.id), Some(CommandSource(name)))) + () + case r: JsonRpcRequestMessage if r.method == "sbt/setting" => + import sbt.protocol.codec.JsonProtocol._ + val param = Converter.fromJson[Q](json(r)).get + onSettingQuery(Option(r.id), param) + } + }, { + case n: JsonRpcNotificationMessage if n.method == "textDocument/didSave" => + appendExec(Exec(";compile; collectAnalyses", None, Some(CommandSource(name)))) + () + } + ) + }) +} + /** * Implements Language Server Protocol . */ -private[sbt] trait LanguageServerProtocol extends CommandChannel { +private[sbt] trait LanguageServerProtocol extends CommandChannel { self => lazy val internalJsonProtocol = new InitializeOptionFormats with sjsonnew.BasicJsonProtocol {} @@ -34,54 +94,24 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { protected def log: Logger protected def onSettingQuery(execId: Option[String], req: Q): Unit - protected def onNotification(notification: JsonRpcNotificationMessage): Unit = { - log.debug(s"onNotification: $notification") - notification.method match { - case "textDocument/didSave" => - append(Exec(";compile; collectAnalyses", None, Some(CommandSource(name)))) - () - case u => log.debug(s"Unhandled notification received: $u") - } - } + protected lazy val callbackImpl: ServerCallback = new ServerCallback { + def jsonRpcRespond[A: JsonFormat](event: A, execId: Option[String]): Unit = + self.jsonRpcRespond(event, execId) - protected def onRequestMessage(request: JsonRpcRequestMessage): Unit = { - import sbt.internal.langserver.codec.JsonProtocol._ - import internalJsonProtocol._ - def json = - request.params.getOrElse( - throw LangServerError(ErrorCodes.InvalidParams, - s"param is expected on '${request.method}' method.")) - log.debug(s"onRequestMessage: $request") - request.method match { - case "initialize" => - if (authOptions(ServerAuthentication.Token)) { - val param = Converter.fromJson[InitializeParams](json).get - val optionJson = param.initializationOptions.getOrElse( - throw LangServerError(ErrorCodes.InvalidParams, - "initializationOptions is expected on 'initialize' param.")) - val opt = Converter.fromJson[InitializeOption](optionJson).get - val token = opt.token.getOrElse(sys.error("'token' is missing.")) - if (authenticate(token)) () - else throw LangServerError(ErrorCodes.InvalidRequest, "invalid token") - } else () - setInitialized(true) - append(Exec(s"collectAnalyses", Some(request.id), Some(CommandSource(name)))) - langRespond(InitializeResult(serverCapabilities), Option(request.id)) - case "textDocument/definition" => - import scala.concurrent.ExecutionContext.Implicits.global - Definition.lspDefinition(json, request.id, CommandSource(name), log) - () - case "sbt/exec" => - val param = Converter.fromJson[SbtExecParams](json).get - append(Exec(param.commandLine, Some(request.id), Some(CommandSource(name)))) - () - case "sbt/setting" => { - import sbt.protocol.codec.JsonProtocol._ - val param = Converter.fromJson[Q](json).get - onSettingQuery(Option(request.id), param) - } - case unhandledRequest => log.debug(s"Unhandled request received: $unhandledRequest") - } + def jsonRpcRespondError(execId: Option[String], code: Long, message: String): Unit = + self.jsonRpcRespondError(execId, code, message) + + def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit = + self.jsonRpcNotify(method, params) + + def appendExec(exec: Exec): Boolean = self.append(exec) + def log: Logger = self.log + def name: String = self.name + 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 onSettingQuery(execId: Option[String], req: Q): Unit = + self.onSettingQuery(execId, req) } /** @@ -97,7 +127,7 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { // LanguageServerReporter sends PublishDiagnosticsParams case "sbt.internal.langserver.PublishDiagnosticsParams" => // val p = event.message.asInstanceOf[PublishDiagnosticsParams] - // langNotify("textDocument/publishDiagnostics", p) + // jsonRpcNotify("textDocument/publishDiagnostics", p) case "xsbti.Problem" => () // ignore case _ => @@ -109,7 +139,7 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { /** * Respond back to Language Server's client. */ - private[sbt] def langRespond[A: JsonFormat](event: A, execId: Option[String]): Unit = { + private[sbt] def jsonRpcRespond[A: JsonFormat](event: A, execId: Option[String]): Unit = { val m = JsonRpcResponseMessage("2.0", execId, Option(Converter.toJson[A](event).get), None) val bytes = Serialization.serializeResponseMessage(m) @@ -119,7 +149,9 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { /** * Respond back to Language Server's client. */ - private[sbt] def langError(execId: Option[String], code: Long, message: String): Unit = { + private[sbt] def jsonRpcRespondError(execId: Option[String], + code: Long, + message: String): Unit = { val e = JsonRpcResponseError(code, message, None) val m = JsonRpcResponseMessage("2.0", execId, None, Option(e)) val bytes = Serialization.serializeResponseMessage(m) @@ -129,10 +161,10 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { /** * Respond back to Language Server's client. */ - private[sbt] def langError[A: JsonFormat](execId: Option[String], - code: Long, - message: String, - data: A): Unit = { + private[sbt] def jsonRpcRespondError[A: JsonFormat](execId: Option[String], + code: Long, + message: String, + data: A): Unit = { val e = JsonRpcResponseError(code, message, Option(Converter.toJson[A](data).get)) val m = JsonRpcResponseMessage("2.0", execId, None, Option(e)) val bytes = Serialization.serializeResponseMessage(m) @@ -142,26 +174,19 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { /** * Notify to Language Server's client. */ - private[sbt] def langNotify[A: JsonFormat](method: String, params: A): Unit = { + private[sbt] def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit = { val m = JsonRpcNotificationMessage("2.0", method, Option(Converter.toJson[A](params).get)) - log.debug(s"langNotify: $m") + log.debug(s"jsonRpcNotify: $m") val bytes = Serialization.serializeNotificationMessage(m) publishBytes(bytes) } def logMessage(level: String, message: String): Unit = { import sbt.internal.langserver.codec.JsonProtocol._ - langNotify( + jsonRpcNotify( "window/logMessage", LogMessageParams(MessageType.fromLevelString(level), message) ) } - - private[sbt] lazy val serverCapabilities: ServerCapabilities = { - ServerCapabilities(textDocumentSync = - TextDocumentSyncOptions(true, 0, false, false, SaveOptions(false)), - hoverProvider = false, - definitionProvider = true) - } } diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 3ad7f2fc5..e1dc9fac0 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -26,6 +26,7 @@ final class NetworkChannel(val name: String, structure: BuildStructure, auth: Set[ServerAuthentication], instance: ServerInstance, + handlers: Seq[ServerHandler], val log: Logger) extends CommandChannel with LanguageServerProtocol { @@ -76,7 +77,6 @@ final class NetworkChannel(val name: String, // contentType = "" state = SingleLine } - def tillEndOfLine: Option[Vector[Byte]] = { val delimPos = buffer.indexOf(delimiter) if (delimPos > 0) { @@ -165,6 +165,21 @@ final class NetworkChannel(val name: String, } } + private lazy val intents = { + val cb = callbackImpl + handlers.toVector map { h => + h.handler(cb) + } + } + lazy val onRequestMessage: PartialFunction[JsonRpcRequestMessage, Unit] = + intents.foldLeft(PartialFunction.empty[JsonRpcRequestMessage, Unit]) { + case (f, i) => f orElse i.onRequest + } + lazy val onNotification: PartialFunction[JsonRpcNotificationMessage, Unit] = + intents.foldLeft(PartialFunction.empty[JsonRpcNotificationMessage, Unit]) { + case (f, i) => f orElse i.onNotification + } + def handleBody(chunk: Vector[Byte]): Unit = { if (isLanguageServerProtocol) { Serialization.deserializeJsonMessage(chunk) match { @@ -174,7 +189,7 @@ final class NetworkChannel(val name: String, } catch { case LangServerError(code, message) => log.debug(s"sending error: $code: $message") - langError(Option(req.id), code, message) + jsonRpcRespondError(Option(req.id), code, message) } case Right(ntf: JsonRpcNotificationMessage) => try { @@ -182,13 +197,13 @@ final class NetworkChannel(val name: String, } catch { case LangServerError(code, message) => log.debug(s"sending error: $code: $message") - langError(None, code, message) // new id? + jsonRpcRespondError(None, code, message) // new id? } case Right(msg) => log.debug(s"Unhandled message: $msg") case Left(errorDesc) => val msg = s"Got invalid chunk from client (${new String(chunk.toArray, "UTF-8")}): " + errorDesc - langError(None, ErrorCodes.ParseError, msg) + jsonRpcRespondError(None, ErrorCodes.ParseError, msg) } } else { contentType match { @@ -230,7 +245,7 @@ final class NetworkChannel(val name: String, private[sbt] def notifyEvent[A: JsonFormat](method: String, params: A): Unit = { if (isLanguageServerProtocol) { - langNotify(method, params) + jsonRpcNotify(method, params) } else { () } @@ -240,7 +255,7 @@ final class NetworkChannel(val name: String, if (isLanguageServerProtocol) { event match { case entry: StringEvent => logMessage(entry.level, entry.message) - case _ => langRespond(event, execId) + case _ => jsonRpcRespond(event, execId) } } else { contentType match { @@ -341,8 +356,8 @@ final class NetworkChannel(val name: String, if (initialized) { import sbt.protocol.codec.JsonProtocol._ SettingQuery.handleSettingQueryEither(req, structure) match { - case Right(x) => langRespond(x, execId) - case Left(s) => langError(execId, ErrorCodes.InvalidParams, s) + case Right(x) => jsonRpcRespond(x, execId) + case Left(s) => jsonRpcRespondError(execId, ErrorCodes.InvalidParams, s) } } else { log.warn(s"ignoring query $req before initialization") diff --git a/sbt/src/server-test/handshake/build.sbt b/sbt/src/server-test/handshake/build.sbt index 192730eef..1267ea46b 100644 --- a/sbt/src/server-test/handshake/build.sbt +++ b/sbt/src/server-test/handshake/build.sbt @@ -1,6 +1,21 @@ lazy val root = (project in file(".")) .settings( Global / serverLog / logLevel := Level.Debug, + + // custom handler + Global / serverHandlers += ServerHandler({ callback => + import callback._ + import sjsonnew.BasicJsonProtocol._ + import sbt.internal.protocol.JsonRpcRequestMessage + ServerIntent( + { + case r: JsonRpcRequestMessage if r.method == "lunar/helo" => + jsonRpcNotify("lunar/oleh", "") + () + }, + PartialFunction.empty + ) + }), name := "handshake", scalaVersion := "2.12.3", ) diff --git a/sbt/src/test/scala/sbt/ServerSpec.scala b/sbt/src/test/scala/sbt/ServerSpec.scala index 7ad307fd8..5c25d1afb 100644 --- a/sbt/src/test/scala/sbt/ServerSpec.scala +++ b/sbt/src/test/scala/sbt/ServerSpec.scala @@ -20,15 +20,19 @@ class ServerSpec extends AsyncFlatSpec with Matchers { "server" should "start" in { withBuildSocket("handshake") { (out, in, tkn) => writeLine( - """{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "root/name" } }""", + """{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "handshake/name" } }""", out) Thread.sleep(100) - val l2 = contentLength(in) - println(l2) - readLine(in) - readLine(in) - val x2 = readContentLength(in, l2) - println(x2) + + println(readFrame(in)) + println(readFrame(in)) + println(readFrame(in)) + println(readFrame(in)) + // println(readFrame(in)) + + writeLine("""{ "jsonrpc": "2.0", "id": 10, "method": "lunar/helo", "params": {} }""", out) + Thread.sleep(100) + assert(1 == 1) } } @@ -90,6 +94,14 @@ object ServerSpec { writeLine(message, out) } + def readFrame(in: InputStream): Option[String] = { + val l = contentLength(in) + println(l) + readLine(in) + readLine(in) + readContentLength(in, l) + } + def contentLength(in: InputStream): Int = { readLine(in) map { line => line.drop(16).toInt @@ -98,7 +110,11 @@ object ServerSpec { def readLine(in: InputStream): Option[String] = { if (buffer.isEmpty) { - val bytesRead = in.read(readBuffer) + val bytesRead = try { + in.read(readBuffer) + } catch { + case _: java.io.IOException => 0 + } if (bytesRead > 0) { buffer = buffer ++ readBuffer.toVector.take(bytesRead) } @@ -153,11 +169,12 @@ object ServerSpec { else { if (n <= 0) sys.error(s"Timeout. $portfile is not found.") else { + println(s" waiting for $portfile...") Thread.sleep(1000) waitForPortfile(n - 1) } } - waitForPortfile(10) + waitForPortfile(20) val (sk, tkn) = ClientSocket.socket(portfile) val out = sk.getOutputStream val in = sk.getInputStream @@ -172,7 +189,7 @@ object ServerSpec { sendJsonRpc( """{ "jsonrpc": "2.0", "id": 9, "method": "sbt/exec", "params": { "commandLine": "exit" } }""", out) - shutdown() + // shutdown() } } } From f13465246c382d5416acf732da2bfee2ed68b28c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 9 Mar 2018 18:01:10 -0500 Subject: [PATCH 177/356] include the full body in debug message --- main-command/src/main/scala/sbt/ServerHandler.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main-command/src/main/scala/sbt/ServerHandler.scala b/main-command/src/main/scala/sbt/ServerHandler.scala index 07fe8616e..ce9429c33 100644 --- a/main-command/src/main/scala/sbt/ServerHandler.scala +++ b/main-command/src/main/scala/sbt/ServerHandler.scala @@ -26,8 +26,8 @@ object ServerHandler { lazy val fallback: ServerHandler = ServerHandler({ handler => ServerIntent( - { case x => handler.log.debug(s"Unhandled notification received: ${x.method}") }, - { case x => handler.log.debug(s"Unhandled request received: ${x.method}") } + { case x => handler.log.debug(s"Unhandled notification received: ${x.method}: $x") }, + { case x => handler.log.debug(s"Unhandled request received: ${x.method}: $x") } ) }) } From 0433440c59c735d2ce908aaa300461759f5d914d Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 13 Mar 2018 23:42:19 +0900 Subject: [PATCH 178/356] move ServerHandler to internal per review --- main-command/src/main/scala/sbt/BasicKeys.scala | 1 + .../scala/sbt/{ => internal/server}/ServerHandler.scala | 2 ++ main/src/main/scala/sbt/Defaults.scala | 7 ++++++- main/src/main/scala/sbt/Keys.scala | 1 + main/src/main/scala/sbt/Project.scala | 1 + sbt/src/server-test/handshake/build.sbt | 2 ++ 6 files changed, 13 insertions(+), 1 deletion(-) rename main-command/src/main/scala/sbt/{ => internal/server}/ServerHandler.scala (98%) diff --git a/main-command/src/main/scala/sbt/BasicKeys.scala b/main-command/src/main/scala/sbt/BasicKeys.scala index 393397f3f..5ceeec21b 100644 --- a/main-command/src/main/scala/sbt/BasicKeys.scala +++ b/main-command/src/main/scala/sbt/BasicKeys.scala @@ -10,6 +10,7 @@ package sbt import java.io.File import sbt.internal.util.AttributeKey import sbt.internal.inc.classpath.ClassLoaderCache +import sbt.internal.server.ServerHandler import sbt.librarymanagement.ModuleID import sbt.util.Level diff --git a/main-command/src/main/scala/sbt/ServerHandler.scala b/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala similarity index 98% rename from main-command/src/main/scala/sbt/ServerHandler.scala rename to main-command/src/main/scala/sbt/internal/server/ServerHandler.scala index ce9429c33..47b4f4fbf 100644 --- a/main-command/src/main/scala/sbt/ServerHandler.scala +++ b/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala @@ -6,6 +6,8 @@ */ package sbt +package internal +package server import sjsonnew.JsonFormat import sbt.internal.protocol._ diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 1bdee43ff..36299ac25 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -26,7 +26,12 @@ import sbt.internal.librarymanagement.mavenint.{ PomExtraDependencyAttributes, SbtPomExtraProperties } -import sbt.internal.server.{ LanguageServerReporter, Definition, LanguageServerProtocol } +import sbt.internal.server.{ + LanguageServerReporter, + Definition, + LanguageServerProtocol, + ServerHandler +} import sbt.internal.testing.TestLogger import sbt.internal.util._ import sbt.internal.util.Attributed.data diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index df551cdd7..82191f556 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -42,6 +42,7 @@ import sbt.internal.{ } import sbt.io.{ FileFilter, WatchService } import sbt.internal.io.WatchState +import sbt.internal.server.ServerHandler import sbt.internal.util.{ AttributeKey, SourcePosition } import sbt.librarymanagement.Configurations.CompilerPlugin diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 38124e5a5..ea3a6b942 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -45,6 +45,7 @@ import sbt.internal.{ import sbt.internal.util.{ AttributeKey, AttributeMap, Dag, Relation, Settings, ~> } import sbt.internal.util.Types.{ const, idFun } import sbt.internal.util.complete.DefaultParsers +import sbt.internal.server.ServerHandler import sbt.librarymanagement.Configuration import sbt.util.{ Show, Level } import sjsonnew.JsonFormat diff --git a/sbt/src/server-test/handshake/build.sbt b/sbt/src/server-test/handshake/build.sbt index 1267ea46b..fcfe026ac 100644 --- a/sbt/src/server-test/handshake/build.sbt +++ b/sbt/src/server-test/handshake/build.sbt @@ -1,3 +1,5 @@ +import sbt.internal.ServerHandler + lazy val root = (project in file(".")) .settings( Global / serverLog / logLevel := Level.Debug, From 89722878923ae5db49021d15743dc53aecaf331a Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 14 Mar 2018 11:27:16 +0000 Subject: [PATCH 179/356] Cleanup ErrorCodes --- .../sbt/internal/langserver/ErrorCodes.scala | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/protocol/src/main/scala/sbt/internal/langserver/ErrorCodes.scala b/protocol/src/main/scala/sbt/internal/langserver/ErrorCodes.scala index 952cf7896..b7f9afc94 100644 --- a/protocol/src/main/scala/sbt/internal/langserver/ErrorCodes.scala +++ b/protocol/src/main/scala/sbt/internal/langserver/ErrorCodes.scala @@ -9,18 +9,39 @@ package sbt package internal package langserver +/** Holds the error codes for the LSP implementation here. */ object ErrorCodes { - // Defined by JSON RPC - val ParseError = -32700L - val InvalidRequest = -32600L - val MethodNotFound = -32601L - val InvalidParams = -32602L - val InternalError = -32603L - val serverErrorStart = -32099L - val serverErrorEnd = -32000L - val ServerNotInitialized = -32002L - val UnknownErrorCode = -32001L + // this is essentially a lookup table encoded in Scala, + // so heavy usage of vertical alignment is beneficial + // format: off - // Defined by the protocol. - val RequestCancelled = -32800L + // Defined by the JSON-RPC 2.0 Specification + // http://www.jsonrpc.org/specification#error_object + // + // The error codes from and including -32768 to -32000 are reserved for pre-defined errors. + // Any code within this range, but not defined explicitly below is reserved for future use. + // + // The error codes are nearly the same as those suggested for XML-RPC at the following url: + // http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php + // + val ParseError = -32700L // Invalid JSON was received by the server. + // An error occurred on the server while parsing the JSON text. + val InvalidRequest = -32600L // The JSON sent is not a valid Request object. + val MethodNotFound = -32601L // The method does not exist / is not available. + val InvalidParams = -32602L // Invalid method parameter(s). + val InternalError = -32603L // Internal JSON-RPC error. + + + // The range -32000 to -32099 are reserved for implementation-defined server-errors. + val serverErrorStart = -32099L // from LSP's spec code snippet + val serverErrorEnd = -32000L // from LSP's spec code snippet + + val UnknownErrorCode = -32001L // Defined by LSP + val ServerNotInitialized = -32002L // Defined by LSP + + + // The remainder of the space is available for application defined errors. + val RequestCancelled = -32800L // Defined by LSP + + // format: on } From 3530349e9ac6e0ea492678479ed2bb584503a598 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 14 Mar 2018 11:41:58 +0000 Subject: [PATCH 180/356] Rename ErrorCodes' UnknownErrorCode to UnknownServerError Allows for a non-server-specific unknown error code to be defined. --- .../src/main/scala/sbt/internal/langserver/ErrorCodes.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/src/main/scala/sbt/internal/langserver/ErrorCodes.scala b/protocol/src/main/scala/sbt/internal/langserver/ErrorCodes.scala index b7f9afc94..6128b25ba 100644 --- a/protocol/src/main/scala/sbt/internal/langserver/ErrorCodes.scala +++ b/protocol/src/main/scala/sbt/internal/langserver/ErrorCodes.scala @@ -36,7 +36,7 @@ object ErrorCodes { val serverErrorStart = -32099L // from LSP's spec code snippet val serverErrorEnd = -32000L // from LSP's spec code snippet - val UnknownErrorCode = -32001L // Defined by LSP + val UnknownServerError = -32001L // Defined by LSP val ServerNotInitialized = -32002L // Defined by LSP From 7baf97d2a63985290cfe321f0855c86363c114ed Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 14 Mar 2018 11:42:32 +0000 Subject: [PATCH 181/356] Introduce ErrorCodes.UnknownError Defined in the application defined errors range. --- protocol/src/main/scala/sbt/internal/langserver/ErrorCodes.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/protocol/src/main/scala/sbt/internal/langserver/ErrorCodes.scala b/protocol/src/main/scala/sbt/internal/langserver/ErrorCodes.scala index 6128b25ba..4b67ee90a 100644 --- a/protocol/src/main/scala/sbt/internal/langserver/ErrorCodes.scala +++ b/protocol/src/main/scala/sbt/internal/langserver/ErrorCodes.scala @@ -42,6 +42,7 @@ object ErrorCodes { // The remainder of the space is available for application defined errors. val RequestCancelled = -32800L // Defined by LSP + val UnknownError = -33000L // A generic error, unknown if the user or server is at fault. // format: on } From bde6365013a5303379f3b224235e544124d14365 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 12 Mar 2018 19:00:24 +0000 Subject: [PATCH 182/356] Add an optional exitCode to ExecStatusEvent so clients can use it --- .../sbt/protocol/ExecStatusEvent.scala | 23 +++++++++++++------ .../codec/ExecStatusEventFormats.scala | 4 +++- protocol/src/main/contraband/server.contra | 1 + 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/protocol/src/main/contraband-scala/sbt/protocol/ExecStatusEvent.scala b/protocol/src/main/contraband-scala/sbt/protocol/ExecStatusEvent.scala index 9af91314f..3f42e0316 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/ExecStatusEvent.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/ExecStatusEvent.scala @@ -9,22 +9,23 @@ final class ExecStatusEvent private ( val status: String, val channelName: Option[String], val execId: Option[String], - val commandQueue: Vector[String]) extends sbt.protocol.EventMessage() with Serializable { - + val commandQueue: Vector[String], + val exitCode: Option[Long]) extends sbt.protocol.EventMessage() with Serializable { + private def this(status: String, channelName: Option[String], execId: Option[String], commandQueue: Vector[String]) = this(status, channelName, execId, commandQueue, None) override def equals(o: Any): Boolean = o match { - case x: ExecStatusEvent => (this.status == x.status) && (this.channelName == x.channelName) && (this.execId == x.execId) && (this.commandQueue == x.commandQueue) + case x: ExecStatusEvent => (this.status == x.status) && (this.channelName == x.channelName) && (this.execId == x.execId) && (this.commandQueue == x.commandQueue) && (this.exitCode == x.exitCode) case _ => false } override def hashCode: Int = { - 37 * (37 * (37 * (37 * (37 * (17 + "sbt.protocol.ExecStatusEvent".##) + status.##) + channelName.##) + execId.##) + commandQueue.##) + 37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.protocol.ExecStatusEvent".##) + status.##) + channelName.##) + execId.##) + commandQueue.##) + exitCode.##) } override def toString: String = { - "ExecStatusEvent(" + status + ", " + channelName + ", " + execId + ", " + commandQueue + ")" + "ExecStatusEvent(" + status + ", " + channelName + ", " + execId + ", " + commandQueue + ", " + exitCode + ")" } - private[this] def copy(status: String = status, channelName: Option[String] = channelName, execId: Option[String] = execId, commandQueue: Vector[String] = commandQueue): ExecStatusEvent = { - new ExecStatusEvent(status, channelName, execId, commandQueue) + private[this] def copy(status: String = status, channelName: Option[String] = channelName, execId: Option[String] = execId, commandQueue: Vector[String] = commandQueue, exitCode: Option[Long] = exitCode): ExecStatusEvent = { + new ExecStatusEvent(status, channelName, execId, commandQueue, exitCode) } def withStatus(status: String): ExecStatusEvent = { copy(status = status) @@ -44,9 +45,17 @@ final class ExecStatusEvent private ( def withCommandQueue(commandQueue: Vector[String]): ExecStatusEvent = { copy(commandQueue = commandQueue) } + def withExitCode(exitCode: Option[Long]): ExecStatusEvent = { + copy(exitCode = exitCode) + } + def withExitCode(exitCode: Long): ExecStatusEvent = { + copy(exitCode = Option(exitCode)) + } } object ExecStatusEvent { def apply(status: String, channelName: Option[String], execId: Option[String], commandQueue: Vector[String]): ExecStatusEvent = new ExecStatusEvent(status, channelName, execId, commandQueue) def apply(status: String, channelName: String, execId: String, commandQueue: Vector[String]): ExecStatusEvent = new ExecStatusEvent(status, Option(channelName), Option(execId), commandQueue) + def apply(status: String, channelName: Option[String], execId: Option[String], commandQueue: Vector[String], exitCode: Option[Long]): ExecStatusEvent = new ExecStatusEvent(status, channelName, execId, commandQueue, exitCode) + def apply(status: String, channelName: String, execId: String, commandQueue: Vector[String], exitCode: Long): ExecStatusEvent = new ExecStatusEvent(status, Option(channelName), Option(execId), commandQueue, Option(exitCode)) } diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/ExecStatusEventFormats.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/ExecStatusEventFormats.scala index 7b906fb1a..0f9c1f10e 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/codec/ExecStatusEventFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/ExecStatusEventFormats.scala @@ -15,8 +15,9 @@ implicit lazy val ExecStatusEventFormat: JsonFormat[sbt.protocol.ExecStatusEvent val channelName = unbuilder.readField[Option[String]]("channelName") val execId = unbuilder.readField[Option[String]]("execId") val commandQueue = unbuilder.readField[Vector[String]]("commandQueue") + val exitCode = unbuilder.readField[Option[Long]]("exitCode") unbuilder.endObject() - sbt.protocol.ExecStatusEvent(status, channelName, execId, commandQueue) + sbt.protocol.ExecStatusEvent(status, channelName, execId, commandQueue, exitCode) case None => deserializationError("Expected JsObject but found None") } @@ -27,6 +28,7 @@ implicit lazy val ExecStatusEventFormat: JsonFormat[sbt.protocol.ExecStatusEvent builder.addField("channelName", obj.channelName) builder.addField("execId", obj.execId) builder.addField("commandQueue", obj.commandQueue) + builder.addField("exitCode", obj.exitCode) builder.endObject() } } diff --git a/protocol/src/main/contraband/server.contra b/protocol/src/main/contraband/server.contra index 2976be229..63c7b816d 100644 --- a/protocol/src/main/contraband/server.contra +++ b/protocol/src/main/contraband/server.contra @@ -43,6 +43,7 @@ type ExecStatusEvent implements EventMessage { channelName: String execId: String commandQueue: [String] + exitCode: Long @since("1.1.2") } interface SettingQueryResponse implements EventMessage {} From 98332c0891ace1ea826fab8f2e4054c08de0b791 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 12 Mar 2018 19:00:47 +0000 Subject: [PATCH 183/356] Reply to sbt/exec w/ a Response/Error w/ error code --- main/src/main/scala/sbt/MainLoop.scala | 55 ++++++++++++++++++- .../sbt/internal/server/NetworkChannel.scala | 8 ++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index 83b38b4c8..2ff80e17e 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -15,6 +15,7 @@ import jline.TerminalFactory import sbt.io.{ IO, Using } import sbt.internal.util.{ ErrorHandling, GlobalLogBacking } import sbt.internal.util.complete.DefaultParsers +import sbt.internal.langserver.ErrorCodes import sbt.util.Logger import sbt.protocol._ @@ -155,13 +156,63 @@ object MainLoop { state.log error errMsg state.fail } - StandardMain.exchange publishEventMessage ExecStatusEvent( + val doneEvent = ExecStatusEvent( "Done", channelName, exec.execId, - newState.remainingCommands.toVector map (_.commandLine)) + newState.remainingCommands.toVector map (_.commandLine), + exitCode(newState, state), + ) + if (doneEvent.execId.isDefined) { // send back a response or error + import sbt.protocol.codec.JsonProtocol._ + StandardMain.exchange publishEvent doneEvent + } else { // send back a notification + StandardMain.exchange publishEventMessage doneEvent + } newState } def logFullException(e: Throwable, log: Logger): Unit = State.logFullException(e, log) + + private[this] type ExitCode = Option[Long] + private[this] object ExitCode { + def apply(n: Long): ExitCode = Option(n) + val Success: ExitCode = ExitCode(0) + val Unknown: ExitCode = None + } + + private[this] def exitCode(state: State, prevState: State): ExitCode = { + exitCodeFromStateNext(state) match { + case ExitCode.Success => exitCodeFromStateOnFailure(state, prevState) + case x => x + } + } + + // State's "next" field indicates the next action for the command processor to take + // we'll use that to determine if the command failed + private[this] def exitCodeFromStateNext(state: State): ExitCode = { + state.next match { + case State.Continue => ExitCode.Success + case State.ClearGlobalLog => ExitCode.Success + case State.KeepLastLog => ExitCode.Success + case ret: State.Return => + ret.result match { + case exit: xsbti.Exit => ExitCode(exit.code().toLong) + case _: xsbti.Continue => ExitCode.Success + case _: xsbti.Reboot => ExitCode.Success + case x => + val clazz = if (x eq null) "" else " (class: " + x.getClass + ")" + state.log debug s"Unknown main result: $x$clazz" + ExitCode.Unknown + } + } + } + + // the shell command specifies an onFailure so that if an exception is thrown + // it's handled by executing the shell again, instead of the state failing + // so we also use that to indicate that the execution failed + private[this] def exitCodeFromStateOnFailure(state: State, prevState: State): ExitCode = + if (prevState.onFailure.isDefined && state.onFailure.isEmpty) ExitCode(ErrorCodes.UnknownError) + else ExitCode.Success + } diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 7c27063de..f8dfdf784 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -240,7 +240,13 @@ final class NetworkChannel(val name: String, if (isLanguageServerProtocol) { event match { case entry: StringEvent => logMessage(entry.level, entry.message) - case _ => langRespond(event, execId) + case entry: ExecStatusEvent => + entry.exitCode match { + case None => langRespond(event, entry.execId) + case Some(0) => langRespond(event, entry.execId) + case Some(exitCode) => langError(entry.execId, exitCode, "") + } + case _ => langRespond(event, execId) } } else { contentType match { From de690f4e41b14dc4b55e44c1ad4e5a43f3bdf0ff Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 9 Mar 2018 12:01:53 +0000 Subject: [PATCH 184/356] Collapse 1-line scaladocs --- .../server/LanguageServerProtocol.scala | 20 +++++-------------- .../scala/sbt/protocol/Serialization.scala | 10 ++-------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index 761073591..0083221b0 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -81,9 +81,7 @@ private[sbt] object LanguageServerProtocol { }) } -/** - * Implements Language Server Protocol . - */ +/** Implements Language Server Protocol . */ private[sbt] trait LanguageServerProtocol extends CommandChannel { self => lazy val internalJsonProtocol = new InitializeOptionFormats with sjsonnew.BasicJsonProtocol {} @@ -136,9 +134,7 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { self => } } - /** - * Respond back to Language Server's client. - */ + /** Respond back to Language Server's client. */ private[sbt] def jsonRpcRespond[A: JsonFormat](event: A, execId: Option[String]): Unit = { val m = JsonRpcResponseMessage("2.0", execId, Option(Converter.toJson[A](event).get), None) @@ -146,9 +142,7 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { self => publishBytes(bytes) } - /** - * Respond back to Language Server's client. - */ + /** Respond back to Language Server's client. */ private[sbt] def jsonRpcRespondError(execId: Option[String], code: Long, message: String): Unit = { @@ -158,9 +152,7 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { self => publishBytes(bytes) } - /** - * Respond back to Language Server's client. - */ + /** Respond back to Language Server's client. */ private[sbt] def jsonRpcRespondError[A: JsonFormat](execId: Option[String], code: Long, message: String, @@ -171,9 +163,7 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { self => publishBytes(bytes) } - /** - * Notify to Language Server's client. - */ + /** Notify to Language Server's client. */ private[sbt] def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit = { val m = JsonRpcNotificationMessage("2.0", method, Option(Converter.toJson[A](params).get)) diff --git a/protocol/src/main/scala/sbt/protocol/Serialization.scala b/protocol/src/main/scala/sbt/protocol/Serialization.scala index 75b9e7c83..1a62e53f7 100644 --- a/protocol/src/main/scala/sbt/protocol/Serialization.scala +++ b/protocol/src/main/scala/sbt/protocol/Serialization.scala @@ -41,10 +41,7 @@ object Serialization { CompactPrinter(json).getBytes("UTF-8") } - /** - * This formats the message according to JSON-RPC. - * http://www.jsonrpc.org/specification - */ + /** This formats the message according to JSON-RPC. http://www.jsonrpc.org/specification */ private[sbt] def serializeResponseMessage(message: JsonRpcResponseMessage): Array[Byte] = { import sbt.internal.protocol.codec.JsonRPCProtocol._ val json: JValue = Converter.toJson[JsonRpcResponseMessage](message).get @@ -57,10 +54,7 @@ object Serialization { body).getBytes("UTF-8") } - /** - * This formats the message according to JSON-RPC. - * http://www.jsonrpc.org/specification - */ + /** This formats the message according to JSON-RPC. http://www.jsonrpc.org/specification */ private[sbt] def serializeNotificationMessage( message: JsonRpcNotificationMessage): Array[Byte] = { import sbt.internal.protocol.codec.JsonRPCProtocol._ From 268b5111ab143fdb1ba1ca138dfc6b9425768268 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 9 Mar 2018 12:02:45 +0000 Subject: [PATCH 185/356] Format LSP methods --- .../server/LanguageServerProtocol.scala | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index 0083221b0..311eb405f 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -143,9 +143,11 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { self => } /** Respond back to Language Server's client. */ - private[sbt] def jsonRpcRespondError(execId: Option[String], - code: Long, - message: String): Unit = { + private[sbt] def jsonRpcRespondError( + execId: Option[String], + code: Long, + message: String, + ): Unit = { val e = JsonRpcResponseError(code, message, None) val m = JsonRpcResponseMessage("2.0", execId, None, Option(e)) val bytes = Serialization.serializeResponseMessage(m) @@ -153,10 +155,12 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { self => } /** Respond back to Language Server's client. */ - private[sbt] def jsonRpcRespondError[A: JsonFormat](execId: Option[String], - code: Long, - message: String, - data: A): Unit = { + private[sbt] def jsonRpcRespondError[A: JsonFormat]( + execId: Option[String], + code: Long, + message: String, + data: A, + ): Unit = { val e = JsonRpcResponseError(code, message, Option(Converter.toJson[A](data).get)) val m = JsonRpcResponseMessage("2.0", execId, None, Option(e)) val bytes = Serialization.serializeResponseMessage(m) From d3ef452a5f7288eb03205d0a13789f0fc2a0821e Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 9 Mar 2018 12:02:52 +0000 Subject: [PATCH 186/356] Extract jsonRpcRespondErrorImpl --- .../server/LanguageServerProtocol.scala | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index 311eb405f..8dc514d40 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -10,6 +10,7 @@ package internal package server import sjsonnew.JsonFormat +import sjsonnew.shaded.scalajson.ast.unsafe.JValue import sjsonnew.support.scalajson.unsafe.Converter import sbt.protocol.Serialization import sbt.protocol.{ SettingQuery => Q } @@ -143,16 +144,8 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { self => } /** Respond back to Language Server's client. */ - private[sbt] def jsonRpcRespondError( - execId: Option[String], - code: Long, - message: String, - ): Unit = { - val e = JsonRpcResponseError(code, message, None) - val m = JsonRpcResponseMessage("2.0", execId, None, Option(e)) - val bytes = Serialization.serializeResponseMessage(m) - publishBytes(bytes) - } + private[sbt] def jsonRpcRespondError(execId: Option[String], code: Long, message: String): Unit = + jsonRpcRespondErrorImpl(execId, code, message, None) /** Respond back to Language Server's client. */ private[sbt] def jsonRpcRespondError[A: JsonFormat]( @@ -160,8 +153,16 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { self => code: Long, message: String, data: A, + ): Unit = + jsonRpcRespondErrorImpl(execId, code, message, Option(Converter.toJson[A](data).get)) + + private[this] def jsonRpcRespondErrorImpl( + execId: Option[String], + code: Long, + message: String, + data: Option[JValue], ): Unit = { - val e = JsonRpcResponseError(code, message, Option(Converter.toJson[A](data).get)) + val e = JsonRpcResponseError(code, message, data) val m = JsonRpcResponseMessage("2.0", execId, None, Option(e)) val bytes = Serialization.serializeResponseMessage(m) publishBytes(bytes) From 5f56fa9f14ad48014f2fc7ce686d9a336e672f66 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 9 Mar 2018 12:06:29 +0000 Subject: [PATCH 187/356] Extract serializeResponse --- .../scala/sbt/protocol/Serialization.scala | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/protocol/src/main/scala/sbt/protocol/Serialization.scala b/protocol/src/main/scala/sbt/protocol/Serialization.scala index 1a62e53f7..21d798b80 100644 --- a/protocol/src/main/scala/sbt/protocol/Serialization.scala +++ b/protocol/src/main/scala/sbt/protocol/Serialization.scala @@ -8,7 +8,7 @@ package sbt package protocol -import sjsonnew.JsonFormat +import sjsonnew.{ JsonFormat, JsonWriter } import sjsonnew.support.scalajson.unsafe.{ Parser, Converter, CompactPrinter } import sjsonnew.shaded.scalajson.ast.unsafe.{ JValue, JObject, JString } import java.nio.ByteBuffer @@ -44,28 +44,28 @@ object Serialization { /** This formats the message according to JSON-RPC. http://www.jsonrpc.org/specification */ private[sbt] def serializeResponseMessage(message: JsonRpcResponseMessage): Array[Byte] = { import sbt.internal.protocol.codec.JsonRPCProtocol._ - val json: JValue = Converter.toJson[JsonRpcResponseMessage](message).get - val body = CompactPrinter(json) - val bodyBytes = body.getBytes("UTF-8") - - (s"Content-Length: ${bodyBytes.size}\r\n" + - s"Content-Type: $VsCode\r\n" + - "\r\n" + - body).getBytes("UTF-8") + serializeResponse(message) } /** This formats the message according to JSON-RPC. http://www.jsonrpc.org/specification */ private[sbt] def serializeNotificationMessage( - message: JsonRpcNotificationMessage): Array[Byte] = { + message: JsonRpcNotificationMessage, + ): Array[Byte] = { import sbt.internal.protocol.codec.JsonRPCProtocol._ - val json: JValue = Converter.toJson[JsonRpcNotificationMessage](message).get - val body = CompactPrinter(json) - val bodyBytes = body.getBytes("UTF-8") + serializeResponse(message) + } - (s"Content-Length: ${bodyBytes.size}\r\n" + - s"Content-Type: $VsCode\r\n" + - "\r\n" + - body).getBytes("UTF-8") + private[sbt] def serializeResponse[A: JsonWriter](message: A): Array[Byte] = { + val json: JValue = Converter.toJson[A](message).get + val body = CompactPrinter(json) + val bodyLength = body.getBytes("UTF-8").length + + Iterator( + s"Content-Length: $bodyLength", + s"Content-Type: $VsCode", + "", + body + ).mkString("\r\n").getBytes("UTF-8") } /** From 04ab2d30e15b456a6842594bdc7fb5b021b85c23 Mon Sep 17 00:00:00 2001 From: Hiroshi Ito Date: Fri, 16 Mar 2018 14:54:34 +0900 Subject: [PATCH 188/356] Improve instruction for locally built sbt in CONTRIBUTING.md --- CONTRIBUTING.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 190d5a0cc..7dc91fcdd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -188,10 +188,12 @@ sbt:sbtRoot> publishLocal ### Using the locally built sbt -To use the locally built sbt, set the version in `build.properties` file to `1.$MINOR.$PATCH-SNAPSHOT`. +The `publishLocal` above will build and publish version `1.$MINOR.$PATCH-SNAPSHOT` (e.g. 1.1.2-SNAPSHOT) to your local ivy repository. + +To use the locally built sbt, set the version in `build.properties` file in your project to `1.$MINOR.$PATCH-SNAPSHOT` then launch `sbt` (this can be the `sbt` launcher installed in your machine). ``` -$ cd ../hello +$ cd $YOUR_OWN_PROJECT $ sbt > compile ``` From 77ffbe896d461b98bab262b12ca86bb3045c7e88 Mon Sep 17 00:00:00 2001 From: tmiyamon Date: Fri, 16 Mar 2018 18:03:52 +0900 Subject: [PATCH 189/356] support test compile on saving in vscode --- .../main/scala/sbt/internal/server/LanguageServerProtocol.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index ce1131d0f..77314890a 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -38,7 +38,7 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { log.debug(s"onNotification: $notification") notification.method match { case "textDocument/didSave" => - append(Exec(";compile; collectAnalyses", None, Some(CommandSource(name)))) + append(Exec(";Test/compile; collectAnalyses", None, Some(CommandSource(name)))) case u => log.debug(s"Unhandled notification received: $u") } } From a5119a411c409cb0ce1f054253ec3d143c053da0 Mon Sep 17 00:00:00 2001 From: tiqwab Date: Fri, 16 Mar 2018 21:46:09 +0900 Subject: [PATCH 190/356] Fix handling id in jsonrpc model Fix #3861 --- .../codec/JsonRpcRequestMessageFormats.scala | 5 ++- .../codec/JsonRpcResponseMessageFormats.scala | 33 ++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcRequestMessageFormats.scala b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcRequestMessageFormats.scala index dac0987e4..2c1273534 100644 --- a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcRequestMessageFormats.scala +++ b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcRequestMessageFormats.scala @@ -24,7 +24,10 @@ trait JsonRpcRequestMessageFormats { val id = try { unbuilder.readField[String]("id") } catch { - case _: Throwable => unbuilder.readField[Long]("id").toString + case _: Throwable => { + val prefix = "\u2668" // Append prefix to show the original type was Number + prefix + unbuilder.readField[Long]("id").toString + } } val method = unbuilder.readField[String]("method") val params = unbuilder.lookupField("params") map { diff --git a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala index d10164e67..c9d943296 100644 --- a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala +++ b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala @@ -7,8 +7,8 @@ package sbt.internal.protocol.codec -import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } -import sjsonnew.shaded.scalajson.ast.unsafe.JValue +import _root_.sjsonnew.{ Builder, JsonFormat, Unbuilder, deserializationError } +import sjsonnew.shaded.scalajson.ast.unsafe._ trait JsonRpcResponseMessageFormats { self: sbt.internal.util.codec.JValueFormats @@ -45,10 +45,35 @@ trait JsonRpcResponseMessageFormats { } override def write[J](obj: sbt.internal.protocol.JsonRpcResponseMessage, builder: Builder[J]): Unit = { + // Parse given id to Long or String judging by prefix + def parseId(str: String): Either[Long, String] = { + if (str.startsWith("\u2668")) Left(str.substring(1).toLong) + else Right(str) + } + def parseResult(jValue: JValue): JValue = jValue match { + case JObject(jFields) => + val replaced = jFields map { + case field @ JField("execId", JString(str)) => + parseId(str) match { + case Right(strId) => field.copy(value = JString(strId)) + case Left(longId) => field.copy(value = JNumber(longId)) + } + case other => + other + } + JObject(replaced) + case other => + other + } builder.beginObject() builder.addField("jsonrpc", obj.jsonrpc) - builder.addField("id", obj.id) - builder.addField("result", obj.result) + obj.id foreach { id => + parseId(id) match { + case Right(strId) => builder.addField("id", strId) + case Left(longId) => builder.addField("id", longId) + } + } + builder.addField("result", obj.result map parseResult) builder.addField("error", obj.error) builder.endObject() } From e5d2588927e1513eab9eaf57a790fa1d8bc2833c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 18 Mar 2018 02:11:23 +0900 Subject: [PATCH 191/356] Add test case for number id in JSON-RPC --- sbt/src/test/scala/sbt/ServerSpec.scala | 29 +++++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/sbt/src/test/scala/sbt/ServerSpec.scala b/sbt/src/test/scala/sbt/ServerSpec.scala index 7ad307fd8..a328c6dfe 100644 --- a/sbt/src/test/scala/sbt/ServerSpec.scala +++ b/sbt/src/test/scala/sbt/ServerSpec.scala @@ -9,6 +9,7 @@ package sbt import org.scalatest._ import scala.concurrent._ +import scala.annotation.tailrec import java.io.{ InputStream, OutputStream } import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.{ ThreadFactory, ThreadPoolExecutor } @@ -23,15 +24,22 @@ class ServerSpec extends AsyncFlatSpec with Matchers { """{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "root/name" } }""", out) Thread.sleep(100) - val l2 = contentLength(in) - println(l2) - readLine(in) - readLine(in) - val x2 = readContentLength(in, l2) - println(x2) - assert(1 == 1) + assert(waitFor(in, 10) { s => + s contains """"id":3""" + }) } } + + @tailrec + private[this] def waitFor(in: InputStream, num: Int)(f: String => Boolean): Boolean = { + if (num < 0) false + else + readFrame(in) match { + case Some(x) if f(x) => true + case _ => + waitFor(in, num - 1)(f) + } + } } object ServerSpec { @@ -90,6 +98,13 @@ object ServerSpec { writeLine(message, out) } + def readFrame(in: InputStream): Option[String] = { + val l = contentLength(in) + readLine(in) + readLine(in) + readContentLength(in, l) + } + def contentLength(in: InputStream): Int = { readLine(in) map { line => line.drop(16).toInt From 6ceed00f48b85545071a7b2490c3177914017670 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 12 Mar 2018 17:49:43 +0000 Subject: [PATCH 192/356] Cleanup NetworkChannel --- build.sbt | 5 ++++- .../internal/server/LanguageServerProtocol.scala | 3 ++- .../scala/sbt/internal/server/NetworkChannel.scala | 13 +++---------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/build.sbt b/build.sbt index a5f333fed..2f21cfe7f 100644 --- a/build.sbt +++ b/build.sbt @@ -451,9 +451,12 @@ lazy val mainProj = (project in file("main")) sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", mimaSettings, mimaBinaryIssueFilters ++= Vector( - // Changed signature or removed something in the internal pacakge + // Changed signature or removed something in the internal package exclude[DirectMissingMethodProblem]("sbt.internal.*"), + // Made something final in the internal package + exclude[FinalClassProblem]("sbt.internal.*"), + // New and changed methods on KeyIndex. internal. exclude[ReversedMissingMethodProblem]("sbt.internal.KeyIndex.*"), diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index 8dc514d40..929ea21d1 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -20,7 +20,8 @@ import sbt.internal.langserver._ import sbt.internal.util.ObjectEvent import sbt.util.Logger -private[sbt] case class LangServerError(code: Long, message: String) extends Throwable(message) +private[sbt] final case class LangServerError(code: Long, message: String) + extends Throwable(message) private[sbt] object LanguageServerProtocol { lazy val internalJsonProtocol = new InitializeOptionFormats with sjsonnew.BasicJsonProtocol {} diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index e1dc9fac0..9e5d3e718 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -46,18 +46,12 @@ final class NetworkChannel(val name: String, private val VsCodeOld = "application/vscode-jsonrpc; charset=utf8" private lazy val jsonFormat = new sjsonnew.BasicJsonProtocol with JValueFormats {} - def setContentType(ct: String): Unit = synchronized { - _contentType = ct - } + def setContentType(ct: String): Unit = synchronized { _contentType = ct } def contentType: String = _contentType - protected def authenticate(token: String): Boolean = { - instance.authenticate(token) - } + protected def authenticate(token: String): Boolean = instance.authenticate(token) - protected def setInitialized(value: Boolean): Unit = { - initialized = value - } + protected def setInitialized(value: Boolean): Unit = initialized = value protected def authOptions: Set[ServerAuthentication] = auth @@ -74,7 +68,6 @@ final class NetworkChannel(val name: String, var bytesRead = 0 def resetChannelState(): Unit = { contentLength = 0 - // contentType = "" state = SingleLine } def tillEndOfLine: Option[Vector[Byte]] = { From 9c0ac90ee99c2145a42fc6af1f7a3aa03fc18990 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 12 Mar 2018 17:13:17 +0000 Subject: [PATCH 193/356] Make Watched use State#handleError --- build.sbt | 2 ++ main-command/src/main/scala/sbt/State.scala | 2 +- main-command/src/main/scala/sbt/Watched.scala | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index a5f333fed..a66cf9996 100644 --- a/build.sbt +++ b/build.sbt @@ -383,6 +383,8 @@ lazy val commandProj = (project in file("main-command")) // Replace nailgun socket stuff exclude[MissingClassProblem]("sbt.internal.NG*"), exclude[MissingClassProblem]("sbt.internal.ReferenceCountedFileDescriptor"), + // made private[sbt] method private[this] + exclude[DirectMissingMethodProblem]("sbt.State.handleException"), ), unmanagedSources in (Compile, headerCreate) := { val old = (unmanagedSources in (Compile, headerCreate)).value diff --git a/main-command/src/main/scala/sbt/State.scala b/main-command/src/main/scala/sbt/State.scala index eb15168fe..a3e178dc6 100644 --- a/main-command/src/main/scala/sbt/State.scala +++ b/main-command/src/main/scala/sbt/State.scala @@ -323,7 +323,7 @@ object State { import ExceptionCategory._ - private[sbt] def handleException(t: Throwable, s: State, log: Logger): State = { + private[this] def handleException(t: Throwable, s: State, log: Logger): State = { ExceptionCategory(t) match { case AlreadyHandled => () case m: MessageOnly => log.error(m.message) diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index 5424c8d7b..fad1b6a8d 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -112,9 +112,9 @@ object Watched { (triggered, newWatchState) } catch { case e: Exception => - val log = s.log - log.error("Error occurred obtaining files to watch. Terminating continuous execution...") - State.handleException(e, s, log) + s.log.error( + "Error occurred obtaining files to watch. Terminating continuous execution...") + s.handleError(e) (false, watchState) } From 00ce32f102092d1586513270c64d8f570d28ae80 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 12 Mar 2018 17:21:22 +0000 Subject: [PATCH 194/356] Cleanup CommandChannel --- build.sbt | 18 ++++++++---------- .../scala/sbt/internal/CommandChannel.scala | 2 +- .../scala/sbt/internal/ConsoleChannel.scala | 2 -- .../sbt/internal/server/NetworkChannel.scala | 2 -- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/build.sbt b/build.sbt index 87b10d702..c5e115e31 100644 --- a/build.sbt +++ b/build.sbt @@ -84,7 +84,14 @@ val mimaSettings = Def settings ( ).map { v => organization.value % moduleName.value % v cross (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled) }.toSet - } + }, + mimaBinaryIssueFilters ++= Seq( + // Changes in the internal pacakge + exclude[DirectMissingMethodProblem]("sbt.internal.*"), + exclude[FinalClassProblem]("sbt.internal.*"), + exclude[FinalMethodProblem]("sbt.internal.*"), + exclude[IncompatibleResultTypeProblem]("sbt.internal.*"), + ), ) lazy val sbtRoot: Project = (project in file(".")) @@ -182,9 +189,6 @@ val completeProj = (project in file("internal") / "util-complete") libraryDependencies += jline, mimaSettings, mimaBinaryIssueFilters ++= Seq( - // Changed signature or removed something in the internal pacakge - exclude[DirectMissingMethodProblem]("sbt.internal.*"), - exclude[IncompatibleResultTypeProblem]("sbt.internal.*"), ), ) .configure(addSbtIO, addSbtUtilControl) @@ -453,12 +457,6 @@ lazy val mainProj = (project in file("main")) sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", mimaSettings, mimaBinaryIssueFilters ++= Vector( - // Changed signature or removed something in the internal package - exclude[DirectMissingMethodProblem]("sbt.internal.*"), - - // Made something final in the internal package - exclude[FinalClassProblem]("sbt.internal.*"), - // New and changed methods on KeyIndex. internal. exclude[ReversedMissingMethodProblem]("sbt.internal.KeyIndex.*"), diff --git a/main-command/src/main/scala/sbt/internal/CommandChannel.scala b/main-command/src/main/scala/sbt/internal/CommandChannel.scala index 6b1b8e391..54c65cfd3 100644 --- a/main-command/src/main/scala/sbt/internal/CommandChannel.scala +++ b/main-command/src/main/scala/sbt/internal/CommandChannel.scala @@ -23,7 +23,7 @@ abstract class CommandChannel { def poll: Option[Exec] = Option(commandQueue.poll) def publishEvent[A: JsonFormat](event: A, execId: Option[String]): Unit - def publishEvent[A: JsonFormat](event: A): Unit + final def publishEvent[A: JsonFormat](event: A): Unit = publishEvent(event, None) def publishEventMessage(event: EventMessage): Unit def publishBytes(bytes: Array[Byte]): Unit def shutdown(): Unit diff --git a/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala b/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala index a0356234e..3f039f270 100644 --- a/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala +++ b/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala @@ -40,8 +40,6 @@ private[sbt] final class ConsoleChannel(val name: String) extends CommandChannel def publishEvent[A: JsonFormat](event: A, execId: Option[String]): Unit = () - def publishEvent[A: JsonFormat](event: A): Unit = () - def publishEventMessage(event: EventMessage): Unit = event match { case e: ConsolePromptEvent => diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 9e5d3e718..75c42f542 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -260,8 +260,6 @@ final class NetworkChannel(val name: String, } } - def publishEvent[A: JsonFormat](event: A): Unit = publishEvent(event, None) - def publishEventMessage(event: EventMessage): Unit = { if (isLanguageServerProtocol) { event match { From 8eb2d7389d5367d6bea91df0975747aa942f15a1 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 21 Mar 2018 10:16:01 -0700 Subject: [PATCH 195/356] Add test for async utest TestSuites Sometimes when utest runs async tests (i.e. tests that return a future) the test suite will fail even when none of the individual tests do. This is due to a data race in sbt. Most, but not all, of the time, this test will induce that race. --- sbt/src/sbt-test/tests/fork-async/build.sbt | 8 +++++++ .../src/test/scala/ForkAsyncTest.scala | 21 +++++++++++++++++++ sbt/src/sbt-test/tests/fork-async/test | 1 + 3 files changed, 30 insertions(+) create mode 100644 sbt/src/sbt-test/tests/fork-async/build.sbt create mode 100644 sbt/src/sbt-test/tests/fork-async/src/test/scala/ForkAsyncTest.scala create mode 100644 sbt/src/sbt-test/tests/fork-async/test diff --git a/sbt/src/sbt-test/tests/fork-async/build.sbt b/sbt/src/sbt-test/tests/fork-async/build.sbt new file mode 100644 index 000000000..bb42b0c17 --- /dev/null +++ b/sbt/src/sbt-test/tests/fork-async/build.sbt @@ -0,0 +1,8 @@ +testFrameworks += new TestFramework("utest.runner.Framework") + +lazy val root = (project in file(".")). + settings( + scalaVersion := "2.12.4", + libraryDependencies += "com.lihaoyi" %% "utest" % "0.6.4" % Test, + fork in Test := true + ) diff --git a/sbt/src/sbt-test/tests/fork-async/src/test/scala/ForkAsyncTest.scala b/sbt/src/sbt-test/tests/fork-async/src/test/scala/ForkAsyncTest.scala new file mode 100644 index 000000000..a8bc4c0f2 --- /dev/null +++ b/sbt/src/sbt-test/tests/fork-async/src/test/scala/ForkAsyncTest.scala @@ -0,0 +1,21 @@ +import utest._ + +import utest._ +import utest.framework._ + +import scala.concurrent.{ ExecutionContext, Promise } +import scala.util.Success + +object ForkAsyncTest extends TestSuite { + val g = ExecutionContext.global + val n = 10 + val (testNames, promises) = (1 to n).map(i => Tree(s"$i") -> Promise[Unit]).unzip + val testTrees = promises.zipWithIndex.map { case (p, i) => + new TestCallTree(Left { + if (i == (n - 1)) promises.foreach(p => g.execute(() => p.tryComplete(Success(())))) + p.future + }) + } + val tests = + Tests(nameTree = Tree("async", testNames: _*), callTree = new TestCallTree(Right(testTrees))) +} diff --git a/sbt/src/sbt-test/tests/fork-async/test b/sbt/src/sbt-test/tests/fork-async/test new file mode 100644 index 000000000..dfffb838b --- /dev/null +++ b/sbt/src/sbt-test/tests/fork-async/test @@ -0,0 +1 @@ +> test From 9b24e9f9ebffadfa7d715b457b9fb1b8d51c3bdb Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Tue, 20 Mar 2018 18:06:03 -0700 Subject: [PATCH 196/356] Use ConcurrentLinkedDeque for EventHandler ArrayList::add is not thread safe. I ran into cases where async tests using utests would fail even when all of the individual tests passed. This was because multiple threads called back into the handle method of the handler instance variable, which just delegated to eventList::add. When this happened, one of the events would get added to the list as a null reference, which would manifest as an NPE upstream on the master process. After this change, my tests stopped failing. --- testing/agent/src/main/java/sbt/ForkMain.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testing/agent/src/main/java/sbt/ForkMain.java b/testing/agent/src/main/java/sbt/ForkMain.java index 24acaf52b..a92d02506 100644 --- a/testing/agent/src/main/java/sbt/ForkMain.java +++ b/testing/agent/src/main/java/sbt/ForkMain.java @@ -17,6 +17,7 @@ import java.net.Socket; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.concurrent.*; @@ -294,7 +295,7 @@ final public class ForkMain { Task[] nestedTasks; final TaskDef taskDef = task.taskDef(); try { - final List eventList = new ArrayList(); + final Collection eventList = new ConcurrentLinkedDeque(); final EventHandler handler = new EventHandler() { public void handle(final Event e){ eventList.add(new ForkEvent(e)); } }; logDebug(os, " Running " + taskDef); nestedTasks = task.execute(handler, loggers); From 9d2d81645b28e90ab17c925ecf7c1b390e4e1ff0 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 24 Mar 2018 02:17:02 +0900 Subject: [PATCH 197/356] bump modules --- main/src/main/scala/sbt/Defaults.scala | 15 +++++++++------ project/Dependencies.scala | 10 +++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index b9ab939ef..3ae24adde 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -623,12 +623,15 @@ object Defaults extends BuildCommon { if (ScalaInstance.isDotty(scalaVersion.value)) file(ScalaArtifacts.dottyID(binVersion)) else file(ScalaArtifacts.CompilerID) - new ScalaInstance(scalaVersion.value, - makeClassLoader(state.value)(allJars.toList), - libraryJar, - compilerJar, - allJars.toArray, - None) + new ScalaInstance( + scalaVersion.value, + makeClassLoader(state.value)(allJars.toList), + makeClassLoader(state.value)(List(libraryJar)), + libraryJar, + compilerJar, + allJars.toArray, + None + ) } def scalaInstanceFromHome(dir: File): Initialize[Task[ScalaInstance]] = Def.task { ScalaInstance(dir)(makeClassLoader(state.value)) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index aa50fc3e1..94a583ea8 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -9,9 +9,9 @@ object Dependencies { // sbt modules private val ioVersion = "1.1.4" - private val utilVersion = "1.1.2" - private val lmVersion = "1.1.3" - private val zincVersion = "1.1.1" + private val utilVersion = "1.1.3" + private val lmVersion = "1.1.4" + private val zincVersion = "1.1.3" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion @@ -26,8 +26,8 @@ object Dependencies { private val libraryManagementCore = "org.scala-sbt" %% "librarymanagement-core" % lmVersion private val libraryManagementIvy = "org.scala-sbt" %% "librarymanagement-ivy" % lmVersion - val launcherInterface = "org.scala-sbt" % "launcher-interface" % "1.0.2" - val rawLauncher = "org.scala-sbt" % "launcher" % "1.0.2" + val launcherInterface = "org.scala-sbt" % "launcher-interface" % "1.0.3" + val rawLauncher = "org.scala-sbt" % "launcher" % "1.0.3" val testInterface = "org.scala-sbt" % "test-interface" % "1.0" val ipcSocket = "org.scala-sbt.ipcsocket" % "ipcsocket" % "1.0.0" From 67e1e4a9ffb5a2faa47f8d4696204760a83d584c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 24 Mar 2018 02:07:28 +0900 Subject: [PATCH 198/356] improve server testing previously I was using separate thread in a forked test to test the server, but that is not enough isolation to run multiple server tests. This adds `RunFromSourceMain.fork(workingDirectory: File)`, which allows us to run a fresh sbt on the given working directory. Next, I've refactored the stateful client-side buffer to a class `TestServer`. --- build.sbt | 6 +- .../src/test/scala/sbt/std/TestUtil.scala | 2 +- .../test/scala/sbt/RunFromSourceMain.scala | 21 +- sbt/src/test/scala/sbt/ServerSpec.scala | 193 ------------------ sbt/src/test/scala/testpkg/ServerSpec.scala | 184 +++++++++++++++++ 5 files changed, 207 insertions(+), 199 deletions(-) delete mode 100644 sbt/src/test/scala/sbt/ServerSpec.scala create mode 100644 sbt/src/test/scala/testpkg/ServerSpec.scala diff --git a/build.sbt b/build.sbt index 5e50c04aa..41723a157 100644 --- a/build.sbt +++ b/build.sbt @@ -497,7 +497,6 @@ lazy val sbtProj = (project in file("sbt")) normalizedName := "sbt", crossScalaVersions := Seq(baseScalaVersion), crossPaths := false, - javaOptions ++= Seq("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"), mimaSettings, mimaBinaryIssueFilters ++= sbtIgnoredProblems, BuildInfoPlugin.buildInfoDefaultSettings, @@ -506,10 +505,9 @@ lazy val sbtProj = (project in file("sbt")) buildInfoKeys in Test := Seq[BuildInfoKey]( // WORKAROUND https://github.com/sbt/sbt-buildinfo/issues/117 BuildInfoKey.map((fullClasspath in Compile).taskValue) { case (ident, cp) => ident -> cp.files }, + classDirectory in Compile, + classDirectory in Test, ), - connectInput in run in Test := true, - outputStrategy in run in Test := Some(StdoutOutput), - fork in Test := true, ) .configure(addSbtCompilerBridge) diff --git a/main-settings/src/test/scala/sbt/std/TestUtil.scala b/main-settings/src/test/scala/sbt/std/TestUtil.scala index df02fb29c..dc0098f19 100644 --- a/main-settings/src/test/scala/sbt/std/TestUtil.scala +++ b/main-settings/src/test/scala/sbt/std/TestUtil.scala @@ -27,6 +27,6 @@ object TestUtil { val mainClassesDir = buildinfo.TestBuildInfo.classDirectory val testClassesDir = buildinfo.TestBuildInfo.test_classDirectory val depsClasspath = buildinfo.TestBuildInfo.dependencyClasspath - mainClassesDir +: testClassesDir +: depsClasspath mkString ":" + mainClassesDir +: testClassesDir +: depsClasspath mkString java.io.File.pathSeparator } } diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala index 6816e24c7..2f4e81a51 100644 --- a/sbt/src/test/scala/sbt/RunFromSourceMain.scala +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -7,14 +7,33 @@ package sbt +import scala.concurrent.Future +import sbt.util.LogExchange import scala.annotation.tailrec - +import buildinfo.TestBuildInfo import xsbti._ object RunFromSourceMain { private val sbtVersion = "1.0.3" // "dev" private val scalaVersion = "2.12.4" + def fork(workingDirectory: File): Future[Unit] = { + val fo = ForkOptions() + .withWorkingDirectory(workingDirectory) + .withOutputStrategy(OutputStrategy.StdoutOutput) + implicit val runner = new ForkRun(fo) + val cp = { + TestBuildInfo.test_classDirectory +: TestBuildInfo.fullClasspath + } + val options = Vector(workingDirectory.toString) + val log = LogExchange.logger("RunFromSourceMain.fork", None, None) + import scala.concurrent.ExecutionContext.Implicits.global + Future { + Run.run("sbt.RunFromSourceMain", cp, options, log) + () + } + } + def main(args: Array[String]): Unit = args match { case Array() => sys.error(s"Must specify working directory as the first argument") case Array(wd, args @ _*) => run(file(wd), args) diff --git a/sbt/src/test/scala/sbt/ServerSpec.scala b/sbt/src/test/scala/sbt/ServerSpec.scala deleted file mode 100644 index a328c6dfe..000000000 --- a/sbt/src/test/scala/sbt/ServerSpec.scala +++ /dev/null @@ -1,193 +0,0 @@ -/* - * sbt - * Copyright 2011 - 2017, Lightbend, Inc. - * Copyright 2008 - 2010, Mark Harrah - * Licensed under BSD-3-Clause license (see LICENSE) - */ - -package sbt - -import org.scalatest._ -import scala.concurrent._ -import scala.annotation.tailrec -import java.io.{ InputStream, OutputStream } -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.{ ThreadFactory, ThreadPoolExecutor } -import sbt.protocol.ClientSocket - -class ServerSpec extends AsyncFlatSpec with Matchers { - import ServerSpec._ - - "server" should "start" in { - withBuildSocket("handshake") { (out, in, tkn) => - writeLine( - """{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "root/name" } }""", - out) - Thread.sleep(100) - assert(waitFor(in, 10) { s => - s contains """"id":3""" - }) - } - } - - @tailrec - private[this] def waitFor(in: InputStream, num: Int)(f: String => Boolean): Boolean = { - if (num < 0) false - else - readFrame(in) match { - case Some(x) if f(x) => true - case _ => - waitFor(in, num - 1)(f) - } - } -} - -object ServerSpec { - private val serverTestBase: File = new File(".").getAbsoluteFile / "sbt" / "src" / "server-test" - private val nextThreadId = new AtomicInteger(1) - private val threadGroup = Thread.currentThread.getThreadGroup() - val readBuffer = new Array[Byte](4096) - var buffer: Vector[Byte] = Vector.empty - var bytesRead = 0 - private val delimiter: Byte = '\n'.toByte - private val RetByte = '\r'.toByte - - private val threadFactory = new ThreadFactory() { - override def newThread(runnable: Runnable): Thread = { - val thread = - new Thread(threadGroup, - runnable, - s"sbt-test-server-threads-${nextThreadId.getAndIncrement}") - // Do NOT setDaemon because then the code in TaskExit.scala in sbt will insta-kill - // the backgrounded process, at least for the case of the run task. - thread - } - } - - private val executor = new ThreadPoolExecutor( - 0, /* corePoolSize */ - 1, /* maxPoolSize, max # of servers */ - 2, - java.util.concurrent.TimeUnit.SECONDS, - /* keep alive unused threads this long (if corePoolSize < maxPoolSize) */ - new java.util.concurrent.SynchronousQueue[Runnable](), - threadFactory - ) - - def backgroundRun(baseDir: File, args: Seq[String]): Unit = { - executor.execute(new Runnable { - def run(): Unit = { - RunFromSourceMain.run(baseDir, args) - } - }) - } - - def shutdown(): Unit = executor.shutdown() - - def withBuildSocket(testBuild: String)( - f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = { - IO.withTemporaryDirectory { temp => - IO.copyDirectory(serverTestBase / testBuild, temp / testBuild) - withBuildSocket(temp / testBuild)(f) - } - } - - def sendJsonRpc(message: String, out: OutputStream): Unit = { - writeLine(s"""Content-Length: ${message.size + 2}""", out) - writeLine("", out) - writeLine(message, out) - } - - def readFrame(in: InputStream): Option[String] = { - val l = contentLength(in) - readLine(in) - readLine(in) - readContentLength(in, l) - } - - def contentLength(in: InputStream): Int = { - readLine(in) map { line => - line.drop(16).toInt - } getOrElse (0) - } - - def readLine(in: InputStream): Option[String] = { - if (buffer.isEmpty) { - val bytesRead = in.read(readBuffer) - if (bytesRead > 0) { - buffer = buffer ++ readBuffer.toVector.take(bytesRead) - } - } - val delimPos = buffer.indexOf(delimiter) - if (delimPos > 0) { - val chunk0 = buffer.take(delimPos) - buffer = buffer.drop(delimPos + 1) - // remove \r at the end of line. - val chunk1 = if (chunk0.lastOption contains RetByte) chunk0.dropRight(1) else chunk0 - Some(new String(chunk1.toArray, "utf-8")) - } else None // no EOL yet, so skip this turn. - } - - def readContentLength(in: InputStream, length: Int): Option[String] = { - if (buffer.isEmpty) { - val bytesRead = in.read(readBuffer) - if (bytesRead > 0) { - buffer = buffer ++ readBuffer.toVector.take(bytesRead) - } - } - if (length <= buffer.size) { - val chunk = buffer.take(length) - buffer = buffer.drop(length) - Some(new String(chunk.toArray, "utf-8")) - } else None // have not read enough yet, so skip this turn. - } - - def writeLine(s: String, out: OutputStream): Unit = { - def writeEndLine(): Unit = { - val retByte: Byte = '\r'.toByte - val delimiter: Byte = '\n'.toByte - out.write(retByte.toInt) - out.write(delimiter.toInt) - out.flush - } - - if (s != "") { - out.write(s.getBytes("UTF-8")) - } - writeEndLine - } - - def withBuildSocket(baseDirectory: File)( - f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = { - backgroundRun(baseDirectory, Nil) - - val portfile = baseDirectory / "project" / "target" / "active.json" - - def waitForPortfile(n: Int): Unit = - if (portfile.exists) () - else { - if (n <= 0) sys.error(s"Timeout. $portfile is not found.") - else { - Thread.sleep(1000) - waitForPortfile(n - 1) - } - } - waitForPortfile(10) - val (sk, tkn) = ClientSocket.socket(portfile) - val out = sk.getOutputStream - val in = sk.getInputStream - - sendJsonRpc( - """{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { } } }""", - out) - - try { - f(out, in, tkn) - } finally { - sendJsonRpc( - """{ "jsonrpc": "2.0", "id": 9, "method": "sbt/exec", "params": { "commandLine": "exit" } }""", - out) - shutdown() - } - } -} diff --git a/sbt/src/test/scala/testpkg/ServerSpec.scala b/sbt/src/test/scala/testpkg/ServerSpec.scala new file mode 100644 index 000000000..f34eee6b1 --- /dev/null +++ b/sbt/src/test/scala/testpkg/ServerSpec.scala @@ -0,0 +1,184 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package testpkg + +import org.scalatest._ +import scala.concurrent._ +import scala.annotation.tailrec +import sbt.protocol.ClientSocket +import TestServer.withTestServer +import java.io.File +import sbt.io.syntax._ +import sbt.io.IO +import sbt.RunFromSourceMain + +class ServerSpec extends AsyncFreeSpec with Matchers { + "server" - { + "should start" in withTestServer("handshake") { p => + p.writeLine( + """{ "jsonrpc": "2.0", "id": "3", "method": "sbt/setting", "params": { "setting": "root/name" } }""") + assert(p.waitForString(10) { s => + s contains """"id":"3"""" + }) + } + + "return number id when number id is sent" in withTestServer("handshake") { p => + p.writeLine( + """{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "root/name" } }""") + assert(p.waitForString(10) { s => + s contains """"id":3""" + }) + } + } +} + +object TestServer { + private val serverTestBase: File = new File(".").getAbsoluteFile / "sbt" / "src" / "server-test" + + def withTestServer(testBuild: String)(f: TestServer => Future[Assertion]): Future[Assertion] = { + IO.withTemporaryDirectory { temp => + IO.copyDirectory(serverTestBase / testBuild, temp / testBuild) + withTestServer(temp / testBuild)(f) + } + } + + def withTestServer(baseDirectory: File)(f: TestServer => Future[Assertion]): Future[Assertion] = { + val testServer = TestServer(baseDirectory) + try { + f(testServer) + } finally { + testServer.bye() + } + } + + def hostLog(s: String): Unit = { + println(s"""[${scala.Console.MAGENTA}build-1${scala.Console.RESET}] $s""") + } +} + +case class TestServer(baseDirectory: File) { + import TestServer.hostLog + + val readBuffer = new Array[Byte](4096) + var buffer: Vector[Byte] = Vector.empty + var bytesRead = 0 + private val delimiter: Byte = '\n'.toByte + private val RetByte = '\r'.toByte + + hostLog("fork to a new sbt instance") + RunFromSourceMain.fork(baseDirectory) + lazy val portfile = baseDirectory / "project" / "target" / "active.json" + + hostLog("wait 30s until the server is ready to respond") + def waitForPortfile(n: Int): Unit = + if (portfile.exists) () + else { + if (n <= 0) sys.error(s"Timeout. $portfile is not found.") + else { + Thread.sleep(1000) + waitForPortfile(n - 1) + } + } + waitForPortfile(30) + + // make connection to the socket described in the portfile + val (sk, tkn) = ClientSocket.socket(portfile) + val out = sk.getOutputStream + val in = sk.getInputStream + + // initiate handshake + sendJsonRpc( + """{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { } } }""") + + def test(f: TestServer => Future[Assertion]): Future[Assertion] = { + f(this) + } + + def bye(): Unit = { + hostLog("sending exit") + sendJsonRpc( + """{ "jsonrpc": "2.0", "id": 9, "method": "sbt/exec", "params": { "commandLine": "exit" } }""") + } + + def sendJsonRpc(message: String): Unit = { + writeLine(s"""Content-Length: ${message.size + 2}""") + writeLine("") + writeLine(message) + } + + def writeLine(s: String): Unit = { + def writeEndLine(): Unit = { + val retByte: Byte = '\r'.toByte + val delimiter: Byte = '\n'.toByte + out.write(retByte.toInt) + out.write(delimiter.toInt) + out.flush + } + + if (s != "") { + out.write(s.getBytes("UTF-8")) + } + writeEndLine + } + + def readFrame: Option[String] = { + def getContentLength: Int = { + readLine map { line => + line.drop(16).toInt + } getOrElse (0) + } + + val l = getContentLength + readLine + readLine + readContentLength(l) + } + + @tailrec + final def waitForString(num: Int)(f: String => Boolean): Boolean = { + if (num < 0) false + else + readFrame match { + case Some(x) if f(x) => true + case _ => + waitForString(num - 1)(f) + } + } + + def readLine: Option[String] = { + if (buffer.isEmpty) { + val bytesRead = in.read(readBuffer) + if (bytesRead > 0) { + buffer = buffer ++ readBuffer.toVector.take(bytesRead) + } + } + val delimPos = buffer.indexOf(delimiter) + if (delimPos > 0) { + val chunk0 = buffer.take(delimPos) + buffer = buffer.drop(delimPos + 1) + // remove \r at the end of line. + val chunk1 = if (chunk0.lastOption contains RetByte) chunk0.dropRight(1) else chunk0 + Some(new String(chunk1.toArray, "utf-8")) + } else None // no EOL yet, so skip this turn. + } + + def readContentLength(length: Int): Option[String] = { + if (buffer.isEmpty) { + val bytesRead = in.read(readBuffer) + if (bytesRead > 0) { + buffer = buffer ++ readBuffer.toVector.take(bytesRead) + } + } + if (length <= buffer.size) { + val chunk = buffer.take(length) + buffer = buffer.drop(length) + Some(new String(chunk.toArray, "utf-8")) + } else None // have not read enough yet, so skip this turn. + } + +} From b111b05d5f0ab6fdfc5bbaf262c711932b9d77e5 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 24 Mar 2018 13:45:35 -0400 Subject: [PATCH 199/356] Fixes new command leaving target directory Fixes #2835 This fixes `new` command creating `target` directory by moving the `target` to a staging directory in the command itself. --- main/src/main/scala/sbt/TemplateCommand.scala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/TemplateCommand.scala b/main/src/main/scala/sbt/TemplateCommand.scala index 2da6dcb33..b30755c0c 100644 --- a/main/src/main/scala/sbt/TemplateCommand.scala +++ b/main/src/main/scala/sbt/TemplateCommand.scala @@ -26,12 +26,22 @@ private[sbt] object TemplateCommandUtil { private def templateCommandParser(state: State): Parser[Seq[String]] = (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map (_ => Nil)) - private def runTemplate(state: State, inputArg: Seq[String]): State = { + private def runTemplate(s0: State, inputArg: Seq[String]): State = { + import BuildPaths._ + val extracted0 = (Project extract s0) + val globalBase = getGlobalBase(s0) + val stagingDirectory = getStagingDirectory(s0, globalBase).getCanonicalFile + val templateStage = stagingDirectory / "new" + // This moves the target directory to a staging directory + // https://github.com/sbt/sbt/issues/2835 + val state = extracted0.appendWithSession(Seq( + Keys.target := templateStage + ), + s0) val infos = (state get templateResolverInfos getOrElse Nil).toList val log = state.globalLogging.full val extracted = (Project extract state) val (s2, ivyConf) = extracted.runTask(Keys.ivyConfiguration, state) - val globalBase = BuildPaths.getGlobalBase(state) val scalaModuleInfo = extracted.get(Keys.scalaModuleInfo in Keys.updateSbtClassifiers) val arguments = inputArg.toList ++ (state.remainingCommands match { From 25ab94d96afa3ed20eac1e057aeb309ab7fe612b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 25 Mar 2018 14:56:00 -0400 Subject: [PATCH 200/356] Fixes -error not suppressing startup logs Fixes #3849 This brings back the 0.13 logic: ```scala def setGlobalLogLevel(s: State, level: Level.Value): State = { s.globalLogging.full match { case a: AbstractLogger => a.setLevel(level) case _ => () } s.put(BasicKeys.explicitGlobalLogLevels, true).put(Keys.logLevel.key, level) } ``` --- main/src/main/scala/sbt/internal/LogManager.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/internal/LogManager.scala b/main/src/main/scala/sbt/internal/LogManager.scala index 9abec04c6..66aa49fce 100644 --- a/main/src/main/scala/sbt/internal/LogManager.scala +++ b/main/src/main/scala/sbt/internal/LogManager.scala @@ -229,8 +229,13 @@ object LogManager { // s // } - def setGlobalLogLevel(s: State, level: Level.Value): State = - s.put(BasicKeys.explicitGlobalLogLevels, true).put(Keys.logLevel.key, level) + def setGlobalLogLevel(s: State, level: Level.Value): State = { + val s1 = s.put(BasicKeys.explicitGlobalLogLevels, true).put(Keys.logLevel.key, level) + val gl = s1.globalLogging + LogExchange.unbindLoggerAppenders(gl.full.name) + LogExchange.bindLoggerAppenders(gl.full.name, (gl.backed -> level) :: Nil) + s1 + } // This is the default implementation for the relay appender val defaultRelay: Unit => Appender = _ => defaultRelayImpl From 006527d246649a5d72f95200576334e904545271 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 27 Mar 2018 09:08:52 +0100 Subject: [PATCH 201/356] Re-order SlashSyntaxSpec arbitraries --- .../src/test/scala/sbt/SlashSyntaxSpec.scala | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala index a4c802934..ea1bcf2ea 100644 --- a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala +++ b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala @@ -57,16 +57,42 @@ object BuildDSLInstances { implicit def arbAttrKey[A: Manifest]: Arbitrary[AttributeKey[_]] = Arbitrary(Gen.identifier map (AttributeKey[A](_))) - def withScope[K <: Scoped.ScopingSetting[K]](keyGen: Gen[K]): Arbitrary[K] = - Arbitrary(Gen.frequency( - 5 -> keyGen, - 1 -> (for (key <- keyGen; scope <- arbitrary[Scope]) yield key in scope) - )) + implicit val arbAttributeMap: Arbitrary[AttributeMap] = Arbitrary { + Gen.frequency( + 20 -> AttributeMap.empty, + 1 -> { + for (name <- Gen.identifier; isModule <- arbitrary[Boolean]) + yield + AttributeMap.empty + .put(AttributeKey[String]("name"), name) + .put(AttributeKey[Boolean]("isModule"), isModule) + } + ) + } + + implicit def arbScopeAxis[A: Arbitrary]: Arbitrary[ScopeAxis[A]] = + Arbitrary(Gen.oneOf[ScopeAxis[A]](This, Zero, arbitrary[A] map (Select(_)))) + + implicit def arbScope: Arbitrary[Scope] = Arbitrary( + for { + r <- arbitrary[ScopeAxis[Reference]] + c <- arbitrary[ScopeAxis[ConfigKey]] + t <- arbitrary[ScopeAxis[AttributeKey[_]]] + e <- arbitrary[ScopeAxis[AttributeMap]] + } yield Scope(r, c, t, e) + ) def genInputKey[A: Manifest]: Gen[InputKey[A]] = Gen.identifier map (InputKey[A](_)) def genSettingKey[A: Manifest]: Gen[SettingKey[A]] = Gen.identifier map (SettingKey[A](_)) def genTaskKey[A: Manifest]: Gen[TaskKey[A]] = Gen.identifier map (TaskKey[A](_)) + def withScope[K <: Scoped.ScopingSetting[K]](keyGen: Gen[K]): Arbitrary[K] = Arbitrary { + Gen.frequency( + 5 -> keyGen, + 1 -> (for (key <- keyGen; scope <- arbitrary[Scope]) yield key in scope) + ) + } + implicit def arbInputKey[A: Manifest]: Arbitrary[InputKey[A]] = withScope(genInputKey[A]) implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = withScope(genSettingKey[A]) implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = withScope(genTaskKey[A]) @@ -88,29 +114,6 @@ object BuildDSLInstances { implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = Arbitrary(genSettingKey[A]) implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = Arbitrary(genTaskKey[A]) } - - implicit def arbScopeAxis[A: Arbitrary]: Arbitrary[ScopeAxis[A]] = - Arbitrary(Gen.oneOf[ScopeAxis[A]](This, Zero, arbitrary[A] map (Select(_)))) - - implicit val arbAttributeMap: Arbitrary[AttributeMap] = Arbitrary { - Gen.frequency( - 20 -> AttributeMap.empty, - 1 -> (for (name <- Gen.identifier; isModule <- arbitrary[Boolean]) - yield AttributeMap.empty - .put(AttributeKey[String]("name"), name) - .put(AttributeKey[Boolean]("isModule"), isModule) - ) - ) - } - - implicit def arbScope: Arbitrary[Scope] = Arbitrary( - for { - r <- arbitrary[ScopeAxis[Reference]] - c <- arbitrary[ScopeAxis[ConfigKey]] - t <- arbitrary[ScopeAxis[AttributeKey[_]]] - e <- arbitrary[ScopeAxis[AttributeMap]] - } yield Scope(r, c, t, e) - ) } import BuildDSLInstances._ From 51ff72872bdc7de92ace2244d090ef032516c058 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 27 Mar 2018 01:21:38 +0100 Subject: [PATCH 202/356] De-generalise expectValue in SlashSyntaxSpec --- .../src/test/scala/sbt/SlashSyntaxSpec.scala | 32 +++---------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala index ea1bcf2ea..2c843d989 100644 --- a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala +++ b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala @@ -117,33 +117,6 @@ object BuildDSLInstances { } import BuildDSLInstances._ -object CustomEquality { - trait Eq[A] { - def equal(x: A, y: A): Boolean - } - - // Avoid reimplementing equality for other standard classes. - trait EqualLowPriority { - implicit def universal[A] = (x: A, y: A) => x == y - } - - object Eq extends EqualLowPriority { - def apply[A: Eq]: Eq[A] = implicitly - - implicit def eqScoped[A <: Scoped]: Eq[A] = (x, y) => x.scope == y.scope && x.key == y.key - } - - implicit class AnyWith_===[A](private val x: A) extends AnyVal { - def ===(y: A)(implicit z: Eq[A]): Boolean = z.equal(x, y) - def =?(y: A)(implicit z: Eq[A]): Prop = { - if (x === y) proved else falsified :| s"Expected $x but got $y" - } - } - - def expectValue[A: Eq](expected: A)(x: A) = expected =? x -} -import CustomEquality._ - object SlashSyntaxSpec extends Properties("SlashSyntax") with SlashSyntax { type Key[K] = Scoped.ScopingSetting[K] with Scoped @@ -296,4 +269,9 @@ object SlashSyntaxSpec extends Properties("SlashSyntax") with SlashSyntax { expectValue(k in ThisScope.copy(project = r, config = c, task = t))(r / c / t / k)) check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] } + + def expectValue(expected: Scoped)(x: Scoped) = { + val equals = x.scope == expected.scope && x.key == expected.key + if (equals) proved else falsified :| s"Expected $expected but got $x" + } } From 42b61a4f8ebfdcb3344bfc83812da3216f5717f0 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 27 Mar 2018 02:14:33 +0100 Subject: [PATCH 203/356] Dedup keys in SlashSyntaxSpec --- .../src/test/scala/sbt/SlashSyntaxSpec.scala | 145 +++++------------- 1 file changed, 41 insertions(+), 104 deletions(-) diff --git a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala index 2c843d989..804031cb3 100644 --- a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala +++ b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala @@ -82,6 +82,8 @@ object BuildDSLInstances { } yield Scope(r, c, t, e) ) + type Key = K forSome { type K <: Scoped.ScopingSetting[K] with Scoped } + def genInputKey[A: Manifest]: Gen[InputKey[A]] = Gen.identifier map (InputKey[A](_)) def genSettingKey[A: Manifest]: Gen[SettingKey[A]] = Gen.identifier map (SettingKey[A](_)) def genTaskKey[A: Manifest]: Gen[TaskKey[A]] = Gen.identifier map (TaskKey[A](_)) @@ -97,16 +99,18 @@ object BuildDSLInstances { implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = withScope(genSettingKey[A]) implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = withScope(genTaskKey[A]) - implicit def arbScoped[A: Manifest](implicit + implicit def arbKey[A: Manifest]( + implicit arbInputKey: Arbitrary[InputKey[A]], arbSettingKey: Arbitrary[SettingKey[A]], arbTaskKey: Arbitrary[TaskKey[A]], - ): Arbitrary[Scoped] = { - Arbitrary(Gen.frequency( - 15 -> arbitrary[InputKey[A]], // 15,431 - 20 -> arbitrary[SettingKey[A]], // 19,645 - 23 -> arbitrary[TaskKey[A]], // 22,867 - )) + ): Arbitrary[Key] = Arbitrary { + def convert[T](g: Gen[T]) = g.asInstanceOf[Gen[Key]] + Gen.frequency( + 15431 -> convert(arbitrary[InputKey[A]]), + 19645 -> convert(arbitrary[SettingKey[A]]), + 22867 -> convert(arbitrary[TaskKey[A]]), + ) } object WithoutScope { @@ -114,160 +118,93 @@ object BuildDSLInstances { implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = Arbitrary(genSettingKey[A]) implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = Arbitrary(genTaskKey[A]) } + + implicit def arbScoped[A: Manifest]: Arbitrary[Scoped] = Arbitrary(arbitrary[Key]) } import BuildDSLInstances._ object SlashSyntaxSpec extends Properties("SlashSyntax") with SlashSyntax { - type Key[K] = Scoped.ScopingSetting[K] with Scoped - property("Global / key == key in Global") = { - def check[K <: Key[K]: Arbitrary] = forAll((k: K) => expectValue(k in Global)(Global / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + forAll((k: Key) => expectValue(k in Global)(Global / k)) } property("Reference / key == key in Reference") = { - def check[K <: Key[K]: Arbitrary] = forAll((r: Reference, k: K) => expectValue(k in r)(r / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + forAll((r: Reference, k: Key) => expectValue(k in r)(r / k)) } property("Reference / Config / key == key in Reference in Config") = { - def check[K <: Key[K]: Arbitrary] = - forAll((r: Reference, c: ConfigKey, k: K) => expectValue(k in r in c)(r / c / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + forAll((r: Reference, c: ConfigKey, k: Key) => expectValue(k in r in c)(r / c / k)) } property("Reference / task.key / key == key in Reference in task") = { - def check[K <: Key[K]: Arbitrary] = - forAll((r: Reference, t: Scoped, k: K) => expectValue(k in (r, t))(r / t.key / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + forAll((r: Reference, t: Scoped, k: Key) => expectValue(k in (r, t))(r / t.key / k)) } property("Reference / task / key ~= key in Reference in task") = { import WithoutScope._ - def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = - forAll((r: Reference, t: T, k: K) => expectValue(k in (r, t))(r / t / k)) - (true - && check[InputKey[String], InputKey[String]] - && check[InputKey[String], SettingKey[String]] - && check[InputKey[String], TaskKey[String]] - && check[SettingKey[String], InputKey[String]] - && check[SettingKey[String], SettingKey[String]] - && check[SettingKey[String], TaskKey[String]] - && check[TaskKey[String], InputKey[String]] - && check[TaskKey[String], SettingKey[String]] - && check[TaskKey[String], TaskKey[String]] - ) + forAll((r: Reference, t: Key, k: Key) => expectValue(k in (r, t))(r / t / k)) } property("Reference / Config / task.key / key == key in Reference in Config in task") = { - def check[K <: Key[K]: Arbitrary] = - forAll((r: Reference, c: ConfigKey, t: Scoped, k: K) => - expectValue(k in (r, c, t))(r / c / t.key / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + forAll { (r: Reference, c: ConfigKey, t: Scoped, k: Key) => + expectValue(k in (r, c, t))(r / c / t.key / k) + } } property("Reference / Config / task / key ~= key in Reference in Config in task") = { import WithoutScope._ - def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = - forAll((r: Reference, c: ConfigKey, t: T, k: K) => expectValue(k in (r, c, t))(r / c / t / k)) - (true - && check[InputKey[String], InputKey[String]] - && check[InputKey[String], SettingKey[String]] - && check[InputKey[String], TaskKey[String]] - && check[SettingKey[String], InputKey[String]] - && check[SettingKey[String], SettingKey[String]] - && check[SettingKey[String], TaskKey[String]] - && check[TaskKey[String], InputKey[String]] - && check[TaskKey[String], SettingKey[String]] - && check[TaskKey[String], TaskKey[String]] - ) + forAll { (r: Reference, c: ConfigKey, t: Key, k: Key) => + expectValue(k in (r, c, t))(r / c / t / k) + } } property("Config / key == key in Config") = { - def check[K <: Key[K]: Arbitrary] = - forAll((c: ConfigKey, k: K) => expectValue(k in c)(c / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + forAll((c: ConfigKey, k: Key) => expectValue(k in c)(c / k)) } property("Config / task.key / key == key in Config in task") = { - def check[K <: Key[K]: Arbitrary] = - forAll((c: ConfigKey, t: Scoped, k: K) => expectValue(k in c in t)(c / t.key / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + forAll((c: ConfigKey, t: Scoped, k: Key) => expectValue(k in c in t)(c / t.key / k)) } property("Config / task / key ~= key in Config in task") = { import WithoutScope._ - def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = - forAll((c: ConfigKey, t: T, k: K) => expectValue(k in c in t)(c / t / k)) - (true - && check[InputKey[String], InputKey[String]] - && check[InputKey[String], SettingKey[String]] - && check[InputKey[String], TaskKey[String]] - && check[SettingKey[String], InputKey[String]] - && check[SettingKey[String], SettingKey[String]] - && check[SettingKey[String], TaskKey[String]] - && check[TaskKey[String], InputKey[String]] - && check[TaskKey[String], SettingKey[String]] - && check[TaskKey[String], TaskKey[String]] - ) + forAll((c: ConfigKey, t: Key, k: Key) => expectValue(k in c in t)(c / t / k)) } property("task.key / key == key in task") = { - def check[K <: Key[K]: Arbitrary] = - forAll((t: Scoped, k: K) => expectValue(k in t)(t.key / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + forAll((t: Scoped, k: Key) => expectValue(k in t)(t.key / k)) } property("task / key ~= key in task") = { import WithoutScope._ - def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = - forAll((t: T, k: K) => expectValue(k in t)(t / k)) - (true - && check[InputKey[String], InputKey[String]] - && check[InputKey[String], SettingKey[String]] - && check[InputKey[String], TaskKey[String]] - && check[SettingKey[String], InputKey[String]] - && check[SettingKey[String], SettingKey[String]] - && check[SettingKey[String], TaskKey[String]] - && check[TaskKey[String], InputKey[String]] - && check[TaskKey[String], SettingKey[String]] - && check[TaskKey[String], TaskKey[String]] - ) + forAll((t: Key, k: Key) => expectValue(k in t)(t / k)) } property("Scope / key == key in Scope") = { - def check[K <: Key[K]: Arbitrary] = forAll((s: Scope, k: K) => expectValue(k in s)(s / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + forAll((s: Scope, k: Key) => expectValue(k in s)(s / k)) } property("Reference? / key == key in ThisScope.copy(..)") = { - def check[K <: Key[K]: Arbitrary] = - forAll((r: ScopeAxis[Reference], k: K) => - expectValue(k in ThisScope.copy(project = r))(r / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + forAll { (r: ScopeAxis[Reference], k: Key) => + expectValue(k in ThisScope.copy(project = r))(r / k) + } } property("Reference? / ConfigKey? / key == key in ThisScope.copy(..)") = { - def check[K <: Key[K]: Arbitrary] = - forAll((r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], k: K) => - expectValue(k in ThisScope.copy(project = r, config = c))(r / c / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + forAll((r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], k: Key) => + expectValue(k in ThisScope.copy(project = r, config = c))(r / c / k)) } // property("Reference? / AttributeKey? / key == key in ThisScope.copy(..)") = { -// def check[K <: Key[K]: Arbitrary] = -// forAll( -// (r: ScopeAxis[Reference], t: ScopeAxis[AttributeKey[_]], k: K) => -// expectValue(k in ThisScope.copy(project = r, task = t))(r / t / k)) -// check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] +// forAll((r: ScopeAxis[Reference], t: ScopeAxis[AttributeKey[_]], k: AnyKey) => +// expectValue(k in ThisScope.copy(project = r, task = t))(r / t / k)) // } property("Reference? / ConfigKey? / AttributeKey? / key == key in ThisScope.copy(..)") = { - def check[K <: Key[K]: Arbitrary] = - forAll( - (r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], t: ScopeAxis[AttributeKey[_]], k: K) => - expectValue(k in ThisScope.copy(project = r, config = c, task = t))(r / c / t / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + forAll { + (r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], t: ScopeAxis[AttributeKey[_]], k: Key) => + expectValue(k in ThisScope.copy(project = r, config = c, task = t))(r / c / t / k) + } } def expectValue(expected: Scoped)(x: Scoped) = { From 3c66f39744d054b073dd9755abb111455b372589 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 27 Mar 2018 09:14:55 +0100 Subject: [PATCH 204/356] Allow SlashSyntaxSpec to be Scalafmt formatted Previously: [warn] /d/sbt/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala:44: error: illegal start of simple expression [warn] ) [warn] ^ --- .../src/test/scala/sbt/SlashSyntaxSpec.scala | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala index 804031cb3..e68d3ba0a 100644 --- a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala +++ b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala @@ -13,7 +13,16 @@ import java.io.File import sbt.io.IO import sbt.SlashSyntax import sbt.{ Scope, ScopeAxis, Scoped, Select, This, Zero }, Scope.{ Global, ThisScope } -import sbt.{ BuildRef, LocalProject, LocalRootProject, ProjectRef, Reference, RootProject, ThisBuild, ThisProject } +import sbt.{ + BuildRef, + LocalProject, + LocalRootProject, + ProjectRef, + Reference, + RootProject, + ThisBuild, + ThisProject +} import sbt.ConfigKey import sbt.librarymanagement.syntax._ import sbt.{ InputKey, SettingKey, TaskKey } @@ -34,13 +43,13 @@ object BuildDSLInstances { implicit val arbReference: Arbitrary[Reference] = Arbitrary { Gen.frequency( - 1 -> arbitrary[BuildRef], // 96 - 100 -> ThisBuild, // 10,271 - 3 -> LocalRootProject, // 325 - 23 -> arbitrary[ProjectRef], // 2,283 - 3 -> ThisProject, // 299 - 4 -> arbitrary[LocalProject], // 436 - 11 -> arbitrary[RootProject], // 1,133 + 96 -> arbitrary[BuildRef], + 10271 -> ThisBuild, + 325 -> LocalRootProject, + 2283 -> arbitrary[ProjectRef], + 299 -> ThisProject, + 436 -> arbitrary[LocalProject], + 1133 -> arbitrary[RootProject], ) } From d19329666ac64c9a28a1dd5be83b7a2f55c02794 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 27 Mar 2018 01:27:25 +0100 Subject: [PATCH 205/356] Re-use Scoped.scoped* methods --- main-settings/src/main/scala/sbt/Structure.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/main-settings/src/main/scala/sbt/Structure.scala b/main-settings/src/main/scala/sbt/Structure.scala index bcedc4171..7091e3fe3 100644 --- a/main-settings/src/main/scala/sbt/Structure.scala +++ b/main-settings/src/main/scala/sbt/Structure.scala @@ -628,7 +628,7 @@ object InputKey { apply(AttributeKey[InputTask[T]](label, description, extendScoped(extend1, extendN), rank)) def apply[T](akey: AttributeKey[InputTask[T]]): InputKey[T] = - new InputKey[T] { val key = akey; def scope = Scope.ThisScope } + Scoped.scopedInput(Scope.ThisScope, akey) } /** Constructs TaskKeys, which are associated with tasks to define a setting.*/ @@ -657,8 +657,7 @@ object TaskKey { ): TaskKey[T] = apply(AttributeKey[Task[T]](label, description, extendScoped(extend1, extendN), rank)) - def apply[T](akey: AttributeKey[Task[T]]): TaskKey[T] = - new TaskKey[T] { val key = akey; def scope = Scope.ThisScope } + def apply[T](akey: AttributeKey[Task[T]]): TaskKey[T] = Scoped.scopedTask(Scope.ThisScope, akey) def local[T: Manifest]: TaskKey[T] = apply[T](AttributeKey.local[Task[T]]) } @@ -689,8 +688,7 @@ object SettingKey { ): SettingKey[T] = apply(AttributeKey[T](label, description, extendScoped(extend1, extendN), rank)) - def apply[T](akey: AttributeKey[T]): SettingKey[T] = - new SettingKey[T] { val key = akey; def scope = Scope.ThisScope } + def apply[T](akey: AttributeKey[T]): SettingKey[T] = Scoped.scopedSetting(Scope.ThisScope, akey) def local[T: Manifest: OptJsonWriter]: SettingKey[T] = apply[T](AttributeKey.local[T]) } From 35decb6ee5e4f7333c268eda941a002c507cb587 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 21 Mar 2018 15:53:08 +0000 Subject: [PATCH 206/356] Deprecate Scope.transformTaskName --- main-settings/src/main/scala/sbt/Scope.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/main-settings/src/main/scala/sbt/Scope.scala b/main-settings/src/main/scala/sbt/Scope.scala index 0bd3e27c6..e3409fc99 100644 --- a/main-settings/src/main/scala/sbt/Scope.scala +++ b/main-settings/src/main/scala/sbt/Scope.scala @@ -236,6 +236,7 @@ object Scope { def showProject012Style = (ref: Reference) => Reference.display(ref) + "/" + @deprecated("No longer used", "1.1.3") def transformTaskName(s: String) = { val parts = s.split("-+") (parts.take(1) ++ parts.drop(1).map(_.capitalize)).mkString From f607b6c73ffe2f6c4dbae0553a25ba1b66f56947 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 27 Mar 2018 06:52:32 -0400 Subject: [PATCH 207/356] sbt 1.1.2 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 31334bbd3..05313438a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.1 +sbt.version=1.1.2 From 1b77b353334af641599592f77b8dadf8105d5d8f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 27 Mar 2018 06:53:05 -0400 Subject: [PATCH 208/356] bump to 1.1.3-SNAPSHOT --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 5e50c04aa..d2b19aef3 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def buildLevelSettings: Seq[Setting[_]] = inThisBuild( Seq( organization := "org.scala-sbt", - version := "1.1.2-SNAPSHOT", + version := "1.1.3-SNAPSHOT", description := "sbt is an interactive build tool", bintrayOrganization := Some("sbt"), bintrayRepository := { @@ -79,7 +79,7 @@ val mimaSettings = Def settings ( mimaPreviousArtifacts := { Seq( "1.0.0", "1.0.1", "1.0.2", "1.0.3", "1.0.4", - "1.1.0", "1.1.1", + "1.1.0", "1.1.1", "1.1.2", ).map { v => organization.value % moduleName.value % v cross (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled) }.toSet From 2e86eed151a33abd4a89196dc2e47b5957b51e5d Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 27 Mar 2018 15:06:20 +0100 Subject: [PATCH 209/356] Split BuildSettingsInstances out of SlashSyntaxSpec.scala --- .../scala/sbt/BuildSettingsInstances.scala | 131 ++++++++++++++++++ .../src/test/scala/sbt/SlashSyntaxSpec.scala | 125 +---------------- 2 files changed, 136 insertions(+), 120 deletions(-) create mode 100644 main-settings/src/test/scala/sbt/BuildSettingsInstances.scala diff --git a/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala b/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala new file mode 100644 index 000000000..6ca193ccf --- /dev/null +++ b/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala @@ -0,0 +1,131 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt.test + +import org.scalacheck.{ Test => _, _ }, Arbitrary.arbitrary, Gen._ + +import java.io.File +import sbt.io.IO +import sbt.{ Scope, ScopeAxis, Scoped, Select, This, Zero } +import sbt.{ + BuildRef, + LocalProject, + LocalRootProject, + ProjectRef, + Reference, + RootProject, + ThisBuild, + ThisProject +} +import sbt.ConfigKey +import sbt.librarymanagement.syntax._ +import sbt.{ InputKey, SettingKey, TaskKey } +import sbt.internal.util.{ AttributeKey, AttributeMap } + +object BuildSettingsInstances { + val genFile: Gen[File] = Gen.oneOf(new File("."), new File("/tmp")) // for now.. + + implicit val arbBuildRef: Arbitrary[BuildRef] = Arbitrary(genFile map (f => BuildRef(IO toURI f))) + + implicit val arbProjectRef: Arbitrary[ProjectRef] = + Arbitrary(for (f <- genFile; id <- Gen.identifier) yield ProjectRef(f, id)) + + implicit val arbLocalProject: Arbitrary[LocalProject] = + Arbitrary(arbitrary[String] map LocalProject) + + implicit val arbRootProject: Arbitrary[RootProject] = Arbitrary(genFile map (RootProject(_))) + + implicit val arbReference: Arbitrary[Reference] = Arbitrary { + Gen.frequency( + 96 -> arbitrary[BuildRef], + 10271 -> ThisBuild, + 325 -> LocalRootProject, + 2283 -> arbitrary[ProjectRef], + 299 -> ThisProject, + 436 -> arbitrary[LocalProject], + 1133 -> arbitrary[RootProject], + ) + } + + implicit def arbConfigKey: Arbitrary[ConfigKey] = Arbitrary { + Gen.frequency( + 2 -> const[ConfigKey](Compile), + 2 -> const[ConfigKey](Test), + 1 -> const[ConfigKey](Runtime), + 1 -> const[ConfigKey](IntegrationTest), + 1 -> const[ConfigKey](Provided), + ) + } + + implicit def arbAttrKey[A: Manifest]: Arbitrary[AttributeKey[_]] = + Arbitrary(Gen.identifier map (AttributeKey[A](_))) + + implicit val arbAttributeMap: Arbitrary[AttributeMap] = Arbitrary { + Gen.frequency( + 20 -> AttributeMap.empty, + 1 -> { + for (name <- Gen.identifier; isModule <- arbitrary[Boolean]) + yield + AttributeMap.empty + .put(AttributeKey[String]("name"), name) + .put(AttributeKey[Boolean]("isModule"), isModule) + } + ) + } + + implicit def arbScopeAxis[A: Arbitrary]: Arbitrary[ScopeAxis[A]] = + Arbitrary(Gen.oneOf[ScopeAxis[A]](This, Zero, arbitrary[A] map (Select(_)))) + + implicit def arbScope: Arbitrary[Scope] = Arbitrary( + for { + r <- arbitrary[ScopeAxis[Reference]] + c <- arbitrary[ScopeAxis[ConfigKey]] + t <- arbitrary[ScopeAxis[AttributeKey[_]]] + e <- arbitrary[ScopeAxis[AttributeMap]] + } yield Scope(r, c, t, e) + ) + + type Key = K forSome { type K <: Scoped.ScopingSetting[K] with Scoped } + + def genInputKey[A: Manifest]: Gen[InputKey[A]] = Gen.identifier map (InputKey[A](_)) + def genSettingKey[A: Manifest]: Gen[SettingKey[A]] = Gen.identifier map (SettingKey[A](_)) + def genTaskKey[A: Manifest]: Gen[TaskKey[A]] = Gen.identifier map (TaskKey[A](_)) + + def withScope[K <: Scoped.ScopingSetting[K]](keyGen: Gen[K]): Arbitrary[K] = Arbitrary { + Gen.frequency( + 5 -> keyGen, + 1 -> (for (key <- keyGen; scope <- arbitrary[Scope]) yield key in scope) + ) + } + + implicit def arbInputKey[A: Manifest]: Arbitrary[InputKey[A]] = withScope(genInputKey[A]) + implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = withScope(genSettingKey[A]) + implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = withScope(genTaskKey[A]) + + implicit def arbKey[A: Manifest]( + implicit + arbInputKey: Arbitrary[InputKey[A]], + arbSettingKey: Arbitrary[SettingKey[A]], + arbTaskKey: Arbitrary[TaskKey[A]], + ): Arbitrary[Key] = Arbitrary { + def convert[T](g: Gen[T]) = g.asInstanceOf[Gen[Key]] + Gen.frequency( + 15431 -> convert(arbitrary[InputKey[A]]), + 19645 -> convert(arbitrary[SettingKey[A]]), + 22867 -> convert(arbitrary[TaskKey[A]]), + ) + } + + object WithoutScope { + implicit def arbInputKey[A: Manifest]: Arbitrary[InputKey[A]] = Arbitrary(genInputKey[A]) + implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = Arbitrary(genSettingKey[A]) + implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = Arbitrary(genTaskKey[A]) + } + + implicit def arbScoped[A: Manifest]: Arbitrary[Scoped] = Arbitrary(arbitrary[Key]) +} diff --git a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala index e68d3ba0a..b3b77c71d 100644 --- a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala +++ b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala @@ -7,130 +7,15 @@ package sbt.test -import org.scalacheck.{ Test => _, _ }, Arbitrary.arbitrary, Gen._, Prop._ +import org.scalacheck.{ Test => _, _ }, Prop._ -import java.io.File -import sbt.io.IO import sbt.SlashSyntax -import sbt.{ Scope, ScopeAxis, Scoped, Select, This, Zero }, Scope.{ Global, ThisScope } -import sbt.{ - BuildRef, - LocalProject, - LocalRootProject, - ProjectRef, - Reference, - RootProject, - ThisBuild, - ThisProject -} +import sbt.{ Scope, ScopeAxis, Scoped }, Scope.{ Global, ThisScope } +import sbt.Reference import sbt.ConfigKey -import sbt.librarymanagement.syntax._ -import sbt.{ InputKey, SettingKey, TaskKey } -import sbt.internal.util.{ AttributeKey, AttributeMap } +import sbt.internal.util.AttributeKey -object BuildDSLInstances { - val genFile: Gen[File] = Gen.oneOf(new File("."), new File("/tmp")) // for now.. - - implicit val arbBuildRef: Arbitrary[BuildRef] = Arbitrary(genFile map (f => BuildRef(IO toURI f))) - - implicit val arbProjectRef: Arbitrary[ProjectRef] = - Arbitrary(for (f <- genFile; id <- Gen.identifier) yield ProjectRef(f, id)) - - implicit val arbLocalProject: Arbitrary[LocalProject] = - Arbitrary(arbitrary[String] map LocalProject) - - implicit val arbRootProject: Arbitrary[RootProject] = Arbitrary(genFile map (RootProject(_))) - - implicit val arbReference: Arbitrary[Reference] = Arbitrary { - Gen.frequency( - 96 -> arbitrary[BuildRef], - 10271 -> ThisBuild, - 325 -> LocalRootProject, - 2283 -> arbitrary[ProjectRef], - 299 -> ThisProject, - 436 -> arbitrary[LocalProject], - 1133 -> arbitrary[RootProject], - ) - } - - implicit def arbConfigKey: Arbitrary[ConfigKey] = Arbitrary { - Gen.frequency( - 2 -> const[ConfigKey](Compile), - 2 -> const[ConfigKey](Test), - 1 -> const[ConfigKey](Runtime), - 1 -> const[ConfigKey](IntegrationTest), - 1 -> const[ConfigKey](Provided), - ) - } - - implicit def arbAttrKey[A: Manifest]: Arbitrary[AttributeKey[_]] = - Arbitrary(Gen.identifier map (AttributeKey[A](_))) - - implicit val arbAttributeMap: Arbitrary[AttributeMap] = Arbitrary { - Gen.frequency( - 20 -> AttributeMap.empty, - 1 -> { - for (name <- Gen.identifier; isModule <- arbitrary[Boolean]) - yield - AttributeMap.empty - .put(AttributeKey[String]("name"), name) - .put(AttributeKey[Boolean]("isModule"), isModule) - } - ) - } - - implicit def arbScopeAxis[A: Arbitrary]: Arbitrary[ScopeAxis[A]] = - Arbitrary(Gen.oneOf[ScopeAxis[A]](This, Zero, arbitrary[A] map (Select(_)))) - - implicit def arbScope: Arbitrary[Scope] = Arbitrary( - for { - r <- arbitrary[ScopeAxis[Reference]] - c <- arbitrary[ScopeAxis[ConfigKey]] - t <- arbitrary[ScopeAxis[AttributeKey[_]]] - e <- arbitrary[ScopeAxis[AttributeMap]] - } yield Scope(r, c, t, e) - ) - - type Key = K forSome { type K <: Scoped.ScopingSetting[K] with Scoped } - - def genInputKey[A: Manifest]: Gen[InputKey[A]] = Gen.identifier map (InputKey[A](_)) - def genSettingKey[A: Manifest]: Gen[SettingKey[A]] = Gen.identifier map (SettingKey[A](_)) - def genTaskKey[A: Manifest]: Gen[TaskKey[A]] = Gen.identifier map (TaskKey[A](_)) - - def withScope[K <: Scoped.ScopingSetting[K]](keyGen: Gen[K]): Arbitrary[K] = Arbitrary { - Gen.frequency( - 5 -> keyGen, - 1 -> (for (key <- keyGen; scope <- arbitrary[Scope]) yield key in scope) - ) - } - - implicit def arbInputKey[A: Manifest]: Arbitrary[InputKey[A]] = withScope(genInputKey[A]) - implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = withScope(genSettingKey[A]) - implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = withScope(genTaskKey[A]) - - implicit def arbKey[A: Manifest]( - implicit - arbInputKey: Arbitrary[InputKey[A]], - arbSettingKey: Arbitrary[SettingKey[A]], - arbTaskKey: Arbitrary[TaskKey[A]], - ): Arbitrary[Key] = Arbitrary { - def convert[T](g: Gen[T]) = g.asInstanceOf[Gen[Key]] - Gen.frequency( - 15431 -> convert(arbitrary[InputKey[A]]), - 19645 -> convert(arbitrary[SettingKey[A]]), - 22867 -> convert(arbitrary[TaskKey[A]]), - ) - } - - object WithoutScope { - implicit def arbInputKey[A: Manifest]: Arbitrary[InputKey[A]] = Arbitrary(genInputKey[A]) - implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = Arbitrary(genSettingKey[A]) - implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = Arbitrary(genTaskKey[A]) - } - - implicit def arbScoped[A: Manifest]: Arbitrary[Scoped] = Arbitrary(arbitrary[Key]) -} -import BuildDSLInstances._ +import BuildSettingsInstances._ object SlashSyntaxSpec extends Properties("SlashSyntax") with SlashSyntax { property("Global / key == key in Global") = { From 25988d22567b24d27c56d8becc4b4ac6143fb63e Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 26 Mar 2018 23:57:34 +0100 Subject: [PATCH 210/356] Cleanup test/ParseKey --- main/src/test/scala/ParseKey.scala | 216 +++++++++++------------------ 1 file changed, 82 insertions(+), 134 deletions(-) diff --git a/main/src/test/scala/ParseKey.scala b/main/src/test/scala/ParseKey.scala index 1f17d9288..23d9d0778 100644 --- a/main/src/test/scala/ParseKey.scala +++ b/main/src/test/scala/ParseKey.scala @@ -8,166 +8,114 @@ package sbt import Def.{ displayFull, displayMasked, ScopedKey } -import sbt.internal.{ TestBuild, Resolve } -import TestBuild._ -import sbt.internal.util.complete._ +import sbt.internal.{ TestBuild, Resolve }, TestBuild._ +import sbt.internal.util.complete.Parser -import org.scalacheck._ -import Gen._ -import Prop._ -import Arbitrary.arbBool +import org.scalacheck._, Arbitrary.arbitrary, Gen._, Prop._ /** * Tests that the scoped key parser in Act can correctly parse a ScopedKey converted by Def.show*Key. * This includes properly resolving omitted components. */ object ParseKey extends Properties("Key parser test") { - final val MaxKeys = 5 - final val MaxScopedKeys = 100 - - implicit val gstructure = genStructure - - property("An explicitly specified axis is always parsed to that explicit value") = - forAllNoShrink(structureDefinedKey) { (skm: StructureKeyMask) => - import skm.{ structure, key, mask => mask0 } - - val hasZeroConfig = key.scope.config == Zero - val mask = if (hasZeroConfig) mask0.copy(project = true) else mask0 - val expected = resolve(structure, key, mask) - // Note that this explicitly displays the configuration axis set to Zero. - // This is to disambiguate `proj/Zero/name`, which could render potentially - // as `Zero/name`, but could be interpretted as `Zero/Zero/name`. - val s = displayMasked(key, mask, hasZeroConfig) - ("Key: " + displayPedantic(key)) |: - parseExpected(structure, s, expected, mask) - } - - property("An unspecified project axis resolves to the current project") = - forAllNoShrink(structureDefinedKey) { (skm: StructureKeyMask) => - import skm.{ structure, key } - - val mask = skm.mask.copy(project = false) - val string = displayMasked(key, mask) - // skip when config axis is set to Zero - val hasZeroConfig = key.scope.config == Zero - - ("Key: " + displayPedantic(key)) |: - ("Mask: " + mask) |: - ("Current: " + structure.current) |: - parse(structure, string) { - case Left(_) => false - case Right(_) if hasZeroConfig => true - case Right(sk) => sk.scope.project == Select(structure.current) - } - } - - property("An unspecified task axis resolves to Zero") = forAllNoShrink(structureDefinedKey) { + property("An explicitly specified axis is always parsed to that explicit value") = forAll { (skm: StructureKeyMask) => import skm.{ structure, key } - val mask = skm.mask.copy(task = false) - val string = displayMasked(key, mask) + val hasZeroConfig = key.scope.config == Zero + val mask = if (hasZeroConfig) skm.mask.copy(project = true) else skm.mask + // Note that this explicitly displays the configuration axis set to Zero. + // This is to disambiguate `proj/Zero/name`, which could render potentially + // as `Zero/name`, but could be interpreted as `Zero/Zero/name`. + val expected = ScopedKey( + Resolve(structure.extra, Select(structure.current), key.key, mask)(key.scope), + key.key + ) + parseCheck(structure, key, mask, hasZeroConfig)( + sk => + Project.equal(sk, expected, mask) + :| s"$sk.key == $expected.key: ${sk.key == expected.key}" + :| s"${sk.scope} == ${expected.scope}: ${Scope.equal(sk.scope, expected.scope, mask)}" + ) :| s"Expected: ${displayFull(expected)}" + } - ("Key: " + displayPedantic(key)) |: - ("Mask: " + mask) |: - parse(structure, string) { - case Left(_) => false - case Right(sk) => sk.scope.task == Zero - } + property("An unspecified project axis resolves to the current project") = forAll { + (skm: StructureKeyMask) => + import skm.{ structure, key } + val mask = skm.mask.copy(project = false) + // skip when config axis is set to Zero + val hasZeroConfig = key.scope.config == Zero + parseCheck(structure, key, mask)( + sk => + (hasZeroConfig || sk.scope.project == Select(structure.current)) + :| s"Current: ${structure.current}" + ) + } + + property("An unspecified task axis resolves to Zero") = forAll { (skm: StructureKeyMask) => + import skm.{ structure, key } + val mask = skm.mask.copy(task = false) + parseCheck(structure, key, mask)(_.scope.task == Zero) } property( "An unspecified configuration axis resolves to the first configuration directly defining the key or else Zero") = - forAllNoShrink(structureDefinedKey) { (skm: StructureKeyMask) => + forAll { (skm: StructureKeyMask) => import skm.{ structure, key } val mask = ScopeMask(config = false) - val string = displayMasked(key, mask) val resolvedConfig = Resolve.resolveConfig(structure.extra, key.key, mask)(key.scope).config - - ("Key: " + displayPedantic(key)) |: - ("Mask: " + mask) |: - ("Expected configuration: " + resolvedConfig.map(_.name)) |: - parse(structure, string) { - case Right(sk) => (sk.scope.config == resolvedConfig) || (sk.scope == Scope.GlobalScope) - case Left(_) => false - } + parseCheck(structure, key, mask)( + sk => (sk.scope.config == resolvedConfig) || (sk.scope == Scope.GlobalScope) + ) :| s"Expected configuration: ${resolvedConfig map (_.name)}" } - def displayPedantic(scoped: ScopedKey[_]): String = - Scope.displayPedantic(scoped.scope, scoped.key.label) - - lazy val structureDefinedKey: Gen[StructureKeyMask] = structureKeyMask { s => - for (scope <- TestBuild.scope(s.env); key <- oneOf(s.allAttributeKeys.toSeq)) - yield ScopedKey(scope, key) - } - def structureKeyMask(genKey: Structure => Gen[ScopedKey[_]])( - implicit maskGen: Gen[ScopeMask], - structureGen: Gen[Structure]): Gen[StructureKeyMask] = - for (mask <- maskGen; structure <- structureGen; key <- genKey(structure)) - yield new StructureKeyMask(structure, key, mask) - final class StructureKeyMask(val structure: Structure, val key: ScopedKey[_], val mask: ScopeMask) - - def resolve(structure: Structure, key: ScopedKey[_], mask: ScopeMask): ScopedKey[_] = - ScopedKey(Resolve(structure.extra, Select(structure.current), key.key, mask)(key.scope), - key.key) - - def parseExpected(structure: Structure, - s: String, - expected: ScopedKey[_], - mask: ScopeMask): Prop = - ("Expected: " + displayFull(expected)) |: - ("Mask: " + mask) |: - parse(structure, s) { - case Left(_) => false - case Right(sk) => - (s"${sk}.key == ${expected}.key: ${sk.key == expected.key}") |: - (s"${sk.scope} == ${expected.scope}: ${Scope.equal(sk.scope, expected.scope, mask)}") |: - Project.equal(sk, expected, mask) - } - - def parse(structure: Structure, s: String)(f: Either[String, ScopedKey[_]] => Prop): Prop = { - val parser = makeParser(structure) - val parsed = DefaultParsers.result(parser, s).left.map(_().toString) - val showParsed = parsed.right.map(displayFull) - ("Key string: '" + s + "'") |: - ("Parsed: " + showParsed) |: - ("Structure: " + structure) |: - f(parsed) - } - - // Here we're shadowing the in-scope implicit called `mkEnv` for this method - // so that it will use the passed-in `Gen` rather than the one imported - // from TestBuild. - def genStructure(implicit mkEnv: Gen[Env]): Gen[Structure] = - structureGenF { (scopes: Seq[Scope], env: Env, current: ProjectRef) => - val settings = - for { - scope <- scopes - t <- env.tasks - } yield Def.setting(ScopedKey(scope, t.key), Def.value("")) - TestBuild.structure(env, settings, current) - } - - // Here we're shadowing the in-scope implicit called `mkEnv` for this method - // so that it will use the passed-in `Gen` rather than the one imported - // from TestBuild. - def structureGenF(f: (Seq[Scope], Env, ProjectRef) => Structure)( - implicit mkEnv: Gen[Env]): Gen[Structure] = - structureGen((s, e, p) => Gen.const(f(s, e, p))) - // Here we're shadowing the in-scope implicit called `mkEnv` for this method - // so that it will use the passed-in `Gen` rather than the one imported - // from TestBuild. - def structureGen(f: (Seq[Scope], Env, ProjectRef) => Gen[Structure])( - implicit mkEnv: Gen[Env]): Gen[Structure] = + implicit val arbStructure: Arbitrary[Structure] = Arbitrary { for { env <- mkEnv loadFactor <- choose(0.0, 1.0) scopes <- pickN(loadFactor, env.allFullScopes) current <- oneOf(env.allProjects.unzip._1) - structure <- f(scopes, env, current) + structure <- { + val settings = for (scope <- scopes; t <- env.tasks) + yield Def.setting(ScopedKey(scope, t.key), Def.value("")) + TestBuild.structure(env, settings, current) + } } yield structure + } - // pickN is a function that randomly picks load % items from the from sequence. + final class StructureKeyMask(val structure: Structure, val key: ScopedKey[_], val mask: ScopeMask) + + implicit val arbStructureKeyMask: Arbitrary[StructureKeyMask] = Arbitrary { + for { + mask <- maskGen + structure <- arbitrary[Structure] + key <- for { + scope <- TestBuild.scope(structure.env) + key <- oneOf(structure.allAttributeKeys.toSeq) + } yield ScopedKey(scope, key) + } yield new StructureKeyMask(structure, key, mask) + } + + def parseCheck( + structure: Structure, + key: ScopedKey[_], + mask: ScopeMask, + showZeroConfig: Boolean = false, + )(f: ScopedKey[_] => Prop): Prop = { + val s = displayMasked(key, mask, showZeroConfig) + val parser = makeParser(structure) + val parsed = Parser.result(parser, s).left.map(_().toString) + ( + parsed.fold(_ => falsified, f) + :| s"Key: ${Scope.displayPedantic(key.scope, key.key.label)}" + :| s"Mask: $mask" + :| s"Key string: '$s'" + :| s"Parsed: ${parsed.right.map(displayFull)}" + :| s"Structure: $structure" + ) + } + + // pickN is a function that randomly picks load % items from the "from" sequence. // The rest of the tests expect at least one item, so I changed it to return 1 in case of 0. def pickN[T](load: Double, from: Seq[T]): Gen[Seq[T]] = - pick(Math.max((load * from.size).toInt, 1), from) + pick((load * from.size).toInt max 1, from) } From e30c0fbd1eaadd18ccad59b35316402aa2e667ba Mon Sep 17 00:00:00 2001 From: Heikki Vesalainen Date: Wed, 28 Mar 2018 14:29:22 +0000 Subject: [PATCH 211/356] Update to Jline 2.14.6 This version of Jline fixes three things for Emacs users: - ANSI colors are now enabled for Emacs. - Terminal echo is now disabled for Emacs. - History is enabled for all dump terminals. --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 94a583ea8..10fc91517 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -97,7 +97,7 @@ object Dependencies { "com.eed3si9n" %% "sjson-new-scalajson" % contrabandSjsonNewVersion.value } - val jline = "jline" % "jline" % "2.14.4" + val jline = "jline" % "jline" % "2.14.6" val scalatest = "org.scalatest" %% "scalatest" % "3.0.4" val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.13.4" val specs2 = "org.specs2" %% "specs2-junit" % "4.0.1" From 289077a40566aa5e9c7a19f04cd66ffdfff780dd Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 16 Mar 2018 17:42:02 +0000 Subject: [PATCH 212/356] Re-introduce Command.process This was an unnecessary removal in e83564a6b71639c7e51bbc002c2d083cf8c9ab5a. --- main-command/src/main/scala/sbt/Command.scala | 10 ++++++++++ main/src/main/scala/sbt/MainLoop.scala | 9 +-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/main-command/src/main/scala/sbt/Command.scala b/main-command/src/main/scala/sbt/Command.scala index 9d9be7a33..6ee8e0e44 100644 --- a/main-command/src/main/scala/sbt/Command.scala +++ b/main-command/src/main/scala/sbt/Command.scala @@ -163,6 +163,16 @@ object Command { case Some(c) => c(state) }) + def process(command: String, state: State): State = { + val parser = combine(state.definedCommands) + parse(command, parser(state)) match { + case Right(s) => s() // apply command. command side effects happen here + case Left(errMsg) => + state.log error errMsg + state.fail + } + } + def invalidValue(label: String, allowed: Iterable[String])(value: String): String = s"Not a valid $label: $value" + similar(value, allowed) diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index 02897f7a0..e2c1fe0f2 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -17,7 +17,6 @@ import scala.util.control.NonFatal import sbt.io.{ IO, Using } import sbt.internal.util.{ ErrorHandling, GlobalLogBacking } -import sbt.internal.util.complete.Parser import sbt.internal.langserver.ErrorCodes import sbt.util.Logger import sbt.protocol._ @@ -148,13 +147,7 @@ object MainLoop { val channelName = exec.source map (_.channelName) StandardMain.exchange publishEventMessage ExecStatusEvent("Processing", channelName, exec.execId, Vector()) - val parser = Command combine state.definedCommands - val newState = Parser.parse(exec.commandLine, parser(state)) match { - case Right(s) => s() // apply command. command side effects happen here - case Left(errMsg) => - state.log error errMsg - state.fail - } + val newState = Command.process(exec.commandLine, state) val doneEvent = ExecStatusEvent( "Done", channelName, From fff32db7ceca4f933dc9a903da76ede4598a468e Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 28 Mar 2018 17:09:12 -0700 Subject: [PATCH 213/356] Use MacOSXWatchService instead of PollingWatchService This watch service should be more responsive and significantly reduce the disk overhead of the polling based service for large repos. --- main-command/src/main/scala/sbt/Watched.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index 8b8385b61..1c2396593 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -141,7 +141,7 @@ object Watched { FileSystems.getDefault.newWatchService() case _ if Properties.isMac => // WatchService is slow on macOS - use old polling mode - new PollingWatchService(PollDelay) + new MacOSXWatchService(PollDelay) case _ => FileSystems.getDefault.newWatchService() } From 88f50ce35d006dad6b810c801d10fdb92af3943a Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 30 Mar 2018 23:09:01 -0400 Subject: [PATCH 214/356] Fix console, JLine issue Fixes #3482 --- main-actions/src/main/scala/sbt/Console.scala | 7 +++---- main/src/main/scala/sbt/Defaults.scala | 6 ++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/main-actions/src/main/scala/sbt/Console.scala b/main-actions/src/main/scala/sbt/Console.scala index c2792d595..d94484030 100644 --- a/main-actions/src/main/scala/sbt/Console.scala +++ b/main-actions/src/main/scala/sbt/Console.scala @@ -9,8 +9,8 @@ package sbt import java.io.File import sbt.internal.inc.AnalyzingCompiler +import sbt.internal.util.JLine import sbt.util.Logger - import xsbti.compile.{ Inputs, Compilers } import scala.util.Try @@ -41,11 +41,10 @@ final class Console(compiler: AnalyzingCompiler) { implicit log: Logger): Try[Unit] = { def console0() = compiler.console(classpath, options, initialCommands, cleanupCommands, log)(loader, bindings) - // TODO: Fix JLine - //JLine.withJLine(Run.executeTrapExit(console0, log)) - Run.executeTrapExit(console0, log) + JLine.withJLine(Run.executeTrapExit(console0, log)) } } + object Console { def apply(conf: Inputs): Console = conf.compilers match { diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 3ae24adde..b3fb287e0 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1373,10 +1373,8 @@ object Defaults extends BuildCommon { val sc = (scalacOptions in task).value val ic = (initialCommands in task).value val cc = (cleanupCommands in task).value - JLine.usingTerminal { _ => - (new Console(compiler))(cpFiles, sc, loader, ic, cc)()(s.log).get - println() - } + (new Console(compiler))(cpFiles, sc, loader, ic, cc)()(s.log).get + println() } private[this] def exported(w: PrintWriter, command: String): Seq[String] => Unit = From cbb953279ccac742867c9d76acee3ae5a46e0455 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sat, 31 Mar 2018 20:28:09 +0200 Subject: [PATCH 215/356] Intial implementation of CompositeProject --- main/src/main/scala/sbt/Project.scala | 8 +++++++- main/src/main/scala/sbt/internal/BuildDef.scala | 3 ++- .../sbt/internal/EvaluateConfigurations.scala | 10 +++++----- .../project/sbt-composite-projects/a/A.scala | 1 + .../project/sbt-composite-projects/a/a.sbt | 2 ++ .../project/sbt-composite-projects/b/build.sbt | 2 ++ .../project/sbt-composite-projects/build.sbt | 13 +++++++++++++ .../sbt-composite-projects/changes/basic.sbt | 1 + .../project/sbt-composite-projects/other.sbt | 1 + .../project/sbt-composite-projects/test | 17 +++++++++++++++++ 10 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/a/A.scala create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/build.sbt create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/changes/basic.sbt create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/other.sbt create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/test diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index f292ca0e1..0fe82b457 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -119,7 +119,13 @@ sealed trait ProjectDefinition[PR <: ProjectReference] { if (ts.isEmpty) Nil else s"$label: $ts" :: Nil } -sealed trait Project extends ProjectDefinition[ProjectReference] { +trait CompositeProject { + def componentProjects: Seq[Project] +} + +sealed trait Project extends ProjectDefinition[ProjectReference] with CompositeProject { + def componentProjects: Seq[Project] = this :: Nil + private[sbt] def copy( id: String = id, base: File = base, diff --git a/main/src/main/scala/sbt/internal/BuildDef.scala b/main/src/main/scala/sbt/internal/BuildDef.scala index 96c4641f6..4194613ae 100644 --- a/main/src/main/scala/sbt/internal/BuildDef.scala +++ b/main/src/main/scala/sbt/internal/BuildDef.scala @@ -17,7 +17,8 @@ import sbt.internal.inc.ReflectUtilities trait BuildDef { def projectDefinitions(baseDirectory: File): Seq[Project] = projects - def projects: Seq[Project] = ReflectUtilities.allVals[Project](this).values.toSeq + def projects: Seq[Project] = + ReflectUtilities.allVals[CompositeProject](this).values.toSeq.flatMap(_.componentProjects) // TODO: Should we grab the build core settings here or in a plugin? def settings: Seq[Setting[_]] = Defaults.buildCore def buildLoaders: Seq[BuildLoader.Components] = Nil diff --git a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala index 6b4e77f91..872b8e33f 100644 --- a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -123,10 +123,10 @@ private[sbt] object EvaluateConfigurations { // Tracks all the files we generated from evaluating the sbt file. val allGeneratedFiles = (definitions.generated ++ dslEntries.flatMap(_.generated)) loader => { - val projects = - definitions.values(loader).collect { - case p: Project => resolveBase(file.getParentFile, p) - } + val projects = definitions.values(loader).flatMap { + case p: CompositeProject => p.componentProjects.map(resolveBase(file.getParentFile, _)) + case _ => Nil + } val (settingsRaw, manipulationsRaw) = dslEntries map (_.result apply loader) partition { case DslEntry.ProjectSettings(_) => true @@ -234,7 +234,7 @@ private[sbt] object EvaluateConfigurations { } private[this] def extractedValTypes: Seq[String] = - Seq(classOf[Project], classOf[InputKey[_]], classOf[TaskKey[_]], classOf[SettingKey[_]]).map(_.getName) + Seq(classOf[CompositeProject], classOf[InputKey[_]], classOf[TaskKey[_]], classOf[SettingKey[_]]).map(_.getName) private[this] def evaluateDefinitions(eval: Eval, name: String, imports: Seq[(String, Int)], definitions: Seq[(String, LineRange)], file: Option[File]): compiler.EvalDefinitions = { diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/a/A.scala b/sbt/src/sbt-test/project/sbt-composite-projects/a/A.scala new file mode 100644 index 000000000..528ffce71 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/a/A.scala @@ -0,0 +1 @@ +object A \ No newline at end of file diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt new file mode 100644 index 000000000..67966f6d2 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt @@ -0,0 +1,2 @@ +val aa = taskKey[Unit]("A task in the 'a' project") +aa := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt new file mode 100644 index 000000000..3c7dc5056 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt @@ -0,0 +1,2 @@ +val h = taskKey[Unit]("A task in project 'b'") +h := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt new file mode 100644 index 000000000..cd56ca9e2 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt @@ -0,0 +1,13 @@ +import sbt.internal.AddSettings +import sbt.CompositeProject + +// Based on sbt-file-projects test +val cross = new CompositeProject +{ + val p1 = Project.apply("a", new File("a")) + val p2 = Project.apply("b", new File("b")) + def componentProjects: Seq[Project] = Seq(p1, p2) +} + +val g = taskKey[Unit]("A task in the root project") +g := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/changes/basic.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/changes/basic.sbt new file mode 100644 index 000000000..c128b140e --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/changes/basic.sbt @@ -0,0 +1 @@ +lazy val root = (project in file(".")) diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/other.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/other.sbt new file mode 100644 index 000000000..94e5f2363 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/other.sbt @@ -0,0 +1 @@ +val c = project diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/test b/sbt/src/sbt-test/project/sbt-composite-projects/test new file mode 100644 index 000000000..dbf2e5289 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/test @@ -0,0 +1,17 @@ +> g +-> root/compile +> a/compile +> a/aa +> b/compile +> b/h +> c/compile + +$ copy-file changes/basic.sbt basic.sbt +> reload +> g +> root/compile +> a/compile +> a/aa +> b/compile +> b/h +> c/compile From 25bbc5f488f608e102907cc4f222f26257c74254 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Mon, 2 Apr 2018 20:46:07 +0200 Subject: [PATCH 216/356] Add shadow tests for CompositeProjects --- .../project/sbt-composite-projects/build.sbt | 13 +++++- .../sbt-composite-projects/changes/shadow.sbt | 40 +++++++++++++++++++ .../project/sbt-composite-projects/test | 5 +++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt index cd56ca9e2..7bd373aa4 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt +++ b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt @@ -1,8 +1,10 @@ import sbt.internal.AddSettings import sbt.CompositeProject +lazy val check = taskKey[Unit]("check") + // Based on sbt-file-projects test -val cross = new CompositeProject +lazy val cross = new CompositeProject { val p1 = Project.apply("a", new File("a")) val p2 = Project.apply("b", new File("b")) @@ -11,3 +13,12 @@ val cross = new CompositeProject val g = taskKey[Unit]("A task in the root project") g := println("Hello.") + + +check := { + val verP1 = (version in cross.p1).?.value + assert (verP1 == Some("0.1.0-SNAPSHOT")) + + val verP2 = (version in cross.p2).?.value + assert (verP2 == Some("0.1.0-SNAPSHOT")) +} diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt new file mode 100644 index 000000000..9bf54ab9f --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt @@ -0,0 +1,40 @@ +import sbt.internal.AddSettings +import sbt.CompositeProject + +lazy val check = taskKey[Unit]("check") + +lazy val a = (project in file("a")) + .settings( + version := "0.2.0" + ) + +// Based on sbt-file-projects test +lazy val cross = new CompositeProject +{ + val p1 = Project.apply("a", new File("a")) + val p2 = Project.apply("b", new File("b")) + def componentProjects: Seq[Project] = Seq(p1, p2) +} + +lazy val b = (project in file("b")) + .settings( + version := "0.2.0" + ) + +val g = taskKey[Unit]("A task in the root project") +g := println("Hello.") + + +check := { + val verP1 = (version in cross.p1).?.value + assert (verP1 == Some("0.2.0"))//Some("0.1.0-SNAPSHOT")) + + val verP2 = (version in cross.p2).?.value + assert (verP2 == Some("0.1.0-SNAPSHOT")) + + val verA = (version in a).?.value + assert (verA == Some("0.2.0")) + + val verB = (version in b).?.value + assert (verA == Some("0.2.0")) +} diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/test b/sbt/src/sbt-test/project/sbt-composite-projects/test index dbf2e5289..868ecac69 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/test +++ b/sbt/src/sbt-test/project/sbt-composite-projects/test @@ -15,3 +15,8 @@ $ copy-file changes/basic.sbt basic.sbt > b/compile > b/h > c/compile +> check + +$ copy-file changes/shadow.sbt build.sbt +> reload +> check From 0b801e7598f9cd2076155f7c90c99bf87de6c38e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 3 Apr 2018 14:25:59 +1000 Subject: [PATCH 217/356] Bump Scala version to 2.12.5 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 94a583ea8..d7272d442 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -4,7 +4,7 @@ import sbt.contraband.ContrabandPlugin.autoImport._ object Dependencies { // WARNING: Please Scala update versions in PluginCross.scala too - val scala212 = "2.12.4" + val scala212 = "2.12.5" val baseScalaVersion = scala212 // sbt modules From 70d3484896345698d5d142578e451bdb6dcd388a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 3 Apr 2018 14:27:21 +1000 Subject: [PATCH 218/356] Avoid printing RejectedExectionExeption stack trace after cancellation Before: ``` [warn] /Users/jz/code/zinc/internal/zinc-apiinfo/src/main/scala/xsbt/api/Visit.scala:187:19: parameter value s in method visitString is never used [warn] def visitString(s: String): Unit = () [warn] ^ ^C [warn] Canceling execution... [warn] 10 warnings found [info] Compilation has been cancelled [error] java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.ExecutorCompletionService$QueueingFuture@33ec70dd rejected from java.util.concurrent.ThreadPoolExecutor@4b832de6[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 1410] [error] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) [error] at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) [error] at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) [error] at java.util.concurrent.ExecutorCompletionService.submit(ExecutorCompletionService.java:181) [error] at sbt.CompletionService$.submit(CompletionService.scala:36) [error] at sbt.ConcurrentRestrictions$$anon$4.submitValid(ConcurrentRestrictions.scala:176) [error] at sbt.ConcurrentRestrictions$$anon$4.submit(ConcurrentRestrictions.scala:165) [error] at sbt.Execute.submit(Execute.scala:262) [error] at sbt.Execute.ready(Execute.scala:242) [error] at sbt.Execute.notifyDone(Execute.scala:181) [error] at sbt.Execute.$anonfun$retire$2(Execute.scala:152) [error] at sbt.Execute.$anonfun$retire$2$adapted(Execute.scala:151) [error] at scala.collection.immutable.List.foreach(List.scala:389) [error] at sbt.Execute.retire(Execute.scala:151) [error] at sbt.Execute.$anonfun$work$2(Execute.scala:279) [error] at sbt.Execute$$anon$1.process(Execute.scala:24) [error] at sbt.Execute.next$1(Execute.scala:104) [error] at sbt.Execute.processAll(Execute.scala:107) [error] at sbt.Execute.runKeep(Execute.scala:84) [error] at sbt.EvaluateTask$.liftedTree1$1(EvaluateTask.scala:387) [error] at sbt.EvaluateTask$.run$1(EvaluateTask.scala:386) [error] at sbt.EvaluateTask$.runTask(EvaluateTask.scala:405) [error] at sbt.internal.Aggregation$.$anonfun$timedRun$4(Aggregation.scala:100) [error] at sbt.EvaluateTask$.withStreams(EvaluateTask.scala:331) [error] at sbt.internal.Aggregation$.timedRun(Aggregation.scala:98) [error] at sbt.internal.Aggregation$.runTasks(Aggregation.scala:111) [error] at sbt.internal.Aggregation$.$anonfun$applyTasks$1(Aggregation.scala:68) [error] at sbt.Command$.$anonfun$applyEffect$2(Command.scala:130) [error] at sbt.internal.Aggregation$.$anonfun$evaluatingParser$11(Aggregation.scala:220) [error] at sbt.internal.Act$.$anonfun$actParser0$3(Act.scala:387) [error] at sbt.MainLoop$.processCommand(MainLoop.scala:154) [error] at sbt.MainLoop$.$anonfun$next$2(MainLoop.scala:137) [error] at sbt.State$$anon$1.runCmd$1(State.scala:242) [error] at sbt.State$$anon$1.process(State.scala:248) [error] at sbt.MainLoop$.$anonfun$next$1(MainLoop.scala:137) [error] at sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:16) [error] at sbt.MainLoop$.next(MainLoop.scala:137) [error] at sbt.MainLoop$.run(MainLoop.scala:130) [error] at sbt.MainLoop$.$anonfun$runWithNewLog$1(MainLoop.scala:108) [error] at sbt.io.Using.apply(Using.scala:22) [error] at sbt.MainLoop$.runWithNewLog(MainLoop.scala:102) [error] at sbt.MainLoop$.runAndClearLast(MainLoop.scala:58) [error] at sbt.MainLoop$.runLoggedLoop(MainLoop.scala:43) [error] at sbt.MainLoop$.runLogged(MainLoop.scala:35) [error] at sbt.StandardMain$.runManaged(Main.scala:113) [error] at sbt.xMain.run(Main.scala:76) [error] at xsbt.boot.Launch$$anonfun$run$1.apply(Launch.scala:109) [error] at xsbt.boot.Launch$.withContextLoader(Launch.scala:128) [error] at xsbt.boot.Launch$.run(Launch.scala:109) [error] at xsbt.boot.Launch$$anonfun$apply$1.apply(Launch.scala:35) [error] at xsbt.boot.Launch$.launch(Launch.scala:117) [error] at xsbt.boot.Launch$.apply(Launch.scala:18) [error] at xsbt.boot.Boot$.runImpl(Boot.scala:41) [error] at xsbt.boot.Boot$.main(Boot.scala:17) [error] at xsbt.boot.Boot.main(Boot.scala) [error] java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.ExecutorCompletionService$QueueingFuture@33ec70dd rejected from java.util.concurrent.ThreadPoolExecutor@4b832de6[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 1410] [error] Use 'last' for the full log. ``` After: ``` sbt:zinc Root> ;zincIvyIntegration/cleanClasses;zincIvyIntegration/compile [success] Total time: 0 s, completed 03/04/2018 2:24:33 PM [info] Compiling 5 Scala sources and 1 Java source to /Users/jz/code/zinc/internal/zinc-ivy-integration/target/scala-2.12/classes ... [warn] /Users/jz/code/zinc/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ResourceLoader.scala:14:42: parameter value classLoader in method getPropertiesFor is never used [warn] def getPropertiesFor(resource: String, classLoader: ClassLoader): Properties = { [warn] ^ [warn] /Users/jz/code/zinc/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala:13:17: Unused import [warn] import java.net.URLClassLoader [warn] ^ [warn] /Users/jz/code/zinc/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala:137:26: Unused import [warn] import sbt.io.Path.toURLs [warn] ^ [warn] /Users/jz/code/zinc/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala:94:30: The outer reference in this type test cannot be checked at run time. [warn] private final case class ScalaArtifacts(compiler: File, library: File, others: Vector[File]) [warn] ^ ^C [warn] Canceling execution... [warn] four warnings found [info] Compilation has been cancelled [error] Total time: 1 s, completed 03/04/2018 2:24:35 PM ``` Best served with https://github.com/scala/scala/pull/6479 Fixes #3958 --- main/src/main/scala/sbt/EvaluateTask.scala | 7 ++++++- tasks/src/main/scala/sbt/Execute.scala | 11 +++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 1d17f5cc3..558fe2daf 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -11,6 +11,7 @@ import sbt.internal.{ Load, BuildStructure, TaskTimings, TaskName, GCUtil } import sbt.internal.util.{ Attributed, ErrorHandling, HList, RMap, Signals, Types } import sbt.util.{ Logger, Show } import sbt.librarymanagement.{ Resolver, UpdateReport } +import java.util.concurrent.RejectedExecutionException import scala.concurrent.duration.Duration import java.io.File @@ -387,7 +388,11 @@ object EvaluateTask { val results = x.runKeep(root)(service) storeValuesForPrevious(results, state, streams) applyResults(results, state, root) - } catch { case inc: Incomplete => (state, Inc(inc)) } finally shutdown() + } catch { + case _: RejectedExecutionException => + (state, Inc(Incomplete(None, message = Some("cancelled")))) + case inc: Incomplete => (state, Inc(inc)) + } finally shutdown() val replaced = transformInc(result) logIncResult(replaced, state, streams) (newState, replaced) diff --git a/tasks/src/main/scala/sbt/Execute.scala b/tasks/src/main/scala/sbt/Execute.scala index 267a1383a..e3030cc00 100644 --- a/tasks/src/main/scala/sbt/Execute.scala +++ b/tasks/src/main/scala/sbt/Execute.scala @@ -7,8 +7,10 @@ package sbt +import java.util.concurrent.RejectedExecutionException + import sbt.internal.util.ErrorHandling.wideConvert -import sbt.internal.util.{ DelegatingPMap, PMap, RMap, IDSet, ~> } +import sbt.internal.util.{ DelegatingPMap, IDSet, PMap, RMap, ~> } import sbt.internal.util.Types._ import Execute._ @@ -76,7 +78,12 @@ private[sbt] final class Execute[A[_] <: AnyRef]( "State: " + state.toString + "\n\nResults: " + results + "\n\nCalls: " + callers + "\n\n" def run[T](root: A[T])(implicit strategy: Strategy): Result[T] = - try { runKeep(root)(strategy)(root) } catch { case i: Incomplete => Inc(i) } + try { + runKeep(root)(strategy)(root) + } catch { + case i: Incomplete => Inc(i) + case _: RejectedExecutionException => Inc(Incomplete(None, message = Some("cancelled"))) + } def runKeep[T](root: A[T])(implicit strategy: Strategy): RMap[A, Result] = { assert(state.isEmpty, "Execute already running/ran.") From d48aa310e6442ff7ed2909c7686496822cea5ab4 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 3 Apr 2018 16:02:52 +0100 Subject: [PATCH 219/356] Catch RejectedExecutionException in Execute#runKeep --- main/src/main/scala/sbt/EvaluateTask.scala | 7 +----- tasks/src/main/scala/sbt/Execute.scala | 29 +++++++++++----------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 558fe2daf..1d17f5cc3 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -11,7 +11,6 @@ import sbt.internal.{ Load, BuildStructure, TaskTimings, TaskName, GCUtil } import sbt.internal.util.{ Attributed, ErrorHandling, HList, RMap, Signals, Types } import sbt.util.{ Logger, Show } import sbt.librarymanagement.{ Resolver, UpdateReport } -import java.util.concurrent.RejectedExecutionException import scala.concurrent.duration.Duration import java.io.File @@ -388,11 +387,7 @@ object EvaluateTask { val results = x.runKeep(root)(service) storeValuesForPrevious(results, state, streams) applyResults(results, state, root) - } catch { - case _: RejectedExecutionException => - (state, Inc(Incomplete(None, message = Some("cancelled")))) - case inc: Incomplete => (state, Inc(inc)) - } finally shutdown() + } catch { case inc: Incomplete => (state, Inc(inc)) } finally shutdown() val replaced = transformInc(result) logIncResult(replaced, state, streams) (newState, replaced) diff --git a/tasks/src/main/scala/sbt/Execute.scala b/tasks/src/main/scala/sbt/Execute.scala index e3030cc00..f063080ca 100644 --- a/tasks/src/main/scala/sbt/Execute.scala +++ b/tasks/src/main/scala/sbt/Execute.scala @@ -78,22 +78,21 @@ private[sbt] final class Execute[A[_] <: AnyRef]( "State: " + state.toString + "\n\nResults: " + results + "\n\nCalls: " + callers + "\n\n" def run[T](root: A[T])(implicit strategy: Strategy): Result[T] = - try { - runKeep(root)(strategy)(root) - } catch { - case i: Incomplete => Inc(i) - case _: RejectedExecutionException => Inc(Incomplete(None, message = Some("cancelled"))) - } - def runKeep[T](root: A[T])(implicit strategy: Strategy): RMap[A, Result] = { - assert(state.isEmpty, "Execute already running/ran.") + try { runKeep(root)(strategy)(root) } catch { case i: Incomplete => Inc(i) } - addNew(root) - processAll() - assert(results contains root, "No result for root node.") - val finalResults = triggers.onComplete(results) - progressState = progress.allCompleted(progressState, finalResults) - finalResults - } + def runKeep[T](root: A[T])(implicit strategy: Strategy): RMap[A, Result] = + try { + assert(state.isEmpty, "Execute already running/ran.") + + addNew(root) + processAll() + assert(results contains root, "No result for root node.") + val finalResults = triggers.onComplete(results) + progressState = progress.allCompleted(progressState, finalResults) + finalResults + } catch { + case _: RejectedExecutionException => throw Incomplete(None, message = Some("cancelled")) + } def processAll()(implicit strategy: Strategy): Unit = { @tailrec def next(): Unit = { From b78a0f667be2c0d27d4f313aeacb98a53833d2cd Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 3 Apr 2018 16:05:56 +0100 Subject: [PATCH 220/356] Catch RejectedExecutionException in CompletionService.submit --- .../main/scala/sbt/CompletionService.scala | 7 ++++-- tasks/src/main/scala/sbt/Execute.scala | 23 ++++++++----------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/tasks/src/main/scala/sbt/CompletionService.scala b/tasks/src/main/scala/sbt/CompletionService.scala index f6fc3a957..88f00a5e8 100644 --- a/tasks/src/main/scala/sbt/CompletionService.scala +++ b/tasks/src/main/scala/sbt/CompletionService.scala @@ -17,7 +17,8 @@ import java.util.concurrent.{ CompletionService => JCompletionService, Executor, Executors, - ExecutorCompletionService + ExecutorCompletionService, + RejectedExecutionException, } object CompletionService { @@ -33,7 +34,9 @@ object CompletionService { def take() = completion.take().get() } def submit[T](work: () => T, completion: JCompletionService[T]): () => T = { - val future = completion.submit { new Callable[T] { def call = work() } } + val future = try completion.submit { new Callable[T] { def call = work() } } catch { + case _: RejectedExecutionException => throw Incomplete(None, message = Some("cancelled")) + } () => future.get() } diff --git a/tasks/src/main/scala/sbt/Execute.scala b/tasks/src/main/scala/sbt/Execute.scala index f063080ca..c5d16288e 100644 --- a/tasks/src/main/scala/sbt/Execute.scala +++ b/tasks/src/main/scala/sbt/Execute.scala @@ -7,8 +7,6 @@ package sbt -import java.util.concurrent.RejectedExecutionException - import sbt.internal.util.ErrorHandling.wideConvert import sbt.internal.util.{ DelegatingPMap, IDSet, PMap, RMap, ~> } import sbt.internal.util.Types._ @@ -80,19 +78,16 @@ private[sbt] final class Execute[A[_] <: AnyRef]( def run[T](root: A[T])(implicit strategy: Strategy): Result[T] = try { runKeep(root)(strategy)(root) } catch { case i: Incomplete => Inc(i) } - def runKeep[T](root: A[T])(implicit strategy: Strategy): RMap[A, Result] = - try { - assert(state.isEmpty, "Execute already running/ran.") + def runKeep[T](root: A[T])(implicit strategy: Strategy): RMap[A, Result] = { + assert(state.isEmpty, "Execute already running/ran.") - addNew(root) - processAll() - assert(results contains root, "No result for root node.") - val finalResults = triggers.onComplete(results) - progressState = progress.allCompleted(progressState, finalResults) - finalResults - } catch { - case _: RejectedExecutionException => throw Incomplete(None, message = Some("cancelled")) - } + addNew(root) + processAll() + assert(results contains root, "No result for root node.") + val finalResults = triggers.onComplete(results) + progressState = progress.allCompleted(progressState, finalResults) + finalResults + } def processAll()(implicit strategy: Strategy): Unit = { @tailrec def next(): Unit = { From 120ab651344b45fb1c90cf418a7e0f204a677285 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 3 Apr 2018 16:06:27 +0100 Subject: [PATCH 221/356] Remove unused type param in Scaladoc --- tasks/src/main/scala/sbt/ConcurrentRestrictions.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala b/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala index a827c139a..7313eba62 100644 --- a/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala +++ b/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala @@ -122,7 +122,6 @@ object ConcurrentRestrictions { * Constructs a CompletionService suitable for backing task execution based on the provided restrictions on concurrent task execution. * @return a pair, with _1 being the CompletionService and _2 a function to shutdown the service. * @tparam A the task type - * @tparam G describes a set of tasks * @tparam R the type of data that will be computed by the CompletionService. */ def completionService[A, R](tags: ConcurrentRestrictions[A], From 56ffac22b7c92e13975eb59993c50d6a12dc9cc1 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 4 Apr 2018 01:29:56 -0400 Subject: [PATCH 222/356] document profiling tools --- CONTRIBUTING.md | 154 +++++++++++++++++++++++++++++++++++++ project/flamegraph_svg.png | Bin 0 -> 553287 bytes 2 files changed, 154 insertions(+) create mode 100644 project/flamegraph_svg.png diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7dc91fcdd..1471d269d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -231,6 +231,160 @@ command. To run a single test, such as the test in sbt "scripted project/global-plugin" +Profiling sbt +------------- + +There are several ways to profile sbt. The new hotness in profiling is FlameGraph. +You first collect stack trace samples, and then it is processed into svg graph. +See: + +- [Using FlameGraphs To Illuminate The JVM by Nitsan Wakart](https://www.youtube.com/watch?v=ugRrFdda_JQ) +- [USENIX ATC '17: Visualizing Performance with Flame Graphs](https://www.youtube.com/watch?v=D53T1Ejig1Q) + +### jvm-profiling-tools/async-profiler + +The first one I recommend is async-profiler. This is available for macOS and Linux, +and works fairly well. + +1. Download the installer from https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v1.2 +2. Make symbolic link to `build/` and `profiler.sh` to `$HOME/bin`, assuming you have PATH to `$HOME/bin`: + `ln -s ~/Applications/async-profiler/profiler.sh $HOME/bin/profiler.sh` + `ln -s ~/Applications/async-profiler/build $HOME/bin/build` + +Next, close all Java appliations and anything that may affect the profiling, and run sbt in one terminal: + +``` +$ sbt exit +``` + +In another terminal, run: + +``` +$ jps +92746 sbt-launch.jar +92780 Jps +``` + +This tells you the process ID of sbt. In this case, it's 92746. While it's running, run + +``` +$ profiler.sh -d 60 +Started [cpu] profiling +--- Execution profile --- +Total samples: 31602 +Non-Java: 3239 (10.25%) +GC active: 46 (0.15%) +Unknown (native): 14667 (46.41%) +Not walkable (native): 3 (0.01%) +Unknown (Java): 433 (1.37%) +Not walkable (Java): 8 (0.03%) +Thread exit: 1 (0.00%) +Deopt: 9 (0.03%) + +Frame buffer usage: 55.658% + +Total: 1932000000 (6.11%) samples: 1932 + [ 0] java.lang.ClassLoader$NativeLibrary.load + [ 1] java.lang.ClassLoader.loadLibrary0 + [ 2] java.lang.ClassLoader.loadLibrary + [ 3] java.lang.Runtime.loadLibrary0 + [ 4] java.lang.System.loadLibrary +.... +``` + +This should show a bunch of stacktraces that are useful. +To visualize this as a flamegraph, run: + +``` +$ profiler.sh -d 60 -f /tmp/flamegraph.svg +``` + +This should produce `/tmp/flamegraph.svg` at the end. + +![flamegraph](project/flamegraph_svg.png) + +See https://gist.github.com/eed3si9n/82d43acc95a002876d357bd8ad5f40d5 + +### running sbt with standby + +One of the tricky things you come across while profiling is figuring out the process ID, +while wnating to profile the beginning of the application. + +For this purpose, we've added `sbt.launcher.standby` JVM flag. +In the next version of sbt, you should be able to run: + +``` +$ sbt -J-Dsbt.launcher.standby=20s exit +``` + +This will count down for 20s before doing anything else. + +### jvm-profiling-tools/perf-map-agent + +If you want to try the mixed flamegraph, you can try perf-map-agent. +This uses `dtrace` on macOS and `perf` on Linux. + +You first have to compile https://github.com/jvm-profiling-tools/perf-map-agent. +For macOS, here to how to export `JAVA_HOME` before running `cmake .`: + +``` +$ export JAVA_HOME=$(/usr/libexec/java_home) +$ cmake . +-- The C compiler identification is AppleClang 9.0.0.9000039 +-- The CXX compiler identification is AppleClang 9.0.0.9000039 +... +$ make +``` + +In addition, you have to git clone https://github.com/brendangregg/FlameGraph + +In a fresh termimal, run sbt with `-XX:+PreserveFramePointer` flag: + +``` +$ sbt -J-Dsbt.launcher.standby=20s -J-XX:+PreserveFramePointer exit +``` + +In the terminal that you will run the perf-map: + +``` +$ cd quicktest/ +$ export JAVA_HOME=$(/usr/libexec/java_home) +$ export FLAMEGRAPH_DIR=$HOME/work/FlameGraph +$ jps +94592 Jps +94549 sbt-launch.jar +$ $HOME/work/perf-map-agent/bin/dtrace-java-flames 94549 +dtrace: system integrity protection is on, some features will not be available + +dtrace: description 'profile-99 ' matched 2 probes +Flame graph SVG written to DTRACE_FLAME_OUTPUT='/Users/xxx/work/quicktest/flamegraph-94549.svg'. +``` + +This would produce better flamegraph in theory, but the output looks too messy for `sbt exit` case. +See https://gist.github.com/eed3si9n/b5856ff3d987655513380d1a551aa0df +This might be because it assumes that the operations are already JITed. + +### ktoso/sbt-jmh + +https://github.com/ktoso/sbt-jmh + +Due to JIT warmup etc, benchmarking is difficult. JMH runs the same tests multiple times to +remove these effects and comes closer to measuring the performance of your code. + +There's also an integration with jvm-profiling-tools/async-profiler, apparently. + +### VisualVM + +I'd also mention traditional JVM profiling tool. Since VisualVM is opensource, +I'll mention this one: https://visualvm.github.io/ + +1. First VisualVM. +2. Start sbt from a terminal. +3. You should see `xsbt.boot.Boot` under Local. +4. Open it, and select either sampler or profiler, and hit CPU button at the point when you want to start. + +If you are familiar with YourKit, it also works similarly. + Other notes for maintainers --------------------------- diff --git a/project/flamegraph_svg.png b/project/flamegraph_svg.png new file mode 100644 index 0000000000000000000000000000000000000000..2fc2638c35b504181297f8df5979188f6ef17490 GIT binary patch literal 553287 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%0Dlw=2%f3d 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 z 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
>Ge+!b54BV(Hu$}li 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
x;_YYX(QXorgBVJV{z4UcKQbMeJT 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 z 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
Jyk0x9{q3=vEy)3}`obbs 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%0DZR3$EL&%4V?rJi~7+H*l7G=7?jy;Ho{FW7 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�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 Y45=7mEa7@bjPQ8LOM|5 Date: Wed, 4 Apr 2018 14:26:03 +0100 Subject: [PATCH 223/356] Fix migrate URL Fixes #4062 --- main-settings/src/main/scala/sbt/Structure.scala | 2 +- main-settings/src/main/scala/sbt/std/TaskMacro.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/main-settings/src/main/scala/sbt/Structure.scala b/main-settings/src/main/scala/sbt/Structure.scala index bcedc4171..3c643f00d 100644 --- a/main-settings/src/main/scala/sbt/Structure.scala +++ b/main-settings/src/main/scala/sbt/Structure.scala @@ -565,7 +565,7 @@ object Scoped { /** The sbt 0.10 style DSL was deprecated in 0.13.13, favouring the use of the '.value' macro. * - * See http://www.scala-sbt.org/0.13/docs/Migrating-from-sbt-012x.html for how to migrate. + * See http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html for how to migrate. */ trait TupleSyntax { import Scoped._ diff --git a/main-settings/src/main/scala/sbt/std/TaskMacro.scala b/main-settings/src/main/scala/sbt/std/TaskMacro.scala index efeff7877..e7ffcee63 100644 --- a/main-settings/src/main/scala/sbt/std/TaskMacro.scala +++ b/main-settings/src/main/scala/sbt/std/TaskMacro.scala @@ -89,12 +89,12 @@ object TaskMacro { final val InputTaskCreateDynName = "createDyn" final val InputTaskCreateFreeName = "createFree" final val append1Migration = - "`<+=` operator is removed. Try `lhs += { x.value }`\n or see http://www.scala-sbt.org/1.0/docs/Migrating-from-sbt-012x.html." + "`<+=` operator is removed. Try `lhs += { x.value }`\n or see http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html." final val appendNMigration = - "`<++=` operator is removed. Try `lhs ++= { x.value }`\n or see http://www.scala-sbt.org/1.0/docs/Migrating-from-sbt-012x.html." + "`<++=` operator is removed. Try `lhs ++= { x.value }`\n or see http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html." final val assignMigration = """`<<=` operator is removed. Use `key := { x.value }` or `key ~= (old => { newValue })`. - |See http://www.scala-sbt.org/1.0/docs/Migrating-from-sbt-012x.html""".stripMargin + |See http://www.scala-sbt.org/1.x/docs/Migrating-from-sbt-013x.html""".stripMargin import LinterDSL.{ Empty => EmptyLinter } From 3692db906832bbc7e00c4b05e966a8b9f3fbe7c8 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 27 Mar 2018 01:36:41 +0100 Subject: [PATCH 224/356] Give Scoped a default equals/hashCode implementation --- build.sbt | 4 + .../src/main/scala/sbt/Structure.scala | 19 ++- .../scala/sbt/BuildSettingsInstances.scala | 10 +- .../src/test/scala/sbt/ScopedSpec.scala | 145 ++++++++++++++++++ project/plugins.sbt | 1 + 5 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 main-settings/src/test/scala/sbt/ScopedSpec.scala diff --git a/build.sbt b/build.sbt index c5e115e31..eeb77984a 100644 --- a/build.sbt +++ b/build.sbt @@ -432,6 +432,10 @@ lazy val mainSettingsProj = (project in file("main-settings")) mimaSettings, mimaBinaryIssueFilters ++= Seq( exclude[DirectMissingMethodProblem]("sbt.Scope.display012StyleMasked"), + + // added a method to a sealed trait + exclude[InheritedNewAbstractMethodProblem]("sbt.Scoped.canEqual"), + exclude[InheritedNewAbstractMethodProblem]("sbt.ScopedTaskable.canEqual"), ), ) .configure( diff --git a/main-settings/src/main/scala/sbt/Structure.scala b/main-settings/src/main/scala/sbt/Structure.scala index 36955839d..9bb02bc02 100644 --- a/main-settings/src/main/scala/sbt/Structure.scala +++ b/main-settings/src/main/scala/sbt/Structure.scala @@ -17,7 +17,18 @@ import sbt.Def.{ Initialize, KeyedInitialize, ScopedKey, Setting, setting } import std.TaskExtra.{ task => mktask, _ } /** An abstraction on top of Settings for build configuration and task definition. */ -sealed trait Scoped { def scope: Scope; val key: AttributeKey[_] } +sealed trait Scoped extends Equals { + def scope: Scope + val key: AttributeKey[_] + + override def equals(that: Any) = + (this eq that.asInstanceOf[AnyRef]) || (that match { + case that: Scoped => scope == that.scope && key == that.key && canEqual(that) + case _ => false + }) + + override def hashCode() = (scope, key).## +} /** A common type for SettingKey and TaskKey so that both can be used as inputs to tasks.*/ sealed trait ScopedTaskable[T] extends Scoped { @@ -95,6 +106,8 @@ sealed abstract class SettingKey[T] final def withRank(rank: Int): SettingKey[T] = SettingKey(AttributeKey.copyWithRank(key, rank)) + + def canEqual(that: Any): Boolean = that.isInstanceOf[SettingKey[_]] } /** @@ -163,6 +176,8 @@ sealed abstract class TaskKey[T] final def withRank(rank: Int): TaskKey[T] = TaskKey(AttributeKey.copyWithRank(key, rank)) + + def canEqual(that: Any): Boolean = that.isInstanceOf[TaskKey[_]] } /** @@ -195,6 +210,8 @@ sealed trait InputKey[T] final def withRank(rank: Int): InputKey[T] = InputKey(AttributeKey.copyWithRank(key, rank)) + + def canEqual(that: Any): Boolean = that.isInstanceOf[InputKey[_]] } /** Methods and types related to constructing settings, including keys, scopes, and initializations. */ diff --git a/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala b/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala index 6ca193ccf..3618fb841 100644 --- a/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala +++ b/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala @@ -92,9 +92,13 @@ object BuildSettingsInstances { type Key = K forSome { type K <: Scoped.ScopingSetting[K] with Scoped } - def genInputKey[A: Manifest]: Gen[InputKey[A]] = Gen.identifier map (InputKey[A](_)) - def genSettingKey[A: Manifest]: Gen[SettingKey[A]] = Gen.identifier map (SettingKey[A](_)) - def genTaskKey[A: Manifest]: Gen[TaskKey[A]] = Gen.identifier map (TaskKey[A](_)) + final case class Label(value: String) + val genLabel: Gen[Label] = Gen.identifier map Label + implicit def arbLabel: Arbitrary[Label] = Arbitrary(genLabel) + + def genInputKey[A: Manifest]: Gen[InputKey[A]] = genLabel map (x => InputKey[A](x.value)) + def genSettingKey[A: Manifest]: Gen[SettingKey[A]] = genLabel map (x => SettingKey[A](x.value)) + def genTaskKey[A: Manifest]: Gen[TaskKey[A]] = genLabel map (x => TaskKey[A](x.value)) def withScope[K <: Scoped.ScopingSetting[K]](keyGen: Gen[K]): Arbitrary[K] = Arbitrary { Gen.frequency( diff --git a/main-settings/src/test/scala/sbt/ScopedSpec.scala b/main-settings/src/test/scala/sbt/ScopedSpec.scala new file mode 100644 index 000000000..5992403b0 --- /dev/null +++ b/main-settings/src/test/scala/sbt/ScopedSpec.scala @@ -0,0 +1,145 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt.test + +import org.scalacheck._, Prop._, util.Pretty + +import sbt.internal.util.AttributeKey +import sbt.util.NoJsonWriter +import sbt.{ InputTask, Scope, Task } +import sbt.{ InputKey, Scoped, SettingKey, TaskKey } + +import BuildSettingsInstances._ + +object ScopedSpec extends Properties("Scoped") { + val intManifest = manifest[Int] + val stringManifest = manifest[String] + + implicit val arbManifest: Arbitrary[Manifest[_]] = + Arbitrary(Gen.oneOf(intManifest, stringManifest)) + + property("setting keys are structurally equal") = { + forAll { (label: Label, manifest: Manifest[_], scope: Scope) => + val k1 = settingKey(label, manifest, scope) + val k2 = settingKey(label, manifest, scope) + expectEq(k1, k2) + } + } + + property("task keys are structurally equal") = { + forAll { (label: Label, manifest: Manifest[_], scope: Scope) => + val k1 = taskKey(label, manifest, scope) + val k2 = taskKey(label, manifest, scope) + expectEq(k1, k2) + } + } + + property("input keys are structurally equal") = { + forAll { (label: Label, manifest: Manifest[_], scope: Scope) => + val k1 = inputKey(label, manifest, scope) + val k2 = inputKey(label, manifest, scope) + expectEq(k1, k2) + } + } + + property("different key types are not equal") = { + forAll { (label: Label, manifest: Manifest[_], scope: Scope) => + val settingKey1 = settingKey(label, manifest, scope) + val taskKey1 = taskKey(label, manifest, scope) + val inputKey1 = inputKey(label, manifest, scope) + + all( + expectNe(settingKey1, taskKey1), + expectNe(settingKey1, inputKey1), + expectNe(taskKey1, inputKey1), + ) + } + } + + property("different key types, with the same manifest, are not equal") = { + forAll { (label: Label, scope: Scope) => + val prop1 = { + val manifest1 = manifest[Task[String]] + val attrKey = attributeKey(label, manifest1) + val k1 = SettingKey(attrKey) in scope + val k2 = TaskKey(attrKey) in scope + expectNeSameManifest(k1, k2) + } + + val prop2 = { + val manifest1 = manifest[InputTask[String]] + val attrKey = attributeKey(label, manifest1) + val k1 = SettingKey(attrKey) in scope + val k2 = InputKey(attrKey) in scope + expectNeSameManifest(k1, k2) + } + + all(prop1, prop2) + } + } + + /// + + def settingKey[A](label: Label, manifest: Manifest[A], scope: Scope): SettingKey[A] = { + val noJsonWriter = NoJsonWriter[A]() + SettingKey[A](label.value)(manifest, noJsonWriter) in scope + } + + def taskKey[A](label: Label, manifest: Manifest[A], s: Scope): TaskKey[A] = + TaskKey[A](label.value)(manifest) in s + + def inputKey[A](label: Label, manifest: Manifest[A], scope: Scope): InputKey[A] = + InputKey[A](label.value)(manifest) in scope + + def attributeKey[A](label: Label, manifest: Manifest[A]): AttributeKey[A] = { + val jsonWriter = NoJsonWriter[A]() + AttributeKey[A](label.value)(manifest, jsonWriter) + } + + /// + + def expectEq(k1: Scoped, k2: Scoped): Prop = + ?=(k1, k2) && ?=(k2, k1) map eqLabels(k1, k2) + + def expectNe(k1: Scoped, k2: Scoped): Prop = + !=(k1, k2) && !=(k2, k1) map eqLabels(k1, k2) + + def expectNeSameManifest(k1: Scoped, k2: Scoped) = { + all( + ?=(k1.key.manifest, k2.key.manifest), // sanity check the manifests are the same + expectNe(k1, k2), + ) + } + + def eqLabels(k1: Scoped, k2: Scoped): Prop.Result => Prop.Result = r => { + val eqLabel = k1.key.label == k2.key.label + val eqManifest = k1.key.manifest == k2.key.manifest + val eqScope = k1.scope == k2.scope + r.label(s"label equality: ${k1.key.label} == ${k2.key.label} : $eqLabel") + .label(s"manifest equality: ${k1.key.manifest} == ${k2.key.manifest} : $eqManifest") + .label(s"scope equality: ${k1.scope} == ${k2.scope} : $eqScope") + } + + def ?=[T](x: T, y: T)(implicit pp: T => Pretty): Prop = + if (x == y) proved + else + falsified :| { + val act = Pretty.pretty[T](x, Pretty.Params(0)) + val exp = Pretty.pretty[T](y, Pretty.Params(0)) + s"Expected $act to be equal to $exp" + } + + def !=[T](x: T, y: T)(implicit pp: T => Pretty): Prop = + if (x == y) falsified + else + proved :| { + val act = Pretty.pretty[T](x, Pretty.Params(0)) + val exp = Pretty.pretty[T](y, Pretty.Params(0)) + s"Expected $act to NOT be equal to $exp" + } +} diff --git a/project/plugins.sbt b/project/plugins.sbt index f2509a98e..0352032f5 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,7 @@ scalaVersion := "2.12.4" scalacOptions ++= Seq("-feature", "-language:postfixOps") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.2.0") addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.5") addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.2") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") From 03fc4ac68640cebdfcc8f7ca8f04efa0aea57e22 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Wed, 4 Apr 2018 21:01:05 +0200 Subject: [PATCH 225/356] Ensure unique project Id in composite projects --- main/src/main/scala/sbt/Project.scala | 21 +++++++++++ .../main/scala/sbt/internal/BuildDef.scala | 4 ++- .../sbt/internal/EvaluateConfigurations.scala | 6 ++-- .../project/sbt-composite-projects/a/a.sbt | 2 -- .../sbt-composite-projects/b/build.sbt | 2 -- .../project/sbt-composite-projects/build.sbt | 24 ++++++++----- .../sbt-composite-projects/changes/shadow.sbt | 36 ++++++++----------- .../sbt-composite-projects/js/build.sbt | 2 ++ .../sbt-composite-projects/{a => jvm}/A.scala | 0 .../project/sbt-composite-projects/jvm/a.sbt | 2 ++ .../project/sbt-composite-projects/test | 24 ++++++++----- 11 files changed, 78 insertions(+), 45 deletions(-) delete mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt delete mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/js/build.sbt rename sbt/src/sbt-test/project/sbt-composite-projects/{a => jvm}/A.scala (100%) create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/jvm/a.sbt diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 2571b011e..1342cce77 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -125,6 +125,27 @@ trait CompositeProject { def componentProjects: Seq[Project] } +object CompositeProject { + + /** + * If two projects with the same id appear in `compositeProjects` and + * in `compositeProjects.map(_.componentProjects)`, the one in + * `compositeProjects` wins. + * This is necessary for backward compatibility with the idiom: + * lazy val foo = crossProject + * lazy val fooJVM = foo.jvm.setting + */ + def uniqueId(compositeProjects: Seq[Project]): Seq[Project] = { + for (p <- compositeProjects.flatMap(_.componentProjects)) yield { + compositeProjects.find(_.id == p.id) match { + case Some(overridingProject) => overridingProject + case None => p + } + } + + } +} + sealed trait Project extends ProjectDefinition[ProjectReference] with CompositeProject { def componentProjects: Seq[Project] = this :: Nil diff --git a/main/src/main/scala/sbt/internal/BuildDef.scala b/main/src/main/scala/sbt/internal/BuildDef.scala index 52a9059d9..03c03a40e 100644 --- a/main/src/main/scala/sbt/internal/BuildDef.scala +++ b/main/src/main/scala/sbt/internal/BuildDef.scala @@ -18,7 +18,9 @@ import sbt.internal.inc.ReflectUtilities trait BuildDef { def projectDefinitions(@deprecated("unused", "") baseDirectory: File): Seq[Project] = projects def projects: Seq[Project] = - ReflectUtilities.allVals[CompositeProject](this).values.toSeq.flatMap(_.componentProjects) + CompositeProject.uniqueId( + ReflectUtilities.allVals[CompositeProject](this).values.toSeq.flatMap(_.componentProjects)) + // TODO: Should we grab the build core settings here or in a plugin? def settings: Seq[Setting[_]] = Defaults.buildCore def buildLoaders: Seq[BuildLoader.Components] = Nil diff --git a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala index cfb599a20..c7e6781e9 100644 --- a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -151,11 +151,13 @@ private[sbt] object EvaluateConfigurations { val allGeneratedFiles = (definitions.generated ++ dslEntries.flatMap(_.generated)) loader => { - val projects = - definitions.values(loader).flatMap { + val projects = { + val compositeProjects = definitions.values(loader).flatMap { case p: CompositeProject => p.componentProjects.map(resolveBase(file.getParentFile, _)) case _ => Nil } + CompositeProject.uniqueId(compositeProjects) + } val (settingsRaw, manipulationsRaw) = dslEntries map (_.result apply loader) partition { case DslEntry.ProjectSettings(_) => true diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt deleted file mode 100644 index 67966f6d2..000000000 --- a/sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt +++ /dev/null @@ -1,2 +0,0 @@ -val aa = taskKey[Unit]("A task in the 'a' project") -aa := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt deleted file mode 100644 index 3c7dc5056..000000000 --- a/sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt +++ /dev/null @@ -1,2 +0,0 @@ -val h = taskKey[Unit]("A task in project 'b'") -h := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt index 7bd373aa4..de8f1c134 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt +++ b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt @@ -4,21 +4,29 @@ import sbt.CompositeProject lazy val check = taskKey[Unit]("check") // Based on sbt-file-projects test -lazy val cross = new CompositeProject +lazy val foo = new CompositeProject { - val p1 = Project.apply("a", new File("a")) - val p2 = Project.apply("b", new File("b")) - def componentProjects: Seq[Project] = Seq(p1, p2) + val jvm = Project.apply("jvm", new File("jvm")) + val js = Project.apply("js", new File("js")) + def componentProjects: Seq[Project] = Seq(jvm, js) } +lazy val fooJVM = foo.jvm + +lazy val bar = project + .dependsOn(foo.jvm) + val g = taskKey[Unit]("A task in the root project") g := println("Hello.") check := { - val verP1 = (version in cross.p1).?.value - assert (verP1 == Some("0.1.0-SNAPSHOT")) + val verJvm = (version in foo.jvm).?.value + assert (verJvm == Some("0.1.0-SNAPSHOT")) - val verP2 = (version in cross.p2).?.value - assert (verP2 == Some("0.1.0-SNAPSHOT")) + val verFooJvm = (version in fooJVM).?.value + assert (verFooJvm == Some("0.1.0-SNAPSHOT")) + + val verJs = (version in foo.js).?.value + assert (verJs == Some("0.1.0-SNAPSHOT")) } diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt index 9bf54ab9f..47334ba0f 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt +++ b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt @@ -3,38 +3,30 @@ import sbt.CompositeProject lazy val check = taskKey[Unit]("check") -lazy val a = (project in file("a")) - .settings( - version := "0.2.0" - ) - // Based on sbt-file-projects test -lazy val cross = new CompositeProject +lazy val foo = new CompositeProject { - val p1 = Project.apply("a", new File("a")) - val p2 = Project.apply("b", new File("b")) - def componentProjects: Seq[Project] = Seq(p1, p2) + val jvm = Project.apply("jvm", new File("jvm")) + val js = Project.apply("js", new File("js")) + def componentProjects: Seq[Project] = Seq(jvm, js) } -lazy val b = (project in file("b")) - .settings( - version := "0.2.0" - ) +lazy val fooJVM = foo.jvm.settings(version := "0.2.0") // this one needs to win + +lazy val bar = project + .dependsOn(foo.jvm) val g = taskKey[Unit]("A task in the root project") g := println("Hello.") check := { - val verP1 = (version in cross.p1).?.value - assert (verP1 == Some("0.2.0"))//Some("0.1.0-SNAPSHOT")) + val verJvm = (version in foo.jvm).?.value + assert (verJvm == Some("0.2.0")) - val verP2 = (version in cross.p2).?.value - assert (verP2 == Some("0.1.0-SNAPSHOT")) + val verFooJvm = (version in fooJVM).?.value + assert (verFooJvm == Some("0.2.0")) - val verA = (version in a).?.value - assert (verA == Some("0.2.0")) - - val verB = (version in b).?.value - assert (verA == Some("0.2.0")) + val verJs = (version in foo.js).?.value + assert (verJs == Some("0.1.0-SNAPSHOT")) } diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/js/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/js/build.sbt new file mode 100644 index 000000000..0ddd49e30 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/js/build.sbt @@ -0,0 +1,2 @@ +val h = taskKey[Unit]("A task in project 'js'") +h := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/a/A.scala b/sbt/src/sbt-test/project/sbt-composite-projects/jvm/A.scala similarity index 100% rename from sbt/src/sbt-test/project/sbt-composite-projects/a/A.scala rename to sbt/src/sbt-test/project/sbt-composite-projects/jvm/A.scala diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/jvm/a.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/jvm/a.sbt new file mode 100644 index 000000000..9b73a55bf --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/jvm/a.sbt @@ -0,0 +1,2 @@ +val aa = taskKey[Unit]("A task in the 'jvm' project") +aa := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/test b/sbt/src/sbt-test/project/sbt-composite-projects/test index 868ecac69..5bb98d747 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/test +++ b/sbt/src/sbt-test/project/sbt-composite-projects/test @@ -1,22 +1,30 @@ > g -> root/compile -> a/compile -> a/aa -> b/compile -> b/h +> jvm/compile +> jvm/aa +> js/compile +> js/h > c/compile +> bar/compile $ copy-file changes/basic.sbt basic.sbt > reload > g > root/compile -> a/compile -> a/aa -> b/compile -> b/h +> jvm/compile +> jvm/aa +> js/compile +> js/h > c/compile +> bar/compile > check $ copy-file changes/shadow.sbt build.sbt > reload +> jvm/compile +> jvm/aa +> js/compile +> js/h +> c/compile +> bar/compile > check From 04d6a8b44c6b1385ebe84236af332b8de59d644e Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 5 Apr 2018 02:14:00 -0400 Subject: [PATCH 226/356] perf: optimize hash for build This hot path was discovered by retronym using FlameGraph. This removes intermediate creation of Array. `filesModifiedBytes` in particular was bad as it was going through all `*.class` files, each generating an Array. This replaces that with `fileModifiedHash` that accepts `MessageDigest`. According to the flamegraph, evalCommon footprint reduced from 4.5% to 3.6%. Using `time sbt reload reload reload exit`, the median reduced from 41.450s to 39.467s. --- build.sbt | 4 ++ .../src/main/scala/sbt/compiler/Eval.scala | 51 ++++++++++++++----- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/build.sbt b/build.sbt index d2b19aef3..349c89e62 100644 --- a/build.sbt +++ b/build.sbt @@ -310,6 +310,10 @@ lazy val actionsProj = (project in file("main-actions")) name := "Actions", libraryDependencies += sjsonNewScalaJson.value, mimaSettings, + mimaBinaryIssueFilters ++= Seq( + exclude[DirectMissingMethodProblem]("sbt.compiler.Eval.filesModifiedBytes"), + exclude[DirectMissingMethodProblem]("sbt.compiler.Eval.fileModifiedBytes"), + ) ) .configure( addSbtIO, diff --git a/main-actions/src/main/scala/sbt/compiler/Eval.scala b/main-actions/src/main/scala/sbt/compiler/Eval.scala index a9d97246a..d2afb03b9 100644 --- a/main-actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main-actions/src/main/scala/sbt/compiler/Eval.scala @@ -18,6 +18,7 @@ import Tokens.{ EOF, NEWLINE, NEWLINES, SEMI } import java.io.File import java.nio.ByteBuffer import java.net.URLClassLoader +import java.security.MessageDigest import Eval.{ getModule, getValue, WrapValName } import sbt.io.{ DirectoryFilter, FileFilter, GlobFilter, Hash, IO, Path } @@ -159,12 +160,31 @@ final class Eval(optionsNoncp: Seq[String], // TODO - We also encode the source of the setting into the hash to avoid conflicts where the exact SAME setting // is defined in multiple evaluated instances with a backing. This leads to issues with finding a previous // value on the classpath when compiling. - val hash = Hash.toHex( - Hash(bytes( - stringSeqBytes(content) :: optBytes(backing)(fileExistsBytes) :: stringSeqBytes(options) :: - seqBytes(classpath)(fileModifiedBytes) :: stringSeqBytes(imports.strings.map(_._1)) :: optBytes( - tpeName)(bytes) :: - bytes(ev.extraHash) :: Nil))) + + // This is a hot path. + val digester = MessageDigest.getInstance("SHA") + content foreach { c => + digester.update(bytes(c)) + } + backing foreach { x => + digester.update(fileExistsBytes(x)) + } + options foreach { o => + digester.update(bytes(o)) + } + classpath foreach { f => + fileModifiedHash(f, digester) + } + imports.strings.map(_._1) foreach { x => + digester.update(bytes(x)) + } + tpeName foreach { x => + digester.update(bytes(x)) + } + digester.update(bytes(ev.extraHash)) + val d = digester.digest() + + val hash = Hash.toHex(d) val moduleName = makeModuleName(hash) lazy val unit = { @@ -482,13 +502,18 @@ private[sbt] object Eval { def seqBytes[T](s: Seq[T])(f: T => Array[Byte]): Array[Byte] = bytes(s map f) def bytes(b: Seq[Array[Byte]]): Array[Byte] = bytes(b.length) ++ b.flatten.toArray[Byte] def bytes(b: Boolean): Array[Byte] = Array[Byte](if (b) 1 else 0) - def filesModifiedBytes(fs: Array[File]): Array[Byte] = - if (fs eq null) filesModifiedBytes(Array[File]()) else seqBytes(fs)(fileModifiedBytes) - def fileModifiedBytes(f: File): Array[Byte] = - (if (f.isDirectory) - filesModifiedBytes(f listFiles classDirFilter) - else - bytes(IO.getModifiedTimeOrZero(f))) ++ bytes(f.getAbsolutePath) + + // fileModifiedBytes is a hot method, taking up 0.85% of reload time + // This is a procedural version + def fileModifiedHash(f: File, digester: MessageDigest): Unit = { + if (f.isDirectory) + (f listFiles classDirFilter) foreach { x => + fileModifiedHash(x, digester) + } else digester.update(bytes(IO.getModifiedTimeOrZero(f))) + + digester.update(bytes(f.getAbsolutePath)) + } + def fileExistsBytes(f: File): Array[Byte] = bytes(f.exists) ++ bytes(f.getAbsolutePath) From ad3692b2dfc8a7e2164f4a9b7d578a4723daac8c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 5 Apr 2018 04:22:16 -0400 Subject: [PATCH 227/356] Use NIO Files.getLastModifiedTime for hashing --- main-actions/src/main/scala/sbt/compiler/Eval.scala | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/main-actions/src/main/scala/sbt/compiler/Eval.scala b/main-actions/src/main/scala/sbt/compiler/Eval.scala index d2afb03b9..f9b0ffeee 100644 --- a/main-actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main-actions/src/main/scala/sbt/compiler/Eval.scala @@ -509,11 +509,22 @@ private[sbt] object Eval { if (f.isDirectory) (f listFiles classDirFilter) foreach { x => fileModifiedHash(x, digester) - } else digester.update(bytes(IO.getModifiedTimeOrZero(f))) + } else digester.update(bytes(JavaMilli.getModifiedTimeOrZero(f))) digester.update(bytes(f.getAbsolutePath)) } + // This uses NIO instead of the JNA-based IO.getModifiedTimeOrZero for speed + object JavaMilli { + import java.nio.file.{ Files, NoSuchFileException } + def getModifiedTimeOrZero(f: File): Long = + try { + Files.getLastModifiedTime(f.toPath).toMillis + } catch { + case e: NoSuchFileException => 0L + } + } + def fileExistsBytes(f: File): Array[Byte] = bytes(f.exists) ++ bytes(f.getAbsolutePath) From 4dc76e2b38c7f6f257a638d7a66a7f8838b735c5 Mon Sep 17 00:00:00 2001 From: Jason Steenstra-Pickens Date: Fri, 6 Apr 2018 10:41:31 +1200 Subject: [PATCH 228/356] Add dependencyResolution scoped to updateSbtClassifiers task Fixes #3432 --- main/src/main/scala/sbt/Defaults.scala | 1 + .../update-sbt-classifiers/build.sbt | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index b3fb287e0..c631ff114 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2168,6 +2168,7 @@ object Classpaths { filterImplicit = false, overrideScalaVersion = true).withScalaOrganization(scalaOrganization.value)) }, + dependencyResolution := IvyDependencyResolution(ivyConfiguration.value), updateSbtClassifiers in TaskGlobal := (Def.task { val lm = dependencyResolution.value val s = streams.value diff --git a/sbt/src/sbt-test/dependency-management/update-sbt-classifiers/build.sbt b/sbt/src/sbt-test/dependency-management/update-sbt-classifiers/build.sbt index b4f691e86..36f0b199b 100644 --- a/sbt/src/sbt-test/dependency-management/update-sbt-classifiers/build.sbt +++ b/sbt/src/sbt-test/dependency-management/update-sbt-classifiers/build.sbt @@ -1 +1,9 @@ scalaVersion := "2.11.11" + +ivyConfiguration := { + throw new RuntimeException("updateSbtClassifiers should use updateSbtClassifiers / ivyConfiguration") +} + +dependencyResolution := { + throw new RuntimeException("updateSbtClassifiers should use updateSbtClassifiers / dependencyResolution") +} From 707bf08c4e46faadce3adbbc8ab4514e3ba5bf4b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 5 Apr 2018 20:29:26 -0400 Subject: [PATCH 229/356] Add new closewatch mode --- main-command/src/main/scala/sbt/Watched.scala | 6 +++--- project/Dependencies.scala | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index 1c2396593..cff9c1597 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -134,14 +134,14 @@ object Watched { AttributeKey[Watched]("watched-configuration", "Configures continuous execution.") def createWatchService(): WatchService = { + def closeWatch = new MacOSXWatchService() sys.props.get("sbt.watch.mode") match { case Some("polling") => new PollingWatchService(PollDelay) case Some("nio") => FileSystems.getDefault.newWatchService() - case _ if Properties.isMac => - // WatchService is slow on macOS - use old polling mode - new MacOSXWatchService(PollDelay) + case Some("closewatch") => closeWatch + case _ if Properties.isMac => closeWatch case _ => FileSystems.getDefault.newWatchService() } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 94a583ea8..88d72b833 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,7 +8,7 @@ object Dependencies { val baseScalaVersion = scala212 // sbt modules - private val ioVersion = "1.1.4" + private val ioVersion = "1.1.5" private val utilVersion = "1.1.3" private val lmVersion = "1.1.4" private val zincVersion = "1.1.3" From 8781c16cbb8695a14a11b3372eaca4971e7bf56b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 6 Apr 2018 16:30:33 -0400 Subject: [PATCH 230/356] bump IO and Zinc --- .../src/main/scala/sbt/compiler/Eval.scala | 19 ++++++++----------- project/Dependencies.scala | 4 ++-- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/main-actions/src/main/scala/sbt/compiler/Eval.scala b/main-actions/src/main/scala/sbt/compiler/Eval.scala index f9b0ffeee..56fbce162 100644 --- a/main-actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main-actions/src/main/scala/sbt/compiler/Eval.scala @@ -15,7 +15,7 @@ import ast.parser.Tokens import reporters.{ ConsoleReporter, Reporter } import scala.reflect.internal.util.{ AbstractFileClassLoader, BatchSourceFile } import Tokens.{ EOF, NEWLINE, NEWLINES, SEMI } -import java.io.File +import java.io.{ File, FileNotFoundException } import java.nio.ByteBuffer import java.net.URLClassLoader import java.security.MessageDigest @@ -509,21 +509,18 @@ private[sbt] object Eval { if (f.isDirectory) (f listFiles classDirFilter) foreach { x => fileModifiedHash(x, digester) - } else digester.update(bytes(JavaMilli.getModifiedTimeOrZero(f))) + } else digester.update(bytes(getModifiedTimeOrZero(f))) digester.update(bytes(f.getAbsolutePath)) } // This uses NIO instead of the JNA-based IO.getModifiedTimeOrZero for speed - object JavaMilli { - import java.nio.file.{ Files, NoSuchFileException } - def getModifiedTimeOrZero(f: File): Long = - try { - Files.getLastModifiedTime(f.toPath).toMillis - } catch { - case e: NoSuchFileException => 0L - } - } + def getModifiedTimeOrZero(f: File): Long = + try { + sbt.io.JavaMilli.getModifiedTime(f.getPath) + } catch { + case _: FileNotFoundException => 0L + } def fileExistsBytes(f: File): Array[Byte] = bytes(f.exists) ++ diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 88d72b833..50d38e300 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,10 +8,10 @@ object Dependencies { val baseScalaVersion = scala212 // sbt modules - private val ioVersion = "1.1.5" + private val ioVersion = "1.1.6" private val utilVersion = "1.1.3" private val lmVersion = "1.1.4" - private val zincVersion = "1.1.3" + private val zincVersion = "1.1.4" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion From 32385c8bb7985fb3bc545c59f6c8bc1292f0e283 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 6 Apr 2018 18:36:10 -0400 Subject: [PATCH 231/356] launcher 1.0.4 --- project/Dependencies.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 50d38e300..6ee63a29c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -26,8 +26,9 @@ object Dependencies { private val libraryManagementCore = "org.scala-sbt" %% "librarymanagement-core" % lmVersion private val libraryManagementIvy = "org.scala-sbt" %% "librarymanagement-ivy" % lmVersion - val launcherInterface = "org.scala-sbt" % "launcher-interface" % "1.0.3" - val rawLauncher = "org.scala-sbt" % "launcher" % "1.0.3" + val launcherVersion = "1.0.4" + val launcherInterface = "org.scala-sbt" % "launcher-interface" % launcherVersion + val rawLauncher = "org.scala-sbt" % "launcher" % launcherVersion val testInterface = "org.scala-sbt" % "test-interface" % "1.0" val ipcSocket = "org.scala-sbt.ipcsocket" % "ipcsocket" % "1.0.0" From 68c005e4b58a968a3c0ead2b342076d3e8054684 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sat, 7 Apr 2018 01:04:45 +0200 Subject: [PATCH 232/356] Ensure precedence of top level Projects over ComponentProjects --- main/src/main/scala/sbt/Project.scala | 28 +++++++++----- .../main/scala/sbt/internal/BuildDef.scala | 9 +++-- .../sbt/internal/EvaluateConfigurations.scala | 15 ++++++-- notes/1.2.0/introduce-CompositeProject.md | 9 +++++ .../project/sbt-composite-projects/build.sbt | 14 ++++--- .../sbt-composite-projects/changes/shadow.sbt | 10 +++-- .../changes/shadowLazy.sbt | 37 +++++++++++++++++++ .../project/sbt-composite-projects/test | 10 +++++ 8 files changed, 107 insertions(+), 25 deletions(-) create mode 100644 notes/1.2.0/introduce-CompositeProject.md create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/changes/shadowLazy.sbt diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 1342cce77..789cb709f 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -125,25 +125,35 @@ trait CompositeProject { def componentProjects: Seq[Project] } -object CompositeProject { +private[sbt] object CompositeProject { /** - * If two projects with the same id appear in `compositeProjects` and - * in `compositeProjects.map(_.componentProjects)`, the one in - * `compositeProjects` wins. - * This is necessary for backward compatibility with the idiom: + * Expand user defined `projects` with the component projects of `compositeProjects`. + * + * If two projects with the same id appear in the user defined `projects` and + * in `compositeProjects.componentProjects`, the one in `projects` wins. + * This is necessary for backward compatibility with the idioms: + * {{{ * lazy val foo = crossProject - * lazy val fooJVM = foo.jvm.setting + * lazy val fooJS = foo.js.settings(...) + * lazy val fooJVM = foo.jvm.settings(...) + * }}} + * and the rarer: + * {{{ + * lazy val fooJS = foo.js.settings(...) + * lazy val foo = crossProject + * lazy val fooJVM = foo.jvm.settings(...) + * }}} */ - def uniqueId(compositeProjects: Seq[Project]): Seq[Project] = { + def expand(projects: Seq[Project], compositeProjects: Seq[CompositeProject]): Seq[Project] = { for (p <- compositeProjects.flatMap(_.componentProjects)) yield { - compositeProjects.find(_.id == p.id) match { + projects.find(_.id == p.id) match { case Some(overridingProject) => overridingProject case None => p } } - } + } sealed trait Project extends ProjectDefinition[ProjectReference] with CompositeProject { diff --git a/main/src/main/scala/sbt/internal/BuildDef.scala b/main/src/main/scala/sbt/internal/BuildDef.scala index 03c03a40e..5a916acb5 100644 --- a/main/src/main/scala/sbt/internal/BuildDef.scala +++ b/main/src/main/scala/sbt/internal/BuildDef.scala @@ -17,10 +17,11 @@ import sbt.internal.inc.ReflectUtilities trait BuildDef { def projectDefinitions(@deprecated("unused", "") baseDirectory: File): Seq[Project] = projects - def projects: Seq[Project] = - CompositeProject.uniqueId( - ReflectUtilities.allVals[CompositeProject](this).values.toSeq.flatMap(_.componentProjects)) - + def projects: Seq[Project] = { + val projects = ReflectUtilities.allVals[Project](this).values.toSeq + val compositeProjects = ReflectUtilities.allVals[CompositeProject](this).values.toSeq + CompositeProject.expand(projects, compositeProjects) + } // TODO: Should we grab the build core settings here or in a plugin? def settings: Seq[Setting[_]] = Defaults.buildCore def buildLoaders: Seq[BuildLoader.Components] = Nil diff --git a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala index c7e6781e9..241b8c6b1 100644 --- a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -152,11 +152,18 @@ private[sbt] object EvaluateConfigurations { loader => { val projects = { - val compositeProjects = definitions.values(loader).flatMap { - case p: CompositeProject => p.componentProjects.map(resolveBase(file.getParentFile, _)) - case _ => Nil + + val projects = definitions.values(loader).collect { + case p: Project => p } - CompositeProject.uniqueId(compositeProjects) + + val compositeProjects = definitions.values(loader).collect { + case p: CompositeProject => p + } + + CompositeProject + .expand(projects, compositeProjects) + .map(resolveBase(file.getParentFile, _)) } val (settingsRaw, manipulationsRaw) = dslEntries map (_.result apply loader) partition { diff --git a/notes/1.2.0/introduce-CompositeProject.md b/notes/1.2.0/introduce-CompositeProject.md new file mode 100644 index 000000000..759d75a71 --- /dev/null +++ b/notes/1.2.0/introduce-CompositeProject.md @@ -0,0 +1,9 @@ + +[#3042]: https://github.com/sbt/sbt/issues/3042 +[#4056]: https://github.com/sbt/sbt/pull/4056 + +### Introduce CompositeProject + +### Improvements + +- Support for: `lazy val foo = someCompositeProject` (e.g.`CrossProject`) [#3042][]/[#4056][] diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt index de8f1c134..1537b2610 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt +++ b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt @@ -6,11 +6,12 @@ lazy val check = taskKey[Unit]("check") // Based on sbt-file-projects test lazy val foo = new CompositeProject { - val jvm = Project.apply("jvm", new File("jvm")) - val js = Project.apply("js", new File("js")) + val jvm = Project.apply("jvm", new File("jvm")).settings(version := "0.1.0") // this one needs to win + val js = Project.apply("js", new File("js")).settings(version := "0.1.0") // this one needs to win def componentProjects: Seq[Project] = Seq(jvm, js) } +lazy val fooJS = foo.js lazy val fooJVM = foo.jvm lazy val bar = project @@ -22,11 +23,14 @@ g := println("Hello.") check := { val verJvm = (version in foo.jvm).?.value - assert (verJvm == Some("0.1.0-SNAPSHOT")) + assert (verJvm == Some("0.1.0")) val verFooJvm = (version in fooJVM).?.value - assert (verFooJvm == Some("0.1.0-SNAPSHOT")) + assert (verFooJvm == Some("0.1.0")) val verJs = (version in foo.js).?.value - assert (verJs == Some("0.1.0-SNAPSHOT")) + assert (verJs == Some("0.1.0")) + + val verFooJs = (version in fooJS).?.value + assert (verFooJs == Some("0.1.0")) } diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt index 47334ba0f..0713e7f4b 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt +++ b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt @@ -6,11 +6,12 @@ lazy val check = taskKey[Unit]("check") // Based on sbt-file-projects test lazy val foo = new CompositeProject { - val jvm = Project.apply("jvm", new File("jvm")) - val js = Project.apply("js", new File("js")) + val jvm = Project.apply("jvm", new File("jvm")).settings(version := "0.1.0") + val js = Project.apply("js", new File("js")).settings(version := "0.1.0") // this one needs to win def componentProjects: Seq[Project] = Seq(jvm, js) } +lazy val fooJS = foo.js lazy val fooJVM = foo.jvm.settings(version := "0.2.0") // this one needs to win lazy val bar = project @@ -28,5 +29,8 @@ check := { assert (verFooJvm == Some("0.2.0")) val verJs = (version in foo.js).?.value - assert (verJs == Some("0.1.0-SNAPSHOT")) + assert (verJs == Some("0.1.0")) + + val verFooJs = (version in fooJS).?.value + assert (verFooJs == Some("0.1.0")) } diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadowLazy.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadowLazy.sbt new file mode 100644 index 000000000..492fbd621 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadowLazy.sbt @@ -0,0 +1,37 @@ +import sbt.internal.AddSettings +import sbt.CompositeProject + +lazy val check = taskKey[Unit]("check") + +lazy val fooJS = foo.js.settings(version := "0.2.1") // this one needs to win + +// Based on sbt-file-projects test +lazy val foo = new CompositeProject +{ + val jvm = Project.apply("jvm", new File("jvm")).settings(version := "0.1.0") + val js = Project.apply("js", new File("js")).settings(version := "0.1.0") + def componentProjects: Seq[Project] = Seq(jvm, js) +} + +lazy val fooJVM = foo.jvm.settings(version := "0.2.0") // this one needs to win + +lazy val bar = project + .dependsOn(foo.jvm) + +val g = taskKey[Unit]("A task in the root project") +g := println("Hello.") + + +check := { + val verJvm = (version in foo.jvm).?.value + assert (verJvm == Some("0.2.0")) + + val verFooJvm = (version in fooJVM).?.value + assert (verFooJvm == Some("0.2.0")) + + val verJs = (version in foo.js).?.value + assert (verJs == Some("0.2.1")) + + val verFooJs = (version in fooJS).?.value + assert (verFooJs == Some("0.2.1")) +} diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/test b/sbt/src/sbt-test/project/sbt-composite-projects/test index 5bb98d747..34bc8097d 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/test +++ b/sbt/src/sbt-test/project/sbt-composite-projects/test @@ -28,3 +28,13 @@ $ copy-file changes/shadow.sbt build.sbt > c/compile > bar/compile > check + +$ copy-file changes/shadowLazy.sbt build.sbt +> reload +> jvm/compile +> jvm/aa +> js/compile +> js/h +> c/compile +> bar/compile +> check \ No newline at end of file From 6cce4f6fd980d308b7ada79721c3c5d85ca86cb5 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sat, 7 Apr 2018 01:26:40 +0200 Subject: [PATCH 233/356] Remove duplicate Projects --- main/src/main/scala/sbt/Project.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 789cb709f..5f867dd94 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -152,7 +152,7 @@ private[sbt] object CompositeProject { case None => p } } - } + }.distinct } From b0ad1a44c06a36c4697a922b6135c73351f5d9ee Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sat, 7 Apr 2018 15:56:31 +0200 Subject: [PATCH 234/356] Remove projects parameter from CompositeProject.expand --- main/src/main/scala/sbt/Project.scala | 15 ++++++++------- main/src/main/scala/sbt/internal/BuildDef.scala | 7 ++----- .../sbt/internal/EvaluateConfigurations.scala | 10 +--------- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 5f867dd94..bac52a877 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -128,10 +128,10 @@ trait CompositeProject { private[sbt] object CompositeProject { /** - * Expand user defined `projects` with the component projects of `compositeProjects`. + * Expand user defined projects with the component projects of `compositeProjects`. * - * If two projects with the same id appear in the user defined `projects` and - * in `compositeProjects.componentProjects`, the one in `projects` wins. + * If two projects with the same id appear in the user defined projects and + * in `compositeProjects.componentProjects`, the user defined project wins. * This is necessary for backward compatibility with the idioms: * {{{ * lazy val foo = crossProject @@ -145,11 +145,12 @@ private[sbt] object CompositeProject { * lazy val fooJVM = foo.jvm.settings(...) * }}} */ - def expand(projects: Seq[Project], compositeProjects: Seq[CompositeProject]): Seq[Project] = { + def expand(compositeProjects: Seq[CompositeProject]): Seq[Project] = { + val userProjects = compositeProjects.collect { case p: Project => p } for (p <- compositeProjects.flatMap(_.componentProjects)) yield { - projects.find(_.id == p.id) match { - case Some(overridingProject) => overridingProject - case None => p + userProjects.find(_.id == p.id) match { + case Some(userProject) => userProject + case None => p } } }.distinct diff --git a/main/src/main/scala/sbt/internal/BuildDef.scala b/main/src/main/scala/sbt/internal/BuildDef.scala index 5a916acb5..d4a435ab5 100644 --- a/main/src/main/scala/sbt/internal/BuildDef.scala +++ b/main/src/main/scala/sbt/internal/BuildDef.scala @@ -17,11 +17,8 @@ import sbt.internal.inc.ReflectUtilities trait BuildDef { def projectDefinitions(@deprecated("unused", "") baseDirectory: File): Seq[Project] = projects - def projects: Seq[Project] = { - val projects = ReflectUtilities.allVals[Project](this).values.toSeq - val compositeProjects = ReflectUtilities.allVals[CompositeProject](this).values.toSeq - CompositeProject.expand(projects, compositeProjects) - } + def projects: Seq[Project] = + CompositeProject.expand(ReflectUtilities.allVals[CompositeProject](this).values.toSeq) // TODO: Should we grab the build core settings here or in a plugin? def settings: Seq[Setting[_]] = Defaults.buildCore def buildLoaders: Seq[BuildLoader.Components] = Nil diff --git a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala index 241b8c6b1..26e5348ff 100644 --- a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -152,18 +152,10 @@ private[sbt] object EvaluateConfigurations { loader => { val projects = { - - val projects = definitions.values(loader).collect { - case p: Project => p - } - val compositeProjects = definitions.values(loader).collect { case p: CompositeProject => p } - - CompositeProject - .expand(projects, compositeProjects) - .map(resolveBase(file.getParentFile, _)) + CompositeProject.expand(compositeProjects).map(resolveBase(file.getParentFile, _)) } val (settingsRaw, manipulationsRaw) = dslEntries map (_.result apply loader) partition { From 1abb0a3641e68b635f5f50229c21cfb96eff85d1 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 8 Apr 2018 14:31:48 +1000 Subject: [PATCH 235/356] Upgrade to latest sbt-houserules --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index c91a3bc6f..68b01d14b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ scalaVersion := "2.12.4" scalacOptions ++= Seq("-feature", "-language:postfixOps") -addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.5") +addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.6") addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.4.0") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.8.0") From 3e201cee4d830353f9a076e863d22cdc623f9ac8 Mon Sep 17 00:00:00 2001 From: Nafer Sanabria Date: Sun, 8 Apr 2018 10:51:54 -0500 Subject: [PATCH 236/356] Add lastGrep command --- main/src/main/scala/sbt/Main.scala | 12 +++++++++--- .../main/scala/sbt/internal/CommandStrings.scala | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 4f4ef57d6..c19d44c40 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -40,7 +40,6 @@ import sbt.internal.util.{ Types } import sbt.util.{ Level, Logger, Show } - import sbt.internal.util.complete.{ DefaultParsers, Parser } import sbt.internal.inc.ScalaInstance import sbt.compiler.EvalImports @@ -52,7 +51,6 @@ import xsbti.compile.CompilerCache import scala.annotation.tailrec import sbt.io.IO import sbt.io.syntax._ - import java.io.{ File, IOException } import java.net.URI import java.util.{ Locale, Properties } @@ -198,6 +196,7 @@ object BuiltinCommands { startServer, eval, last, + oldLastGrep, lastGrep, export, boot, @@ -452,8 +451,15 @@ object BuiltinCommands { s } + @deprecated("Use `lastGrep` instead.", "1.2.0") + def oldLastGrep: Command = + lastGrepCommand(OldLastGrepCommand, oldLastGrepBrief, oldLastGrepDetailed) + def lastGrep: Command = - Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) { + lastGrepCommand(LastGrepCommand, lastGrepBrief, lastGrepDetailed) + + private def lastGrepCommand(name: String, briefHelp: (String, String), detail: String): Command = + Command(name, briefHelp, detail)(lastGrepParser) { case (s, (pattern, Some(sks))) => val (str, _, display) = extractLast(s) Output.lastGrep(sks, str.streams(s), pattern, printLast)(display) diff --git a/main/src/main/scala/sbt/internal/CommandStrings.scala b/main/src/main/scala/sbt/internal/CommandStrings.scala index dc070a468..278985b64 100644 --- a/main/src/main/scala/sbt/internal/CommandStrings.scala +++ b/main/src/main/scala/sbt/internal/CommandStrings.scala @@ -57,10 +57,23 @@ $ShowCommand def pluginsDetailed = pluginsBrief // TODO: expand val LastCommand = "last" - val LastGrepCommand = "last-grep" + val OldLastGrepCommand = "last-grep" + val LastGrepCommand = "lastGrep" val ExportCommand = "export" val ExportStream = "export" + val oldLastGrepBrief = + (OldLastGrepCommand, "Shows lines from the last output for 'key' that match 'pattern'.") + val oldLastGrepDetailed = + s"""$OldLastGrepCommand + Displays lines from the logging of previous commands that match `pattern`. + +$OldLastGrepCommand [key] + Displays lines from logging associated with `key` that match `pattern`. The key typically refers to a task (for example, test:compile). The logging that is displayed is restricted to the logging for that particular task. + + is a regular expression interpreted by java.util.Pattern. Matching text is highlighted (when highlighting is supported and enabled). + See also '$LastCommand'.""" + val lastGrepBrief = (LastGrepCommand, "Shows lines from the last output for 'key' that match 'pattern'.") val lastGrepDetailed = From 8ed796fb2570956a2edfa9cc36e4c80ddb05438b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 9 Apr 2018 00:11:17 -0400 Subject: [PATCH 237/356] Re-fixing console and JLine Fixes #3482 take 2 I thought I tested #4054 using a local build, but when I ran 1.1.3, `console` did not display anything that I typed. Switching to `usingTerminal` which calls `terminal.restore` similar to what I had in 1.1.1 fixes `console`. --- main-actions/src/main/scala/sbt/Console.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main-actions/src/main/scala/sbt/Console.scala b/main-actions/src/main/scala/sbt/Console.scala index d94484030..61bfb9e47 100644 --- a/main-actions/src/main/scala/sbt/Console.scala +++ b/main-actions/src/main/scala/sbt/Console.scala @@ -41,7 +41,9 @@ final class Console(compiler: AnalyzingCompiler) { implicit log: Logger): Try[Unit] = { def console0() = compiler.console(classpath, options, initialCommands, cleanupCommands, log)(loader, bindings) - JLine.withJLine(Run.executeTrapExit(console0, log)) + JLine.usingTerminal { _ => + Run.executeTrapExit(console0, log) + } } } From b9eb7764f3d28c7b30bbf3ceb641afde9f583e3f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 9 Apr 2018 12:37:42 -0400 Subject: [PATCH 238/356] Zinc 1.1.5 --- build.sbt | 2 +- project/Dependencies.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 349c89e62..a45918803 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def buildLevelSettings: Seq[Setting[_]] = inThisBuild( Seq( organization := "org.scala-sbt", - version := "1.1.3-SNAPSHOT", + version := "1.1.4-SNAPSHOT", description := "sbt is an interactive build tool", bintrayOrganization := Some("sbt"), bintrayRepository := { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 6ee63a29c..5705334fa 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -11,7 +11,7 @@ object Dependencies { private val ioVersion = "1.1.6" private val utilVersion = "1.1.3" private val lmVersion = "1.1.4" - private val zincVersion = "1.1.4" + private val zincVersion = "1.1.5" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion From e5a37cad429bcfc7d74a8820c13be3381cc5f2fe Mon Sep 17 00:00:00 2001 From: Heikki Vesalainen Date: Wed, 11 Apr 2018 00:10:56 +0300 Subject: [PATCH 239/356] Update to Jline 2.14.6 This version of Jline fixes three things for Emacs users: - ANSI colors are now enabled for Emacs. - Terminal echo is now disabled for Emacs. - History is enabled for all dump terminals. --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 5705334fa..56706d639 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -98,7 +98,7 @@ object Dependencies { "com.eed3si9n" %% "sjson-new-scalajson" % contrabandSjsonNewVersion.value } - val jline = "jline" % "jline" % "2.14.4" + val jline = "jline" % "jline" % "2.14.6" val scalatest = "org.scalatest" %% "scalatest" % "3.0.4" val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.13.4" val specs2 = "org.specs2" %% "specs2-junit" % "4.0.1" From 190dc23f7063787e145b1cf1c061259829681504 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 11 Apr 2018 01:31:38 -0400 Subject: [PATCH 240/356] Fixes linter that detects missing .value Fixes #4079 #3216 introduced a linter that checks against missing `.value`, but the tree only checked for `Ident`. This doesn't work because in reality the symbols of build.sbt are transformed to `$somehash.npmInstallTask` where `somehash` is the wrapper object we create. Similarly for the built-in keys, they are presented as `sbt.Keys.compile`. With this change unused task will fail to load the build with the following message: ``` /sbt-4079/build.sbt:26: error: The key `compile` is not being invoked inside the task definition. Problem: Keys missing `.value` are not initialized and their dependency is not registered. Solution: Replace `compile` by `compile.value` or remove it if unused. compile ^ /sbt-4079/build.sbt:27: error: The key `npmInstallTask` is not being invoked inside the task definition. Problem: Keys missing `.value` are not initialized and their dependency is not registered. Solution: Replace `npmInstallTask` by `npmInstallTask.value` or remove it if unused. npmInstallTask ^ ``` --- .../main/scala/sbt/std/TaskLinterDSL.scala | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala b/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala index a328cee70..1c47216fa 100644 --- a/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala +++ b/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala @@ -27,6 +27,7 @@ abstract class BaseTaskLinterDSL extends LinterDSL { private val taskKeyType = typeOf[sbt.TaskKey[_]] private val settingKeyType = typeOf[sbt.SettingKey[_]] private val inputKeyType = typeOf[sbt.InputKey[_]] + private val initializeType = typeOf[sbt.Def.Initialize[_]] private val uncheckedWrappers = MutableSet.empty[Tree] var insideIf: Boolean = false var insideAnon: Boolean = false @@ -57,7 +58,7 @@ abstract class BaseTaskLinterDSL extends LinterDSL { } @inline def isKey(tpe: Type): Boolean = - tpe <:< taskKeyType || tpe <:< settingKeyType || tpe <:< inputKeyType + tpe <:< initializeType || tpe <:< taskKeyType || tpe <:< settingKeyType || tpe <:< inputKeyType def detectAndErrorOnKeyMissingValue(i: Ident): Unit = { if (isKey(i.tpe)) { @@ -66,6 +67,13 @@ abstract class BaseTaskLinterDSL extends LinterDSL { } else () } + def detectAndErrorOnKeyMissingValue(s: Select): Unit = { + if (isKey(s.tpe)) { + val keyName = s.name.decodedName.toString + ctx.error(s.pos, TaskLinterDSLFeedback.missingValueForKey(keyName)) + } else () + } + override def traverse(tree: ctx.universe.Tree): Unit = { tree match { case ap @ Apply(TypeApply(Select(_, nme), tpe :: Nil), qual :: Nil) => @@ -118,11 +126,13 @@ abstract class BaseTaskLinterDSL extends LinterDSL { // TODO: Consider using unused names analysis to be able to report on more cases case ValDef(_, valName, _, rhs) if valName == termNames.WILDCARD => rhs match { - case i: Ident => detectAndErrorOnKeyMissingValue(i) - case _ => () + case i: Ident => detectAndErrorOnKeyMissingValue(i) + case s: Select => detectAndErrorOnKeyMissingValue(s) + case _ => () } - case i: Ident => detectAndErrorOnKeyMissingValue(i) - case _ => () + case i: Ident => detectAndErrorOnKeyMissingValue(i) + case s: Select => detectAndErrorOnKeyMissingValue(s) + case t => () } } traverseTrees(stmts) From a1e3146c0865f085b5ac65966561efc02c16e922 Mon Sep 17 00:00:00 2001 From: Alexey Alekhin Date: Wed, 11 Apr 2018 18:37:16 +0200 Subject: [PATCH 241/356] Don't use initialize request id for the internal collectAnalyses call --- .../main/scala/sbt/internal/server/LanguageServerProtocol.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index 77314890a..af5b37337 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -64,7 +64,7 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { else throw LangServerError(ErrorCodes.InvalidRequest, "invalid token") } else () setInitialized(true) - append(Exec(s"collectAnalyses", Some(request.id), Some(CommandSource(name)))) + append(Exec(s"collectAnalyses", None, Some(CommandSource(name)))) langRespond(InitializeResult(serverCapabilities), Option(request.id)) case "textDocument/definition" => import scala.concurrent.ExecutionContext.Implicits.global From 2bb717dbf97f94bdc6d7259654757dea2a278b35 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 11 Apr 2018 18:51:53 -0700 Subject: [PATCH 242/356] Exclude directories instead of including files The existing filter caused SourceModificationWatch.watch to ignore deleted files because !file.exists implies !file.isFile. The intention of the filter was to exclude directories that had a name ending in ".scala". --- main/src/main/scala/sbt/Defaults.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index c631ff114..82af2a9e4 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -162,8 +162,7 @@ object Defaults extends BuildCommon { artifactClassifier in packageSrc :== Some(SourceClassifier), artifactClassifier in packageDoc :== Some(DocClassifier), includeFilter :== NothingFilter, - includeFilter in unmanagedSources :== ("*.java" | "*.scala") && new SimpleFileFilter( - _.isFile), + includeFilter in unmanagedSources :== ("*.java" | "*.scala") -- DirectoryFilter, includeFilter in unmanagedJars :== "*.jar" | "*.so" | "*.dll" | "*.jnilib" | "*.zip", includeFilter in unmanagedResources :== AllPassFilter, bgList := { bgJobService.value.jobs }, From 1ec07c1867d2b7eb01503f68e0fc43d84f445911 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 26 Mar 2018 10:37:25 -0400 Subject: [PATCH 243/356] Recover sbtOn --- build.sbt | 4 ++++ sbt/src/test/scala/sbt/RunFromSourceMain.scala | 17 +++++++++-------- sbt/src/test/scala/testpkg/ServerSpec.scala | 6 +++++- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/build.sbt b/build.sbt index 41723a157..01f18a2a8 100644 --- a/build.sbt +++ b/build.sbt @@ -497,6 +497,7 @@ lazy val sbtProj = (project in file("sbt")) normalizedName := "sbt", crossScalaVersions := Seq(baseScalaVersion), crossPaths := false, + javaOptions ++= Seq("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"), mimaSettings, mimaBinaryIssueFilters ++= sbtIgnoredProblems, BuildInfoPlugin.buildInfoDefaultSettings, @@ -508,6 +509,9 @@ lazy val sbtProj = (project in file("sbt")) classDirectory in Compile, classDirectory in Test, ), + Test / run / connectInput := true, + Test / run / outputStrategy := Some(StdoutOutput), + Test / run / fork := true, ) .configure(addSbtCompilerBridge) diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala index 2f4e81a51..c0bd9cfc1 100644 --- a/sbt/src/test/scala/sbt/RunFromSourceMain.scala +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -7,7 +7,7 @@ package sbt -import scala.concurrent.Future +import scala.util.Try import sbt.util.LogExchange import scala.annotation.tailrec import buildinfo.TestBuildInfo @@ -17,21 +17,22 @@ object RunFromSourceMain { private val sbtVersion = "1.0.3" // "dev" private val scalaVersion = "2.12.4" - def fork(workingDirectory: File): Future[Unit] = { + def fork(workingDirectory: File): Try[Unit] = { val fo = ForkOptions() - .withWorkingDirectory(workingDirectory) .withOutputStrategy(OutputStrategy.StdoutOutput) + fork(fo, workingDirectory) + } + + def fork(fo0: ForkOptions, workingDirectory: File): Try[Unit] = { + val fo = fo0 + .withWorkingDirectory(workingDirectory) implicit val runner = new ForkRun(fo) val cp = { TestBuildInfo.test_classDirectory +: TestBuildInfo.fullClasspath } val options = Vector(workingDirectory.toString) val log = LogExchange.logger("RunFromSourceMain.fork", None, None) - import scala.concurrent.ExecutionContext.Implicits.global - Future { - Run.run("sbt.RunFromSourceMain", cp, options, log) - () - } + Run.run("sbt.RunFromSourceMain", cp, options, log) } def main(args: Array[String]): Unit = args match { diff --git a/sbt/src/test/scala/testpkg/ServerSpec.scala b/sbt/src/test/scala/testpkg/ServerSpec.scala index f34eee6b1..259e5c248 100644 --- a/sbt/src/test/scala/testpkg/ServerSpec.scala +++ b/sbt/src/test/scala/testpkg/ServerSpec.scala @@ -71,7 +71,11 @@ case class TestServer(baseDirectory: File) { private val RetByte = '\r'.toByte hostLog("fork to a new sbt instance") - RunFromSourceMain.fork(baseDirectory) + import scala.concurrent.ExecutionContext.Implicits.global + Future { + RunFromSourceMain.fork(baseDirectory) + () + } lazy val portfile = baseDirectory / "project" / "target" / "active.json" hostLog("wait 30s until the server is ready to respond") From f459b218c47a330354111c884e4f204cd9067d02 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 19 Apr 2018 09:47:02 +0100 Subject: [PATCH 244/356] Switch inThisBuild (+friends) to use varargs SettingsDefinition --- main/src/main/scala/sbt/Project.scala | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index bac52a877..8657f119f 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -32,7 +32,7 @@ import Keys.{ watch } import Scope.{ Global, ThisScope } -import Def.{ Flattened, Initialize, ScopedKey, Setting } +import Def.{ Flattened, Initialize, ScopedKey, Setting, SettingsDefinition } import sbt.internal.{ Load, BuildStructure, @@ -863,17 +863,19 @@ trait ProjectExtra { implicit def richTaskSessionVar[T](init: Initialize[Task[T]]): Project.RichTaskSessionVar[T] = new Project.RichTaskSessionVar(init) - def inThisBuild(ss: Seq[Setting[_]]): Seq[Setting[_]] = - inScope(ThisScope.copy(project = Select(ThisBuild)))(ss) + def inThisBuild(ss: SettingsDefinition*): Seq[Setting[_]] = + inScope(ThisScope.copy(project = Select(ThisBuild)))(ss flatMap (_.settings)) - def inConfig(conf: Configuration)(ss: Seq[Setting[_]]): Seq[Setting[_]] = - inScope(ThisScope.copy(config = Select(conf)))((configuration :== conf) +: ss) + def inConfig(conf: Configuration)(ss: SettingsDefinition*): Seq[Setting[_]] = + inScope(ThisScope.copy(config = Select(conf)))( + (configuration :== conf) +: (ss flatMap (_.settings)) + ) - def inTask(t: Scoped)(ss: Seq[Setting[_]]): Seq[Setting[_]] = - inScope(ThisScope.copy(task = Select(t.key)))(ss) + def inTask(t: Scoped)(ss: SettingsDefinition*): Seq[Setting[_]] = + inScope(ThisScope.copy(task = Select(t.key)))(ss flatMap (_.settings)) - def inScope(scope: Scope)(ss: Seq[Setting[_]]): Seq[Setting[_]] = - Project.transform(Scope.replaceThis(scope), ss) + def inScope(scope: Scope)(ss: SettingsDefinition*): Seq[Setting[_]] = + Project.transform(Scope.replaceThis(scope), ss flatMap (_.settings)) private[sbt] def inThisBuild[T](i: Initialize[T]): Initialize[T] = inScope(ThisScope.copy(project = Select(ThisBuild)), i) From ccf938c7861bcc0734f89a61b325a69277808de8 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 19 Apr 2018 09:47:58 +0100 Subject: [PATCH 245/356] Switch to varargs inThisBuild (+friends) --- main/src/main/scala/sbt/Defaults.scala | 315 +++++++++--------- main/src/main/scala/sbt/PluginCross.scala | 2 +- .../scala/sbt/internal/GlobalPlugin.scala | 13 +- main/src/main/scala/sbt/internal/Load.scala | 31 +- .../actions/cross-multiproject/build.sbt | 4 +- sbt/src/sbt-test/actions/previous/scopes.sbt | 4 +- .../cache-update/build.sbt | 4 +- .../cached-resolution-circular/multi.sbt | 4 +- .../cached-resolution-conflicts/multi.sbt | 4 +- .../cached-resolution-exclude/multi.sbt | 4 +- .../cached-resolution-interproj/multi.sbt | 4 +- .../make-pom-type/build.sbt | 4 +- .../publish-local/build.sbt | 4 +- .../publish-local/changes/RetrieveTest.sbt | 4 +- sbt/src/sbt-test/project/derived/build.sbt | 4 +- .../tests/fork-test-group-parallel/build.sbt | 4 +- 16 files changed, 199 insertions(+), 210 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index c8f1beecb..67dc6997e 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -134,9 +134,8 @@ object Defaults extends BuildCommon { def buildCore: Seq[Setting[_]] = thisBuildCore ++ globalCore def thisBuildCore: Seq[Setting[_]] = inScope(GlobalScope.copy(project = Select(ThisBuild)))( - Seq( - managedDirectory := baseDirectory.value / "lib_managed" - )) + managedDirectory := baseDirectory.value / "lib_managed" + ) private[sbt] lazy val globalCore: Seq[Setting[_]] = globalDefaults( defaultTestTasks(test) ++ defaultTestTasks(testOnly) ++ defaultTestTasks(testQuick) ++ Seq( excludeFilter :== HiddenFileFilter @@ -294,10 +293,9 @@ object Defaults extends BuildCommon { def defaultTestTasks(key: Scoped): Seq[Setting[_]] = inTask(key)( - Seq( - tags := Seq(Tags.Test -> 1), - logBuffered := true - )) + tags := Seq(Tags.Test -> 1), + logBuffered := true + ) // TODO: This should be on the new default settings for a project. def projectCore: Seq[Setting[_]] = Seq( @@ -715,21 +713,19 @@ object Defaults extends BuildCommon { lazy val ConfigGlobal: Scope = ConfigZero def testTaskOptions(key: Scoped): Seq[Setting[_]] = inTask(key)( - Seq( - testListeners := { - TestLogger.make(streams.value.log, - closeableTestLogger(streamsManager.value, - test in resolvedScoped.value.scope, - logBuffered.value)) +: - new TestStatusReporter(succeededFile(streams.in(test).value.cacheDirectory)) +: - testListeners.in(TaskZero).value - }, - testOptions := Tests.Listeners(testListeners.value) +: (testOptions in TaskZero).value, - testExecution := testExecutionTask(key).value - )) ++ inScope(GlobalScope)( - Seq( - derive(testGrouping := singleTestGroupDefault.value) - )) + testListeners := { + TestLogger.make(streams.value.log, + closeableTestLogger(streamsManager.value, + test in resolvedScoped.value.scope, + logBuffered.value)) +: + new TestStatusReporter(succeededFile(streams.in(test).value.cacheDirectory)) +: + testListeners.in(TaskZero).value + }, + testOptions := Tests.Listeners(testListeners.value) +: (testOptions in TaskZero).value, + testExecution := testExecutionTask(key).value + ) ++ inScope(GlobalScope)( + derive(testGrouping := singleTestGroupDefault.value) + ) private[this] def closeableTestLogger(manager: Streams, baseKey: Scoped, buffered: Boolean)( tdef: TestDefinition): TestLogger.PerTest = { @@ -985,7 +981,7 @@ object Defaults extends BuildCommon { )) lazy val packageConfig: Seq[Setting[_]] = - inTask(packageBin)(Seq( + inTask(packageBin)( packageOptions := { val n = name.value val ver = version.value @@ -997,14 +993,13 @@ object Defaults extends BuildCommon { Package.addImplManifestAttributes(n, ver, homepage.value, org, orgName) +: main.map(Package.MainClass.apply) ++: old } - )) ++ + ) ++ inTask(packageSrc)( - Seq( - packageOptions := Package.addSpecManifestAttributes( - name.value, - version.value, - organizationName.value) +: packageOptions.value - )) ++ + packageOptions := Package.addSpecManifestAttributes( + name.value, + version.value, + organizationName.value) +: packageOptions.value + ) ++ packageTaskSettings(packageBin, packageBinMappings) ++ packageTaskSettings(packageSrc, packageSrcMappings) ++ packageTaskSettings(packageDoc, packageDocMappings) ++ @@ -1081,14 +1076,13 @@ object Defaults extends BuildCommon { def packageTaskSettings(key: TaskKey[File], mappingsTask: Initialize[Task[Seq[(File, String)]]]) = inTask(key)( - Seq( - key in TaskZero := packageTask.value, - packageConfiguration := packageConfigurationTask.value, - mappings := mappingsTask.value, - packagedArtifact := (artifact.value -> key.value), - artifact := artifactSetting.value, - artifactPath := artifactPathSetting(artifact).value - )) + key in TaskZero := packageTask.value, + packageConfiguration := packageConfigurationTask.value, + mappings := mappingsTask.value, + packagedArtifact := (artifact.value -> key.value), + artifact := artifactSetting.value, + artifactPath := artifactPathSetting(artifact).value + ) def packageTask: Initialize[Task[File]] = Def.task { @@ -1288,49 +1282,48 @@ object Defaults extends BuildCommon { def docTaskSettings(key: TaskKey[File] = doc): Seq[Setting[_]] = inTask(key)( - Seq( - apiMappings ++= { - val dependencyCp = dependencyClasspath.value - val log = streams.value.log - if (autoAPIMappings.value) APIMappings.extract(dependencyCp, log).toMap - else Map.empty[File, URL] - }, - fileInputOptions := Seq("-doc-root-content", "-diagrams-dot-path"), - key in TaskZero := { - val s = streams.value - val cs: Compilers = compilers.value - val srcs = sources.value - val out = target.value - val sOpts = scalacOptions.value - val xapis = apiMappings.value - val hasScala = srcs.exists(_.name.endsWith(".scala")) - val hasJava = srcs.exists(_.name.endsWith(".java")) - val cp = data(dependencyClasspath.value).toList - val label = nameForSrc(configuration.value.name) - val fiOpts = fileInputOptions.value - val reporter = (compilerReporter in compile).value - (hasScala, hasJava) match { - case (true, _) => - val options = sOpts ++ Opts.doc.externalAPI(xapis) - val runDoc = Doc.scaladoc(label, s.cacheStoreFactory sub "scala", cs.scalac match { - case ac: AnalyzingCompiler => ac.onArgs(exported(s, "scaladoc")) - }, fiOpts) - runDoc(srcs, cp, out, options, maxErrors.value, s.log) - case (_, true) => - val javadoc = - sbt.inc.Doc.cachedJavadoc(label, s.cacheStoreFactory sub "java", cs.javaTools) - javadoc.run(srcs.toList, - cp, - out, - javacOptions.value.toList, - IncToolOptionsUtil.defaultIncToolOptions(), - s.log, - reporter) - case _ => () // do nothing - } - out + apiMappings ++= { + val dependencyCp = dependencyClasspath.value + val log = streams.value.log + if (autoAPIMappings.value) APIMappings.extract(dependencyCp, log).toMap + else Map.empty[File, URL] + }, + fileInputOptions := Seq("-doc-root-content", "-diagrams-dot-path"), + key in TaskZero := { + val s = streams.value + val cs: Compilers = compilers.value + val srcs = sources.value + val out = target.value + val sOpts = scalacOptions.value + val xapis = apiMappings.value + val hasScala = srcs.exists(_.name.endsWith(".scala")) + val hasJava = srcs.exists(_.name.endsWith(".java")) + val cp = data(dependencyClasspath.value).toList + val label = nameForSrc(configuration.value.name) + val fiOpts = fileInputOptions.value + val reporter = (compilerReporter in compile).value + (hasScala, hasJava) match { + case (true, _) => + val options = sOpts ++ Opts.doc.externalAPI(xapis) + val runDoc = Doc.scaladoc(label, s.cacheStoreFactory sub "scala", cs.scalac match { + case ac: AnalyzingCompiler => ac.onArgs(exported(s, "scaladoc")) + }, fiOpts) + runDoc(srcs, cp, out, options, maxErrors.value, s.log) + case (_, true) => + val javadoc = + sbt.inc.Doc.cachedJavadoc(label, s.cacheStoreFactory sub "java", cs.javaTools) + javadoc.run(srcs.toList, + cp, + out, + javacOptions.value.toList, + IncToolOptionsUtil.defaultIncToolOptions(), + s.log, + reporter) + case _ => () // do nothing } - )) + out + } + ) def mainBgRunTask = mainBgRunTaskForConfig(Select(Runtime)) def mainBgRunMainTask = mainBgRunMainTaskForConfig(Select(Runtime)) @@ -1630,15 +1623,14 @@ object Defaults extends BuildCommon { // build.sbt is treated a Scala source of metabuild, so to enable deprecation flag on build.sbt we set the option here. lazy val deprecationSettings: Seq[Setting[_]] = inConfig(Compile)( - Seq( - scalacOptions := { - val old = scalacOptions.value - val existing = old.toSet - val d = "-deprecation" - if (sbtPlugin.value && !existing(d)) d :: old.toList - else old - } - )) + scalacOptions := { + val old = scalacOptions.value + val existing = old.toSet + val d = "-deprecation" + if (sbtPlugin.value && !existing(d)) d :: old.toList + else old + } + ) } object Classpaths { import Keys._ @@ -2146,78 +2138,77 @@ object Classpaths { def sbtClassifiersTasks = sbtClassifiersGlobalDefaults ++ inTask(updateSbtClassifiers)( - Seq( - externalResolvers := { - val explicit = buildStructure.value - .units(thisProjectRef.value.build) - .unit - .plugins - .pluginData - .resolvers - explicit orElse bootRepositories(appConfiguration.value) getOrElse externalResolvers.value - }, - ivyConfiguration := InlineIvyConfiguration( - paths = ivyPaths.value, - resolvers = externalResolvers.value.toVector, - otherResolvers = Vector.empty, - moduleConfigurations = Vector.empty, - lock = Option(lock(appConfiguration.value)), - checksums = checksums.value.toVector, - managedChecksums = false, - resolutionCacheDir = Some(crossTarget.value / "resolution-cache"), - updateOptions = UpdateOptions(), - log = streams.value.log - ), - ivySbt := ivySbt0.value, - classifiersModule := classifiersModuleTask.value, - // Redefine scalaVersion and scalaBinaryVersion specifically for the dependency graph used for updateSbtClassifiers task. - // to fix https://github.com/sbt/sbt/issues/2686 - scalaVersion := appConfiguration.value.provider.scalaProvider.version, - scalaBinaryVersion := binaryScalaVersion(scalaVersion.value), - scalaModuleInfo := { - Some( - ScalaModuleInfo( - scalaVersion.value, - scalaBinaryVersion.value, - Vector(), - checkExplicit = false, - filterImplicit = false, - overrideScalaVersion = true).withScalaOrganization(scalaOrganization.value)) - }, - updateSbtClassifiers in TaskGlobal := (Def.task { - val lm = dependencyResolution.value - val s = streams.value - val is = ivySbt.value - val mod = classifiersModule.value - val c = updateConfiguration.value - val app = appConfiguration.value - val srcTypes = sourceArtifactTypes.value - val docTypes = docArtifactTypes.value - val log = s.log - val out = is.withIvy(log)(_.getSettings.getDefaultIvyUserDir) - val uwConfig = (unresolvedWarningConfiguration in update).value - withExcludes(out, mod.classifiers, lock(app)) { - excludes => - // val noExplicitCheck = ivy.map(_.withCheckExplicit(false)) - LibraryManagement.transitiveScratch( - lm, - "sbt", - GetClassifiersConfiguration( - mod, - excludes.toVector, - c.withArtifactFilter(c.artifactFilter.map(af => af.withInverted(!af.inverted))), - srcTypes.toVector, - docTypes.toVector - ), - uwConfig, - log - ) match { - case Left(_) => ??? - case Right(ur) => ur - } - } - } tag (Tags.Update, Tags.Network)).value - )) ++ Seq(bootIvyConfiguration := (ivyConfiguration in updateSbtClassifiers).value) + externalResolvers := { + val explicit = buildStructure.value + .units(thisProjectRef.value.build) + .unit + .plugins + .pluginData + .resolvers + explicit orElse bootRepositories(appConfiguration.value) getOrElse externalResolvers.value + }, + ivyConfiguration := InlineIvyConfiguration( + paths = ivyPaths.value, + resolvers = externalResolvers.value.toVector, + otherResolvers = Vector.empty, + moduleConfigurations = Vector.empty, + lock = Option(lock(appConfiguration.value)), + checksums = checksums.value.toVector, + managedChecksums = false, + resolutionCacheDir = Some(crossTarget.value / "resolution-cache"), + updateOptions = UpdateOptions(), + log = streams.value.log + ), + ivySbt := ivySbt0.value, + classifiersModule := classifiersModuleTask.value, + // Redefine scalaVersion and scalaBinaryVersion specifically for the dependency graph used for updateSbtClassifiers task. + // to fix https://github.com/sbt/sbt/issues/2686 + scalaVersion := appConfiguration.value.provider.scalaProvider.version, + scalaBinaryVersion := binaryScalaVersion(scalaVersion.value), + scalaModuleInfo := { + Some( + ScalaModuleInfo( + scalaVersion.value, + scalaBinaryVersion.value, + Vector(), + checkExplicit = false, + filterImplicit = false, + overrideScalaVersion = true).withScalaOrganization(scalaOrganization.value)) + }, + updateSbtClassifiers in TaskGlobal := (Def.task { + val lm = dependencyResolution.value + val s = streams.value + val is = ivySbt.value + val mod = classifiersModule.value + val c = updateConfiguration.value + val app = appConfiguration.value + val srcTypes = sourceArtifactTypes.value + val docTypes = docArtifactTypes.value + val log = s.log + val out = is.withIvy(log)(_.getSettings.getDefaultIvyUserDir) + val uwConfig = (unresolvedWarningConfiguration in update).value + withExcludes(out, mod.classifiers, lock(app)) { + excludes => + // val noExplicitCheck = ivy.map(_.withCheckExplicit(false)) + LibraryManagement.transitiveScratch( + lm, + "sbt", + GetClassifiersConfiguration( + mod, + excludes.toVector, + c.withArtifactFilter(c.artifactFilter.map(af => af.withInverted(!af.inverted))), + srcTypes.toVector, + docTypes.toVector + ), + uwConfig, + log + ) match { + case Left(_) => ??? + case Right(ur) => ur + } + } + } tag (Tags.Update, Tags.Network)).value + ) ++ Seq(bootIvyConfiguration := (ivyConfiguration in updateSbtClassifiers).value) def classifiersModuleTask: Initialize[Task[GetClassifiersModule]] = Def.task { @@ -3245,7 +3236,7 @@ trait BuildExtra extends BuildCommon with DefExtra { * This is useful for reducing test:compile time when not running test. */ def noTestCompletion(config: Configuration = Test): Setting[_] = - inConfig(config)(Seq(definedTests := detectTests.value)).head + inConfig(config)(definedTests := detectTests.value).head def filterKeys(ss: Seq[Setting[_]], transitive: Boolean = false)( f: ScopedKey[_] => Boolean): Seq[Setting[_]] = diff --git a/main/src/main/scala/sbt/PluginCross.scala b/main/src/main/scala/sbt/PluginCross.scala index 799c854f6..0da74dc7b 100644 --- a/main/src/main/scala/sbt/PluginCross.scala +++ b/main/src/main/scala/sbt/PluginCross.scala @@ -46,7 +46,7 @@ private[sbt] object PluginCross { val add = List(sbtVersion in GlobalScope in pluginCrossBuild :== version) ++ List(scalaVersion := scalaVersionSetting.value) ++ inScope(GlobalScope.copy(project = Select(currentRef)))( - Seq(scalaVersion := scalaVersionSetting.value) + scalaVersion := scalaVersionSetting.value ) val cleared = session.mergeSettings.filterNot(crossExclude) val newStructure = Load.reapply(cleared ++ add, structure) diff --git a/main/src/main/scala/sbt/internal/GlobalPlugin.scala b/main/src/main/scala/sbt/internal/GlobalPlugin.scala index 0424a325c..317e6fd0d 100644 --- a/main/src/main/scala/sbt/internal/GlobalPlugin.scala +++ b/main/src/main/scala/sbt/internal/GlobalPlugin.scala @@ -98,13 +98,12 @@ object GlobalPlugin { } } val globalPluginSettings = Project.inScope(Scope.GlobalScope in LocalRootProject)( - Seq( - organization := SbtArtifacts.Organization, - onLoadMessage := Keys.baseDirectory("Loading global plugins from " + _).value, - name := "global-plugin", - sbtPlugin := true, - version := "0.0" - )) + organization := SbtArtifacts.Organization, + onLoadMessage := Keys.baseDirectory("Loading global plugins from " + _).value, + name := "global-plugin", + sbtPlugin := true, + version := "0.0" + ) } final case class GlobalPluginData(projectID: ModuleID, dependencies: Seq[ModuleID], diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index da6c2c8c1..64819b906 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -1126,22 +1126,21 @@ private[sbt] object Load { /** These are the settings defined when loading a project "meta" build. */ val autoPluginSettings: Seq[Setting[_]] = inScope(GlobalScope in LocalRootProject)( - Seq( - sbtPlugin :== true, - pluginData := { - val prod = (exportedProducts in Configurations.Runtime).value - val cp = (fullClasspath in Configurations.Runtime).value - val opts = (scalacOptions in Configurations.Compile).value - PluginData( - removeEntries(cp, prod), - prod, - Some(fullResolvers.value.toVector), - Some(update.value), - opts - ) - }, - onLoadMessage := ("Loading project definition from " + baseDirectory.value) - )) + sbtPlugin :== true, + pluginData := { + val prod = (exportedProducts in Configurations.Runtime).value + val cp = (fullClasspath in Configurations.Runtime).value + val opts = (scalacOptions in Configurations.Compile).value + PluginData( + removeEntries(cp, prod), + prod, + Some(fullResolvers.value.toVector), + Some(update.value), + opts + ) + }, + onLoadMessage := ("Loading project definition from " + baseDirectory.value) + ) private[this] def removeEntries( cp: Seq[Attributed[File]], diff --git a/sbt/src/sbt-test/actions/cross-multiproject/build.sbt b/sbt/src/sbt-test/actions/cross-multiproject/build.sbt index d0eff709c..f7c0b12fc 100644 --- a/sbt/src/sbt-test/actions/cross-multiproject/build.sbt +++ b/sbt/src/sbt-test/actions/cross-multiproject/build.sbt @@ -1,6 +1,6 @@ -inThisBuild(List( +inThisBuild( crossScalaVersions := Seq("2.12.1", "2.11.8") -)) +) lazy val rootProj = (project in file(".")) .aggregate(libProj, fooPlugin) diff --git a/sbt/src/sbt-test/actions/previous/scopes.sbt b/sbt/src/sbt-test/actions/previous/scopes.sbt index 8062b1df1..a6998efe0 100644 --- a/sbt/src/sbt-test/actions/previous/scopes.sbt +++ b/sbt/src/sbt-test/actions/previous/scopes.sbt @@ -22,7 +22,7 @@ x in subA in Compile := { } -inConfig(Compile)(Seq( +inConfig(Compile)( y in subB := { // verify that the referenced key gets delegated val xty = (x in Test in y).previous getOrElse 0 // 13 @@ -31,7 +31,7 @@ inConfig(Compile)(Seq( println(s"xcy=$xcy, xty=$xty") xty * xcy } -)) +) def parser = { import complete.DefaultParsers._ diff --git a/sbt/src/sbt-test/dependency-management/cache-update/build.sbt b/sbt/src/sbt-test/dependency-management/cache-update/build.sbt index b0f3eec0b..146aca5b5 100644 --- a/sbt/src/sbt-test/dependency-management/cache-update/build.sbt +++ b/sbt/src/sbt-test/dependency-management/cache-update/build.sbt @@ -4,7 +4,7 @@ dependencyOverrides in ThisBuild += "com.github.nscala-time" %% "nscala-time" % lazy val root = (project in file(".")) .dependsOn(p1 % Compile) .settings( - inThisBuild(List( + inThisBuild( organizationName := "eed3si9n", organizationHomepage := Some(url("http://example.com/")), homepage := Some(url("https://github.com/example/example")), @@ -20,7 +20,7 @@ lazy val root = (project in file(".")) version := "0.3.1-SNAPSHOT", description := "An HTTP client for Scala with Async Http Client underneath.", licenses := Seq("Apache 2" -> new URL("http://www.apache.org/licenses/LICENSE-2.0.txt")), - )), + ), ivyPaths := IvyPaths( (baseDirectory in ThisBuild).value, Some((baseDirectory in LocalRootProject).value / "ivy-cache") diff --git a/sbt/src/sbt-test/dependency-management/cached-resolution-circular/multi.sbt b/sbt/src/sbt-test/dependency-management/cached-resolution-circular/multi.sbt index b1180c1af..05a8ffae7 100644 --- a/sbt/src/sbt-test/dependency-management/cached-resolution-circular/multi.sbt +++ b/sbt/src/sbt-test/dependency-management/cached-resolution-circular/multi.sbt @@ -41,8 +41,8 @@ lazy val c = project. lazy val root = (project in file(".")). settings(commonSettings). - settings(inThisBuild(Seq( + settings(inThisBuild( organization := "org.example", version := "1.0-SNAPSHOT", updateOptions := updateOptions.value.withCachedResolution(true) - ))) + )) diff --git a/sbt/src/sbt-test/dependency-management/cached-resolution-conflicts/multi.sbt b/sbt/src/sbt-test/dependency-management/cached-resolution-conflicts/multi.sbt index 61a573e18..aabebeae1 100644 --- a/sbt/src/sbt-test/dependency-management/cached-resolution-conflicts/multi.sbt +++ b/sbt/src/sbt-test/dependency-management/cached-resolution-conflicts/multi.sbt @@ -1,12 +1,12 @@ // https://github.com/sbt/sbt/issues/1710 // https://github.com/sbt/sbt/issues/1760 -inThisBuild(Seq( +inThisBuild( organization := "com.example", version := "0.1.0", scalaVersion := "2.10.4", updateOptions := updateOptions.value.withCachedResolution(true) -)) +) def commonSettings: Seq[Def.Setting[_]] = Seq( ivyPaths := IvyPaths((baseDirectory in ThisBuild).value, Some((baseDirectory in LocalRootProject).value / "ivy-cache")), diff --git a/sbt/src/sbt-test/dependency-management/cached-resolution-exclude/multi.sbt b/sbt/src/sbt-test/dependency-management/cached-resolution-exclude/multi.sbt index 7bca9158b..0a3327fa1 100644 --- a/sbt/src/sbt-test/dependency-management/cached-resolution-exclude/multi.sbt +++ b/sbt/src/sbt-test/dependency-management/cached-resolution-exclude/multi.sbt @@ -27,7 +27,7 @@ lazy val b = project. lazy val root = (project in file(".")). aggregate(a, b). - settings(inThisBuild(Seq( + settings(inThisBuild( organization := "org.example", version := "1.0", updateOptions := updateOptions.value.withCachedResolution(true), @@ -45,4 +45,4 @@ lazy val root = (project in file(".")). sys.error("commons-io NOT found when it should NOT be excluded") } } - ))) + )) diff --git a/sbt/src/sbt-test/dependency-management/cached-resolution-interproj/multi.sbt b/sbt/src/sbt-test/dependency-management/cached-resolution-interproj/multi.sbt index f359cfc76..c21995ecc 100644 --- a/sbt/src/sbt-test/dependency-management/cached-resolution-interproj/multi.sbt +++ b/sbt/src/sbt-test/dependency-management/cached-resolution-interproj/multi.sbt @@ -27,7 +27,7 @@ lazy val a = project. lazy val root = (project in file(".")). aggregate(a). - settings(inThisBuild(Seq( + settings(inThisBuild( organization := "org.example", version := "1.0", updateOptions := updateOptions.value.withCachedResolution(true), @@ -49,4 +49,4 @@ lazy val root = (project in file(".")). sys.error("junit NOT found when it should be included: " + atestcp.toString) } } - ))) + )) diff --git a/sbt/src/sbt-test/dependency-management/make-pom-type/build.sbt b/sbt/src/sbt-test/dependency-management/make-pom-type/build.sbt index 6d9005519..a7857d39d 100644 --- a/sbt/src/sbt-test/dependency-management/make-pom-type/build.sbt +++ b/sbt/src/sbt-test/dependency-management/make-pom-type/build.sbt @@ -2,12 +2,12 @@ lazy val p1 = (project in file("p1")). settings( checkTask(expectedMongo), libraryDependencies += "org.mongodb" %% "casbah" % "2.4.1" pomOnly(), - inThisBuild(List( + inThisBuild( organization := "org.example", version := "1.0", scalaVersion := "2.9.2", autoScalaLibrary := false - )) + ) ) lazy val p2 = (project in file("p2")). diff --git a/sbt/src/sbt-test/dependency-management/publish-local/build.sbt b/sbt/src/sbt-test/dependency-management/publish-local/build.sbt index 261aa6ac3..b8ace934d 100644 --- a/sbt/src/sbt-test/dependency-management/publish-local/build.sbt +++ b/sbt/src/sbt-test/dependency-management/publish-local/build.sbt @@ -1,12 +1,12 @@ lazy val root = (project in file(".")). dependsOn(sub). aggregate(sub). - settings(inThisBuild(List( + settings(inThisBuild( organization := "A", version := "1.0", ivyPaths := baseDirectory( dir => IvyPaths(dir, Some(dir / "ivy" / "cache")) ).value, externalResolvers := (baseDirectory map { base => Resolver.file("local", base / "ivy" / "local" asFile)(Resolver.ivyStylePatterns) :: Nil }).value - )), + ), mavenStyle, interProject, name := "Publish Test" diff --git a/sbt/src/sbt-test/dependency-management/publish-local/changes/RetrieveTest.sbt b/sbt/src/sbt-test/dependency-management/publish-local/changes/RetrieveTest.sbt index 795608334..ca033e3b8 100644 --- a/sbt/src/sbt-test/dependency-management/publish-local/changes/RetrieveTest.sbt +++ b/sbt/src/sbt-test/dependency-management/publish-local/changes/RetrieveTest.sbt @@ -1,10 +1,10 @@ lazy val root = (project in file(".")). - settings(inThisBuild(List( + settings(inThisBuild( organization := "A", version := "1.0", ivyPaths := baseDirectory( dir => IvyPaths(dir, Some(dir / "ivy" / "cache")) ).value, externalResolvers := (baseDirectory map { base => Resolver.file("local", base / "ivy" / "local" asFile)(Resolver.ivyStylePatterns) :: Nil }).value - )), + ), mavenStyle, name := "Retrieve Test", libraryDependencies := (publishMavenStyle { style => if(style) mavenStyleDependencies else ivyStyleDependencies }).value diff --git a/sbt/src/sbt-test/project/derived/build.sbt b/sbt/src/sbt-test/project/derived/build.sbt index 21cfc9207..1d1a5fd5b 100644 --- a/sbt/src/sbt-test/project/derived/build.sbt +++ b/sbt/src/sbt-test/project/derived/build.sbt @@ -23,7 +23,7 @@ globalDepE in Global := "globalE" // ---------------- Derived settings // verify that deriving is transitive -inScope(GlobalScope)(Seq( +inScope(GlobalScope)( Def.derive(customA := customB.value + "-a"), Def.derive(customB := thisProject.value.id + "-b"), // verify that a setting with multiple triggers still only gets added once @@ -36,7 +36,7 @@ inScope(GlobalScope)(Seq( // if customE were added in Global because of name, there would be an error // because description wouldn't be found Def.derive(customE := globalDepE.value + "-" + projectDepE.value) -)) +) // ---------------- Projects diff --git a/sbt/src/sbt-test/tests/fork-test-group-parallel/build.sbt b/sbt/src/sbt-test/tests/fork-test-group-parallel/build.sbt index 761d141ee..8f4ca2611 100644 --- a/sbt/src/sbt-test/tests/fork-test-group-parallel/build.sbt +++ b/sbt/src/sbt-test/tests/fork-test-group-parallel/build.sbt @@ -1,7 +1,7 @@ scalaVersion in ThisBuild := "2.11.8" concurrentRestrictions in Global := Seq(Tags.limitAll(4)) libraryDependencies += "org.specs2" %% "specs2-core" % "3.8.4" % Test -inConfig(Test)(Seq( +inConfig(Test)( testGrouping := { val home = javaHome.value val strategy = outputStrategy.value @@ -22,4 +22,4 @@ inConfig(Test)(Seq( ))} }, TaskKey[Unit]("test-failure") := test.failure.value -)) +) From 8c6f71a18057217ade4d56b9d1f6be1a7e2a15ae Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Thu, 12 Apr 2018 12:30:11 -0700 Subject: [PATCH 246/356] Bump io My next commit replaces the implementation of Watched.executeContinuously using apis that are available only in a pending io pull request (https://github.com/sbt/io/pull/142). --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index bcefc8b4e..81aced917 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,7 +8,7 @@ object Dependencies { val baseScalaVersion = scala212 // sbt modules - private val ioVersion = "1.1.6" + private val ioVersion = "1.1.6-SNAPSHOT" private val utilVersion = "1.1.3" private val lmVersion = "1.1.4" private val zincVersion = "1.1.5" From 754385125acb1debb5bb95e6b1b690a8dd73eb9e Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Thu, 12 Apr 2018 13:06:54 -0700 Subject: [PATCH 247/356] Use new EventMonitor in executeContinuously In https://github.com/sbt/io/pull/142, I add a new api for watching for source file events. This commit updates sbt to use the new EventMonitor based api. The EventMonitor has an anti-entropy parameter, so that multiple events on the same file in a short window of time do not trigger a build. I add a key to tune it. The implementation of executeContinuously is pretty similar. The main changes are that shouldTerminate now blocks (EventMonitor spins up a thread to check the termination condition) and that the EventMonitor.watch method only returns a Boolean. This is because the event monitor contains mutable state. It does, however, have a state() method that returns an immutable snapshot of the state. --- main-command/src/main/scala/sbt/Watched.scala | 81 ++++++++++++------- main/src/main/scala/sbt/Defaults.scala | 5 +- main/src/main/scala/sbt/Keys.scala | 1 + 3 files changed, 55 insertions(+), 32 deletions(-) diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index cff9c1597..029702841 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -12,7 +12,7 @@ import java.nio.file.FileSystems import sbt.BasicCommandStrings.ClearOnFailure import sbt.State.FailureWall -import sbt.internal.io.{ Source, SourceModificationWatch, WatchState } +import sbt.internal.io.{ EventMonitor, Source, SourceModificationWatch, WatchState } import sbt.internal.util.AttributeKey import sbt.internal.util.Types.const import sbt.io._ @@ -33,6 +33,12 @@ trait Watched { */ def pollInterval: FiniteDuration = Watched.PollDelay + /** + * The duration for which the EventMonitor while ignore file events after a file triggers + * a new build. + */ + def antiEntropy: FiniteDuration = Watched.AntiEntropy + /** The message to show when triggered execution waits for sources to change.*/ private[sbt] def watchingMessage(s: WatchState): String = Watched.defaultWatchingMessage(s) @@ -81,52 +87,65 @@ object Watched { override def watchSources(s: State) = (base.watchSources(s) /: paths)(_ ++ _.watchSources(s)) override def terminateWatch(key: Int): Boolean = base.terminateWatch(key) override val pollInterval = (base +: paths).map(_.pollInterval).min + override val antiEntropy = (base +: paths).map(_.antiEntropy).min override def watchingMessage(s: WatchState) = base.watchingMessage(s) override def triggeredMessage(s: WatchState) = base.triggeredMessage(s) } def empty: Watched = new AWatched val PollDelay: FiniteDuration = 500.milliseconds + val AntiEntropy: FiniteDuration = 40.milliseconds def isEnter(key: Int): Boolean = key == 10 || key == 13 def printIfDefined(msg: String) = if (!msg.isEmpty) System.out.println(msg) def executeContinuously(watched: Watched, s: State, next: String, repeat: String): State = { @tailrec def shouldTerminate: Boolean = - (System.in.available > 0) && (watched.terminateWatch(System.in.read()) || shouldTerminate) - val sources = watched.watchSources(s) - val service = s get ContinuousWatchService getOrElse watched.watchService() - val watchState = s get ContinuousState getOrElse WatchState.empty(service, sources) - - if (watchState.count > 0) - printIfDefined(watched watchingMessage watchState) - - val (triggered, newWatchState) = - try { - val (triggered, newWatchState) = - SourceModificationWatch.watch(watched.pollInterval, watchState)(shouldTerminate) - (triggered, newWatchState) - } catch { - case e: Exception => - val log = s.log - log.error("Error occurred obtaining files to watch. Terminating continuous execution...") - State.handleException(e, s, log) - (false, watchState) - } - - if (triggered) { - printIfDefined(watched triggeredMessage newWatchState) - (ClearOnFailure :: next :: FailureWall :: repeat :: s) - .put(ContinuousState, newWatchState) - .put(ContinuousWatchService, service) - } else { - while (System.in.available() > 0) System.in.read() - service.close() - s.remove(ContinuousState).remove(ContinuousWatchService) + watched.terminateWatch(System.in.read()) || shouldTerminate + val log = s.log + val logger = new EventMonitor.Logger { + override def debug(msg: => Any): Unit = log.debug(msg.toString) + } + s get ContinuousEventMonitor match { + case None => + // This is the first iteration, so run the task and create a new EventMonitor + (ClearOnFailure :: next :: FailureWall :: repeat :: s) + .put( + ContinuousEventMonitor, + EventMonitor(WatchState.empty(watched.watchService(), watched.watchSources(s)), + watched.pollInterval, + watched.antiEntropy, + shouldTerminate, + logger) + ) + case Some(eventMonitor) => + printIfDefined(watched watchingMessage eventMonitor.state) + val triggered = try eventMonitor.watch() + catch { + case e: Exception => + log.error( + "Error occurred obtaining files to watch. Terminating continuous execution...") + State.handleException(e, s, log) + false + } + if (triggered) { + printIfDefined(watched triggeredMessage eventMonitor.state) + ClearOnFailure :: next :: FailureWall :: repeat :: s + } else { + while (System.in.available() > 0) System.in.read() + eventMonitor.close() + s.remove(ContinuousEventMonitor) + } } } + + val ContinuousEventMonitor = + AttributeKey[EventMonitor]("watch event monitor", + "Internal: maintains watch state and monitor threads.") + @deprecated("Superseded by ContinuousEventMonitor", "1.1.5") val ContinuousState = AttributeKey[WatchState]("watch state", "Internal: tracks state for continuous execution.") + @deprecated("Superseded by ContinuousEventMonitor", "1.1.5") val ContinuousWatchService = AttributeKey[WatchService]("watch service", "Internal: tracks watch service for continuous execution.") diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 82af2a9e4..e34ccfc04 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -248,6 +248,7 @@ object Defaults extends BuildCommon { concurrentRestrictions := defaultRestrictions.value, parallelExecution :== true, pollInterval :== new FiniteDuration(500, TimeUnit.MILLISECONDS), + watchAntiEntropy :== new FiniteDuration(40, TimeUnit.MILLISECONDS), watchService :== { () => Watched.createWatchService() }, @@ -555,13 +556,15 @@ object Defaults extends BuildCommon { Def.setting { val getService = watchService.value val interval = pollInterval.value + val _antiEntropy = watchAntiEntropy.value val base = thisProjectRef.value val msg = watchingMessage.value val trigMsg = triggeredMessage.value new Watched { val scoped = watchTransitiveSources in base val key = scoped.scopedKey - override def pollInterval = interval + override def antiEntropy: FiniteDuration = _antiEntropy + override def pollInterval: FiniteDuration = interval override def watchingMessage(s: WatchState) = msg(s) override def triggeredMessage(s: WatchState) = trigMsg(s) override def watchService() = getService() diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 8e26b54fb..473bfffba 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -140,6 +140,7 @@ object Keys { val analysis = AttributeKey[CompileAnalysis]("analysis", "Analysis of compilation, including dependencies and generated outputs.", DSetting) val watch = SettingKey(BasicKeys.watch) val suppressSbtShellNotification = settingKey[Boolean]("""True to suppress the "Executing in batch mode.." message.""").withRank(CSetting) + val watchAntiEntropy = settingKey[FiniteDuration]("Duration for which the watch EventMonitor will ignore events for a file after that file has triggered a build.").withRank(BMinusSetting) val pollInterval = settingKey[FiniteDuration]("Interval between checks for modified sources by the continuous execution command.").withRank(BMinusSetting) val watchService = settingKey[() => WatchService]("Service to use to monitor file system changes.").withRank(BMinusSetting) val watchSources = taskKey[Seq[Watched.WatchSource]]("Defines the sources in this project for continuous execution to watch for changes.").withRank(BMinusSetting) From eeeb4c9ff2e047599b9bbc83a1dcd8951cf9ddad Mon Sep 17 00:00:00 2001 From: OlegYch Date: Mon, 23 Apr 2018 20:46:08 +0300 Subject: [PATCH 248/356] Remove usage of DynamicVariable and fix memory leak, fixes https://github.com/sbt/sbt/issues/4112 --- build.sbt | 2 ++ .../scala/sbt/JUnitXmlTestsListener.scala | 23 ++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index 5baf26bfc..c1e10290a 100644 --- a/build.sbt +++ b/build.sbt @@ -220,6 +220,8 @@ lazy val testingProj = (project in file("testing")) exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestItemEvent.copy$default$*"), exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestStringEvent.copy"), exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestStringEvent.copy$default$1"), + //no reason to use + exclude[DirectMissingMethodProblem]("sbt.JUnitXmlTestsListener.testSuite"), ) ) .configure(addSbtIO, addSbtCompilerClasspath, addSbtUtilLogging) diff --git a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala index 3e62dcea2..29a4ebc8c 100644 --- a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala +++ b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala @@ -12,7 +12,6 @@ import java.net.InetAddress import java.util.Hashtable import scala.collection.mutable.ListBuffer -import scala.util.DynamicVariable import scala.xml.{ Elem, Node => XNode, XML } import testing.{ Event => TEvent, @@ -124,21 +123,28 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { } /**The currently running test suite*/ - val testSuite = new DynamicVariable(null: TestSuite) + private val testSuite = new InheritableThreadLocal[Option[TestSuite]] { + override def initialValue(): Option[TestSuite] = None + } + + private def withTestSuite[T](f: TestSuite => T) = + testSuite.get().map(f).getOrElse(sys.error("no test suite")) /**Creates the output Dir*/ - override def doInit() = { targetDir.mkdirs() } + override def doInit() = { + val _ = targetDir.mkdirs() + } /** * Starts a new, initially empty Suite with the given name. */ - override def startGroup(name: String): Unit = testSuite.value_=(new TestSuite(name)) + override def startGroup(name: String): Unit = testSuite.set(Some(new TestSuite(name))) /** * Adds all details for the given even to the current suite. */ override def testEvent(event: TestEvent): Unit = for (e <- event.detail) { - testSuite.value.addEvent(e) + withTestSuite(_.addEvent(e)) } /** @@ -174,7 +180,7 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { def selector = null def throwable = new OptionalThrowable(t) } - testSuite.value.addEvent(event) + withTestSuite(_.addEvent(event)) writeSuite() } @@ -191,10 +197,11 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { private[this] def normalizeName(s: String) = s.replaceAll("""\s+""", "-") private def writeSuite() = { - val file = new File(targetDir, s"${normalizeName(testSuite.value.name)}.xml").getAbsolutePath + val file = new File(targetDir, s"${normalizeName(withTestSuite(_.name))}.xml").getAbsolutePath // TODO would be nice to have a logger and log this with level debug // System.err.println("Writing JUnit XML test report: " + file) - XML.save(file, testSuite.value.stop(), "UTF-8", true, null) + XML.save(file, withTestSuite(_.stop()), "UTF-8", true, null) + testSuite.remove() } /**Does nothing, as we write each file after a suite is done.*/ From 065b65a05e25885840c1f713ef16cffeb6a1f8af Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 24 Apr 2018 07:29:49 +0100 Subject: [PATCH 249/356] Drop -Ywarn-unused:-params Previously we'd get in the build logs: [error] params cannot be negated, it enables other arguments and lots of wawrnings. Now we just get lots of warnings without the non-fatal error message. --- project/Util.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Util.scala b/project/Util.scala index 8c68f4ced..d2141341b 100644 --- a/project/Util.scala +++ b/project/Util.scala @@ -48,7 +48,7 @@ object Util { "-Yno-adapted-args", "-Ywarn-dead-code", "-Ywarn-numeric-widen", - "-Ywarn-unused:-patvars,-params,-implicits,_", + "-Ywarn-unused:-patvars,-implicits,_", "-Ywarn-unused-import" ) }), From 490f57c779e007436455d683ffaedaf235387185 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 4 Apr 2018 15:13:53 +0100 Subject: [PATCH 250/356] Upgrade to sbt-scalafmt 1.15 & scalafmt 1.4.0 --- build.sbt | 2 +- project/plugins.sbt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 61231fbd7..1c24cf775 100644 --- a/build.sbt +++ b/build.sbt @@ -37,7 +37,7 @@ def buildLevelSettings: Seq[Setting[_]] = resolvers += Resolver.mavenLocal, scalafmtOnCompile := true, scalafmtOnCompile in Sbt := false, - scalafmtVersion := "1.3.0", + scalafmtVersion := "1.4.0", )) def commonSettings: Seq[Setting[_]] = Def.settings( diff --git a/project/plugins.sbt b/project/plugins.sbt index 278929bd0..4ec42a3d6 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -6,4 +6,5 @@ addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.5") addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.4.0") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.8.0") +addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.15") addSbtPlugin("com.lightbend" % "sbt-whitesource" % "0.1.9") From cf1f65612a205f554c538f0d8542c1b845881213 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 3 Oct 2017 16:16:09 +0100 Subject: [PATCH 251/356] Don't align args or params by the opening parens --- .scalafmt.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.scalafmt.conf b/.scalafmt.conf index e4ab36511..2486945d0 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -8,3 +8,8 @@ docstrings = JavaDoc # This also seems more idiomatic to include whitespace in import x.{ yyy } spaces.inImportCurlyBraces = true + +# This is more idiomatic Scala. +# http://docs.scala-lang.org/style/indentation.html#methods-with-numerous-arguments +align.openParenCallSite = false +align.openParenDefnSite = false From aac916b1193e2b4b7f578ad64802b2b99ea21fa2 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 4 Apr 2018 15:20:24 +0100 Subject: [PATCH 252/356] Improve code clarity with danglingParentheses = true --- .scalafmt.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.scalafmt.conf b/.scalafmt.conf index 2486945d0..a36334e44 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -13,3 +13,6 @@ spaces.inImportCurlyBraces = true # http://docs.scala-lang.org/style/indentation.html#methods-with-numerous-arguments align.openParenCallSite = false align.openParenDefnSite = false + +# For better code clarity +danglingParentheses = true From 8f4b8abb7bc80cf1a2836d20ab3b6cad35a979ac Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 24 Apr 2018 16:12:10 +0100 Subject: [PATCH 253/356] Run scalafmt & test:scalafmt --- .../internal/util/appmacro/ContextUtil.scala | 24 +- .../sbt/internal/util/appmacro/Convert.scala | 14 +- .../sbt/internal/util/appmacro/Instance.scala | 8 +- .../internal/util/appmacro/KListBuilder.scala | 22 +- .../internal/util/appmacro/MixedBuilder.scala | 5 +- .../internal/util/appmacro/TupleBuilder.scala | 5 +- .../util/appmacro/TupleNBuilder.scala | 20 +- .../main/scala/sbt/internal/util/AList.scala | 293 ++++-- .../scala/sbt/internal/util/Attributes.scala | 26 +- .../main/scala/sbt/internal/util/INode.scala | 6 +- .../scala/sbt/internal/util/Settings.scala | 14 +- .../scala/sbt/internal/util/LineReader.scala | 5 +- .../util/complete/JLineCompletion.scala | 3 +- .../sbt/internal/util/complete/Parser.scala | 16 +- .../sbt/internal/util/complete/Parsers.scala | 3 +- .../src/test/scala/DefaultParsersSpec.scala | 3 +- .../sbt/complete/ParserWithExamplesTest.scala | 9 +- main-actions/src/main/scala/sbt/Console.scala | 35 +- main-actions/src/main/scala/sbt/Doc.scala | 26 +- .../src/main/scala/sbt/DotGraph.scala | 46 +- .../src/main/scala/sbt/ForkTests.scala | 85 +- main-actions/src/main/scala/sbt/Package.scala | 26 +- .../src/main/scala/sbt/RawCompileLike.scala | 24 +- main-actions/src/main/scala/sbt/Sync.scala | 24 +- .../src/main/scala/sbt/TestResultLogger.scala | 61 +- main-actions/src/main/scala/sbt/Tests.scala | 171 ++-- .../src/main/scala/sbt/compiler/Eval.scala | 152 ++-- .../src/test/scala/sbt/CacheIvyTest.scala | 20 +- .../test/scala/sbt/compiler/EvalTest.scala | 12 +- .../main/scala/sbt/BasicCommandStrings.scala | 12 +- .../src/main/scala/sbt/BasicCommands.scala | 40 +- .../src/main/scala/sbt/BasicKeys.scala | 48 +- main-command/src/main/scala/sbt/Command.scala | 64 +- .../src/main/scala/sbt/MainControl.scala | 44 +- main-command/src/main/scala/sbt/State.scala | 5 +- main-command/src/main/scala/sbt/Watched.scala | 9 +- .../scala/sbt/internal/server/Server.scala | 11 +- .../sbt/internal/server/ServerHandler.scala | 12 +- main-settings/src/main/scala/sbt/Append.scala | 6 +- main-settings/src/main/scala/sbt/Def.scala | 38 +- .../src/main/scala/sbt/DelegateIndex.scala | 8 +- .../src/main/scala/sbt/InputTask.scala | 64 +- .../src/main/scala/sbt/Previous.scala | 14 +- main-settings/src/main/scala/sbt/Remove.scala | 6 +- main-settings/src/main/scala/sbt/Scope.scala | 89 +- .../src/main/scala/sbt/Structure.scala | 23 +- .../src/main/scala/sbt/std/InputWrapper.scala | 70 +- .../src/main/scala/sbt/std/KeyMacro.scala | 18 +- .../src/main/scala/sbt/std/SettingMacro.scala | 21 +- .../src/main/scala/sbt/std/TaskMacro.scala | 207 +++-- .../src/test/scala/sbt/SlashSyntaxSpec.scala | 6 +- .../main/scala/sbt/BackgroundJobService.scala | 6 +- main/src/main/scala/sbt/BuildPaths.scala | 31 +- main/src/main/scala/sbt/Cross.scala | 18 +- main/src/main/scala/sbt/Defaults.scala | 852 +++++++++++------- main/src/main/scala/sbt/EvaluateTask.scala | 149 +-- main/src/main/scala/sbt/Extracted.scala | 11 +- main/src/main/scala/sbt/Main.scala | 98 +- main/src/main/scala/sbt/MainLoop.scala | 3 +- main/src/main/scala/sbt/Opts.scala | 6 +- main/src/main/scala/sbt/PluginCross.scala | 10 +- main/src/main/scala/sbt/Plugins.scala | 33 +- main/src/main/scala/sbt/Project.scala | 99 +- main/src/main/scala/sbt/RichURI.scala | 16 +- main/src/main/scala/sbt/ScopeFilter.scala | 76 +- main/src/main/scala/sbt/ScopedKeyData.scala | 8 +- main/src/main/scala/sbt/ScriptedPlugin.scala | 3 +- main/src/main/scala/sbt/SessionVar.scala | 6 +- main/src/main/scala/sbt/TemplateCommand.scala | 29 +- main/src/main/scala/sbt/internal/Act.scala | 219 +++-- .../main/scala/sbt/internal/Aggregation.scala | 15 +- .../sbt/internal/BuildDependencies.scala | 12 +- .../main/scala/sbt/internal/BuildLoader.scala | 132 +-- .../scala/sbt/internal/BuildStructure.scala | 109 ++- .../main/scala/sbt/internal/BuildUtil.scala | 15 +- .../scala/sbt/internal/CommandExchange.scala | 3 +- .../scala/sbt/internal/CommandStrings.scala | 6 +- .../scala/sbt/internal/ConsoleProject.scala | 3 +- .../DefaultBackgroundJobService.scala | 24 +- .../sbt/internal/EvaluateConfigurations.scala | 164 ++-- .../scala/sbt/internal/GlobalPlugin.scala | 60 +- .../sbt/internal/GroupedAutoPlugins.scala | 6 +- .../main/scala/sbt/internal/IvyConsole.scala | 18 +- .../main/scala/sbt/internal/KeyIndex.scala | 112 ++- main/src/main/scala/sbt/internal/Load.scala | 59 +- .../main/scala/sbt/internal/LogManager.scala | 15 +- .../scala/sbt/internal/PluginDiscovery.scala | 32 +- .../scala/sbt/internal/PluginManagement.scala | 36 +- .../scala/sbt/internal/PluginsDebug.scala | 72 +- .../sbt/internal/ProjectNavigation.scala | 3 +- .../src/main/scala/sbt/internal/Resolve.scala | 13 +- main/src/main/scala/sbt/internal/Script.scala | 23 +- .../scala/sbt/internal/SessionSettings.scala | 40 +- .../sbt/internal/SettingCompletions.scala | 65 +- .../scala/sbt/internal/SettingGraph.scala | 24 +- .../scala/sbt/internal/TaskSequential.scala | 549 ++++++----- .../main/scala/sbt/internal/TaskTimings.scala | 10 +- .../scala/sbt/internal/parser/SbtParser.scala | 49 +- .../sbt/internal/parser/SbtRefactorings.scala | 12 +- .../sbt/internal/server/Definition.scala | 3 +- .../server/LanguageServerProtocol.scala | 23 +- .../sbt/internal/server/NetworkChannel.scala | 23 +- .../sbt/internal/server/SettingQuery.scala | 28 +- main/src/test/scala/DefaultsTest.scala | 24 +- main/src/test/scala/Delegates.scala | 3 +- main/src/test/scala/ParseKey.scala | 18 +- main/src/test/scala/PluginCommandTest.scala | 50 +- main/src/test/scala/PluginsTest.scala | 6 +- .../test/scala/sbt/internal/TestBuild.scala | 74 +- .../internal/parser/CheckIfParsedSpec.scala | 4 +- .../internal/parser/CommentedXmlSpec.scala | 20 +- .../sbt/internal/parser/EmbeddedXmlSpec.scala | 20 +- .../scala/sbt/internal/parser/ErrorSpec.scala | 3 +- .../internal/parser/SessionSettingsSpec.scala | 5 +- .../parser/SplitExpressionsBehavior.scala | 3 +- .../sbt/internal/server/DefinitionTest.scala | 15 +- .../internal/server/SettingQueryTest.scala | 32 +- .../JsonRpcNotificationMessageFormats.scala | 9 +- .../codec/JsonRpcRequestMessageFormats.scala | 12 +- .../codec/JsonRpcResponseErrorFormats.scala | 12 +- .../codec/JsonRpcResponseMessageFormats.scala | 9 +- run/src/main/scala/sbt/Fork.scala | 8 +- run/src/main/scala/sbt/Run.scala | 17 +- run/src/main/scala/sbt/SelectMainClass.scala | 6 +- run/src/main/scala/sbt/TrapExit.scala | 18 +- sbt/src/test/scala/sbt/ServerSpec.scala | 27 +- .../sbt/scriptedtest/ScriptedTests.scala | 3 +- .../src/main/scala/sbt/Action.scala | 6 +- .../src/main/scala/sbt/std/Streams.scala | 24 +- .../src/main/scala/sbt/std/System.scala | 5 +- .../src/main/scala/sbt/std/TaskExtra.scala | 42 +- tasks-standard/src/test/scala/TaskGen.scala | 8 +- .../src/test/scala/TaskSerial.scala | 32 +- .../main/scala/sbt/CompletionService.scala | 10 +- .../scala/sbt/ConcurrentRestrictions.scala | 17 +- tasks/src/main/scala/sbt/Execute.scala | 39 +- tasks/src/main/scala/sbt/Incomplete.scala | 13 +- .../scala/sbt/JUnitXmlTestsListener.scala | 3 +- .../src/main/scala/sbt/TestFramework.scala | 109 ++- 139 files changed, 3740 insertions(+), 2370 deletions(-) diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala index 7e627317d..aeea0683e 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala @@ -29,8 +29,9 @@ object ContextUtil { * Given `myImplicitConversion(someValue).extensionMethod`, where `extensionMethod` is a macro that uses this * method, the result of this method is `f()`. */ - def selectMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)( - f: (c.Expr[Any], c.Position) => c.Expr[T]): c.Expr[T] = { + def selectMacroImpl[T: c.WeakTypeTag]( + c: blackbox.Context + )(f: (c.Expr[Any], c.Position) => c.Expr[T]): c.Expr[T] = { import c.universe._ c.macroApplication match { case s @ Select(Apply(_, t :: Nil), _) => f(c.Expr[Any](t), s.pos) @@ -211,12 +212,14 @@ final class ContextUtil[C <: blackbox.Context](val ctx: C) { def changeOwner(tree: Tree, prev: Symbol, next: Symbol): Unit = new ChangeOwnerAndModuleClassTraverser( prev.asInstanceOf[global.Symbol], - next.asInstanceOf[global.Symbol]).traverse(tree.asInstanceOf[global.Tree]) + next.asInstanceOf[global.Symbol] + ).traverse(tree.asInstanceOf[global.Tree]) // Workaround copied from scala/async:can be removed once https://github.com/scala/scala/pull/3179 is merged. - private[this] class ChangeOwnerAndModuleClassTraverser(oldowner: global.Symbol, - newowner: global.Symbol) - extends global.ChangeOwnerTraverser(oldowner, newowner) { + private[this] class ChangeOwnerAndModuleClassTraverser( + oldowner: global.Symbol, + newowner: global.Symbol + ) extends global.ChangeOwnerTraverser(oldowner, newowner) { override def traverse(tree: global.Tree): Unit = { tree match { case _: global.DefTree => change(tree.symbol.moduleClass) @@ -248,7 +251,8 @@ final class ContextUtil[C <: blackbox.Context](val ctx: C) { * the type constructor `[x] List[x]`. */ def extractTC(tcp: AnyRef with Singleton, name: String)( - implicit it: ctx.TypeTag[tcp.type]): ctx.Type = { + implicit it: ctx.TypeTag[tcp.type] + ): ctx.Type = { val itTpe = it.tpe.asInstanceOf[global.Type] val m = itTpe.nonPrivateMember(global.newTypeName(name)) val tc = itTpe.memberInfo(m).asInstanceOf[ctx.universe.Type] @@ -262,8 +266,10 @@ final class ContextUtil[C <: blackbox.Context](val ctx: C) { * Typically, `f` is a `Select` or `Ident`. * The wrapper is replaced with the result of `subWrapper(, , )` */ - def transformWrappers(t: Tree, - subWrapper: (String, Type, Tree, Tree) => Converted[ctx.type]): Tree = { + def transformWrappers( + t: Tree, + subWrapper: (String, Type, Tree, Tree) => Converted[ctx.type] + ): Tree = { // the main tree transformer that replaces calls to InputWrapper.wrap(x) with // plain Idents that reference the actual input value object appTransformer extends Transformer { diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/Convert.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/Convert.scala index 2aae74f3b..36529ec6c 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/Convert.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/Convert.scala @@ -26,9 +26,10 @@ sealed trait Converted[C <: blackbox.Context with Singleton] { } object Converted { def NotApplicable[C <: blackbox.Context with Singleton] = new NotApplicable[C] - final case class Failure[C <: blackbox.Context with Singleton](position: C#Position, - message: String) - extends Converted[C] { + final case class Failure[C <: blackbox.Context with Singleton]( + position: C#Position, + message: String + ) extends Converted[C] { def isSuccess = false def transform(f: C#Tree => C#Tree): Converted[C] = new Failure(position, message) } @@ -36,9 +37,10 @@ object Converted { def isSuccess = false def transform(f: C#Tree => C#Tree): Converted[C] = this } - final case class Success[C <: blackbox.Context with Singleton](tree: C#Tree, - finalTransform: C#Tree => C#Tree) - extends Converted[C] { + final case class Success[C <: blackbox.Context with Singleton]( + tree: C#Tree, + finalTransform: C#Tree => C#Tree + ) extends Converted[C] { def isSuccess = true def transform(f: C#Tree => C#Tree): Converted[C] = Success(f(tree), finalTransform) } diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/Instance.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/Instance.scala index 33e614eab..a2d41f3e4 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/Instance.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/Instance.scala @@ -41,9 +41,11 @@ object Instance { final val MapName = "map" final val InstanceTCName = "M" - final class Input[U <: Universe with Singleton](val tpe: U#Type, - val expr: U#Tree, - val local: U#ValDef) + final class Input[U <: Universe with Singleton]( + val tpe: U#Type, + val expr: U#Tree, + val local: U#ValDef + ) trait Transform[C <: blackbox.Context with Singleton, N[_]] { def apply(in: C#Tree): C#Tree } diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/KListBuilder.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/KListBuilder.scala index 99a210a0c..148b772df 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/KListBuilder.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/KListBuilder.scala @@ -13,8 +13,9 @@ import macros._ /** A `TupleBuilder` that uses a KList as the tuple representation.*/ object KListBuilder extends TupleBuilder { - def make(c: blackbox.Context)(mt: c.Type, - inputs: Inputs[c.universe.type]): BuilderResult[c.type] = + def make( + c: blackbox.Context + )(mt: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type] = new BuilderResult[c.type] { val ctx: c.type = c val util = ContextUtil[c.type](c) @@ -47,15 +48,20 @@ object KListBuilder extends TupleBuilder { case Nil => revBindings.reverse } - private[this] def makeKList(revInputs: Inputs[c.universe.type], - klist: Tree, - klistType: Type): Tree = + private[this] def makeKList( + revInputs: Inputs[c.universe.type], + klist: Tree, + klistType: Type + ): Tree = revInputs match { case in :: tail => val next = ApplyTree( - TypeApply(Ident(kcons), - TypeTree(in.tpe) :: TypeTree(klistType) :: TypeTree(mTC) :: Nil), - in.expr :: klist :: Nil) + TypeApply( + Ident(kcons), + TypeTree(in.tpe) :: TypeTree(klistType) :: TypeTree(mTC) :: Nil + ), + in.expr :: klist :: Nil + ) makeKList(tail, next, appliedType(kconsTC, in.tpe :: klistType :: mTC :: Nil)) case Nil => klist } diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/MixedBuilder.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/MixedBuilder.scala index 472a0446b..990bcb6e5 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/MixedBuilder.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/MixedBuilder.scala @@ -16,8 +16,9 @@ import macros._ * and `KList` for larger numbers of inputs. This builder cannot handle fewer than 2 inputs. */ object MixedBuilder extends TupleBuilder { - def make(c: blackbox.Context)(mt: c.Type, - inputs: Inputs[c.universe.type]): BuilderResult[c.type] = { + def make( + c: blackbox.Context + )(mt: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type] = { val delegate = if (inputs.size > TupleNBuilder.MaxInputs) KListBuilder else TupleNBuilder delegate.make(c)(mt, inputs) } diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleBuilder.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleBuilder.scala index 0cd12ba44..1c67f9ed8 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleBuilder.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleBuilder.scala @@ -35,8 +35,9 @@ trait TupleBuilder { type Inputs[U <: Universe with Singleton] = List[Instance.Input[U]] /** Constructs a one-time use Builder for Context `c` and type constructor `tcType`. */ - def make(c: blackbox.Context)(tcType: c.Type, - inputs: Inputs[c.universe.type]): BuilderResult[c.type] + def make( + c: blackbox.Context + )(tcType: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type] } trait BuilderResult[C <: blackbox.Context with Singleton] { diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleNBuilder.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleNBuilder.scala index fab552157..6cf541e41 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleNBuilder.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleNBuilder.scala @@ -22,8 +22,9 @@ object TupleNBuilder extends TupleBuilder { final val MaxInputs = 11 final val TupleMethodName = "tuple" - def make(c: blackbox.Context)(mt: c.Type, - inputs: Inputs[c.universe.type]): BuilderResult[c.type] = + def make( + c: blackbox.Context + )(mt: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type] = new BuilderResult[c.type] { val util = ContextUtil[c.type](c) import c.universe._ @@ -34,8 +35,9 @@ object TupleNBuilder extends TupleBuilder { val ctx: c.type = c val representationC: PolyType = { val tcVariable: Symbol = newTCVariable(util.initialOwner) - val tupleTypeArgs = inputs.map(in => - internal.typeRef(NoPrefix, tcVariable, in.tpe :: Nil).asInstanceOf[global.Type]) + val tupleTypeArgs = inputs.map( + in => internal.typeRef(NoPrefix, tcVariable, in.tpe :: Nil).asInstanceOf[global.Type] + ) val tuple = global.definitions.tupleType(tupleTypeArgs) internal.polyType(tcVariable :: Nil, tuple.asInstanceOf[Type]) } @@ -47,10 +49,12 @@ object TupleNBuilder extends TupleBuilder { } def extract(param: ValDef): List[ValDef] = bindTuple(param, Nil, inputs.map(_.local), 1) - def bindTuple(param: ValDef, - revBindings: List[ValDef], - params: List[ValDef], - i: Int): List[ValDef] = + def bindTuple( + param: ValDef, + revBindings: List[ValDef], + params: List[ValDef], + i: Int + ): List[ValDef] = params match { case (x @ ValDef(mods, name, tpt, _)) :: xs => val rhs = select(Ident(param.name), "_" + i.toString) diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/AList.scala b/internal/util-collection/src/main/scala/sbt/internal/util/AList.scala index cc5343d2f..ba2379567 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/AList.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/AList.scala @@ -17,7 +17,9 @@ import Types._ */ trait AList[K[L[x]]] { def transform[M[_], N[_]](value: K[M], f: M ~> N): K[N] - def traverse[M[_], N[_], P[_]](value: K[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[K[P]] + def traverse[M[_], N[_], P[_]](value: K[M], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[K[P]] def foldr[M[_], A](value: K[M], f: (M[_], A) => A, init: A): A def toList[M[_]](value: K[M]): List[M[_]] = foldr[M, List[M[_]]](value, _ :: _, Nil) @@ -33,8 +35,11 @@ object AList { val empty: Empty = new Empty { def transform[M[_], N[_]](in: Unit, f: M ~> N) = () def foldr[M[_], T](in: Unit, f: (M[_], T) => T, init: T) = init - override def apply[M[_], C](in: Unit, f: Unit => C)(implicit app: Applicative[M]): M[C] = app.pure(f(())) - def traverse[M[_], N[_], P[_]](in: Unit, f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[Unit] = np.pure(()) + override def apply[M[_], C](in: Unit, f: Unit => C)(implicit app: Applicative[M]): M[C] = + app.pure(f(())) + def traverse[M[_], N[_], P[_]](in: Unit, f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[Unit] = np.pure(()) } type SeqList[T] = AList[λ[L[x] => List[L[T]]]] @@ -42,9 +47,12 @@ object AList { /** AList for a homogeneous sequence. */ def seq[T]: SeqList[T] = new SeqList[T] { def transform[M[_], N[_]](s: List[M[T]], f: M ~> N) = s.map(f.fn[T]) - def foldr[M[_], A](s: List[M[T]], f: (M[_], A) => A, init: A): A = (init /: s.reverse)((t, m) => f(m, t)) + def foldr[M[_], A](s: List[M[T]], f: (M[_], A) => A, init: A): A = + (init /: s.reverse)((t, m) => f(m, t)) - override def apply[M[_], C](s: List[M[T]], f: List[T] => C)(implicit ap: Applicative[M]): M[C] = { + override def apply[M[_], C](s: List[M[T]], f: List[T] => C)( + implicit ap: Applicative[M] + ): M[C] = { def loop[V](in: List[M[T]], g: List[T] => V): M[V] = in match { case Nil => ap.pure(g(Nil)) @@ -55,15 +63,20 @@ object AList { loop(s, f) } - def traverse[M[_], N[_], P[_]](s: List[M[T]], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[List[P[T]]] = ??? + def traverse[M[_], N[_], P[_]](s: List[M[T]], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[List[P[T]]] = ??? } /** AList for the arbitrary arity data structure KList. */ def klist[KL[M[_]] <: KList.Aux[M, KL]]: AList[KL] = new AList[KL] { def transform[M[_], N[_]](k: KL[M], f: M ~> N) = k.transform(f) def foldr[M[_], T](k: KL[M], f: (M[_], T) => T, init: T): T = k.foldr(f, init) - override def apply[M[_], C](k: KL[M], f: KL[Id] => C)(implicit app: Applicative[M]): M[C] = k.apply(f)(app) - def traverse[M[_], N[_], P[_]](k: KL[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[KL[P]] = k.traverse[N, P](f)(np) + override def apply[M[_], C](k: KL[M], f: KL[Id] => C)(implicit app: Applicative[M]): M[C] = + k.apply(f)(app) + def traverse[M[_], N[_], P[_]](k: KL[M], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[KL[P]] = k.traverse[N, P](f)(np) override def toList[M[_]](k: KL[M]) = k.toList } @@ -73,7 +86,9 @@ object AList { def single[A]: Single[A] = new Single[A] { def transform[M[_], N[_]](a: M[A], f: M ~> N) = f(a) def foldr[M[_], T](a: M[A], f: (M[_], T) => T, init: T): T = f(a, init) - def traverse[M[_], N[_], P[_]](a: M[A], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[P[A]] = f(a) + def traverse[M[_], N[_], P[_]](a: M[A], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[P[A]] = f(a) } type ASplit[K[L[x]], B[x]] = AList[λ[L[x] => K[(L ∙ B)#l]]] @@ -85,7 +100,9 @@ object AList { def transform[M[_], N[_]](value: Split[M], f: M ~> N): Split[N] = base.transform[(M ∙ B)#l, (N ∙ B)#l](value, nestCon[M, N, B](f)) - def traverse[M[_], N[_], P[_]](value: Split[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[Split[P]] = { + def traverse[M[_], N[_], P[_]](value: Split[M], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[Split[P]] = { val g = nestCon[M, (N ∙ P)#l, B](f) base.traverse[(M ∙ B)#l, N, (P ∙ B)#l](value, g)(np) } @@ -101,7 +118,9 @@ object AList { type T2[M[_]] = (M[A], M[B]) def transform[M[_], N[_]](t: T2[M], f: M ~> N): T2[N] = (f(t._1), f(t._2)) def foldr[M[_], T](t: T2[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, init)) - def traverse[M[_], N[_], P[_]](t: T2[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[T2[P]] = { + def traverse[M[_], N[_], P[_]](t: T2[M], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[T2[P]] = { val g = (Tuple2.apply[P[A], P[B]] _).curried np.apply(np.map(g, f(t._1)), f(t._2)) } @@ -113,7 +132,9 @@ object AList { type T3[M[_]] = (M[A], M[B], M[C]) def transform[M[_], N[_]](t: T3[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3)) def foldr[M[_], T](t: T3[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, f(t._3, init))) - def traverse[M[_], N[_], P[_]](t: T3[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[T3[P]] = { + def traverse[M[_], N[_], P[_]](t: T3[M], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[T3[P]] = { val g = (Tuple3.apply[P[A], P[B], P[C]] _).curried np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)) } @@ -124,8 +145,11 @@ object AList { def tuple4[A, B, C, D]: T4List[A, B, C, D] = new T4List[A, B, C, D] { type T4[M[_]] = (M[A], M[B], M[C], M[D]) def transform[M[_], N[_]](t: T4[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3), f(t._4)) - def foldr[M[_], T](t: T4[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, f(t._3, f(t._4, init)))) - def traverse[M[_], N[_], P[_]](t: T4[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[T4[P]] = { + def foldr[M[_], T](t: T4[M], f: (M[_], T) => T, init: T): T = + f(t._1, f(t._2, f(t._3, f(t._4, init)))) + def traverse[M[_], N[_], P[_]](t: T4[M], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[T4[P]] = { val g = (Tuple4.apply[P[A], P[B], P[C], P[D]] _).curried np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)) } @@ -136,8 +160,11 @@ object AList { def tuple5[A, B, C, D, E]: T5List[A, B, C, D, E] = new T5List[A, B, C, D, E] { type T5[M[_]] = (M[A], M[B], M[C], M[D], M[E]) def transform[M[_], N[_]](t: T5[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5)) - def foldr[M[_], T](t: T5[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, init))))) - def traverse[M[_], N[_], P[_]](t: T5[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[T5[P]] = { + def foldr[M[_], T](t: T5[M], f: (M[_], T) => T, init: T): T = + f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, init))))) + def traverse[M[_], N[_], P[_]](t: T5[M], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[T5[P]] = { val g = (Tuple5.apply[P[A], P[B], P[C], P[D], P[E]] _).curried np.apply(np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), f(t._5)) } @@ -147,71 +174,213 @@ object AList { type T6List[A, B, C, D, E, F] = AList[T6K[A, B, C, D, E, F]#l] def tuple6[A, B, C, D, E, F]: T6List[A, B, C, D, E, F] = new T6List[A, B, C, D, E, F] { type T6[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F]) - def transform[M[_], N[_]](t: T6[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6)) - def foldr[M[_], T](t: T6[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, init)))))) - def traverse[M[_], N[_], P[_]](t: T6[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[T6[P]] = { + def transform[M[_], N[_]](t: T6[M], f: M ~> N) = + (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6)) + def foldr[M[_], T](t: T6[M], f: (M[_], T) => T, init: T): T = + f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, init)))))) + def traverse[M[_], N[_], P[_]](t: T6[M], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[T6[P]] = { val g = (Tuple6.apply[P[A], P[B], P[C], P[D], P[E], P[F]] _).curried - np.apply(np.apply(np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), f(t._5)), f(t._6)) + np.apply( + np.apply( + np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), + f(t._5) + ), + f(t._6) + ) } } - sealed trait T7K[A, B, C, D, E, F, G] { type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G]) } + sealed trait T7K[A, B, C, D, E, F, G] { + type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G]) + } type T7List[A, B, C, D, E, F, G] = AList[T7K[A, B, C, D, E, F, G]#l] def tuple7[A, B, C, D, E, F, G]: T7List[A, B, C, D, E, F, G] = new T7List[A, B, C, D, E, F, G] { type T7[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G]) - def transform[M[_], N[_]](t: T7[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7)) - def foldr[M[_], T](t: T7[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, init))))))) - def traverse[M[_], N[_], P[_]](t: T7[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[T7[P]] = { + def transform[M[_], N[_]](t: T7[M], f: M ~> N) = + (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7)) + def foldr[M[_], T](t: T7[M], f: (M[_], T) => T, init: T): T = + f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, init))))))) + def traverse[M[_], N[_], P[_]](t: T7[M], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[T7[P]] = { val g = (Tuple7.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G]] _).curried - np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), f(t._5)), f(t._6)), f(t._7)) + np.apply( + np.apply( + np.apply( + np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), + f(t._5) + ), + f(t._6) + ), + f(t._7) + ) } } - sealed trait T8K[A, B, C, D, E, F, G, H] { type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H]) } + sealed trait T8K[A, B, C, D, E, F, G, H] { + type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H]) + } type T8List[A, B, C, D, E, F, G, H] = AList[T8K[A, B, C, D, E, F, G, H]#l] - def tuple8[A, B, C, D, E, F, G, H]: T8List[A, B, C, D, E, F, G, H] = new T8List[A, B, C, D, E, F, G, H] { - type T8[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H]) - def transform[M[_], N[_]](t: T8[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7), f(t._8)) - def foldr[M[_], T](t: T8[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, init)))))))) - def traverse[M[_], N[_], P[_]](t: T8[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[T8[P]] = { - val g = (Tuple8.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H]] _).curried - np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), f(t._5)), f(t._6)), f(t._7)), f(t._8)) + def tuple8[A, B, C, D, E, F, G, H]: T8List[A, B, C, D, E, F, G, H] = + new T8List[A, B, C, D, E, F, G, H] { + type T8[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H]) + def transform[M[_], N[_]](t: T8[M], f: M ~> N) = + (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7), f(t._8)) + def foldr[M[_], T](t: T8[M], f: (M[_], T) => T, init: T): T = + f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, init)))))))) + def traverse[M[_], N[_], P[_]](t: T8[M], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[T8[P]] = { + val g = (Tuple8.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H]] _).curried + np.apply( + np.apply( + np.apply( + np.apply( + np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), + f(t._5) + ), + f(t._6) + ), + f(t._7) + ), + f(t._8) + ) + } } - } - sealed trait T9K[A, B, C, D, E, F, G, H, I] { type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H], L[I]) } + sealed trait T9K[A, B, C, D, E, F, G, H, I] { + type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H], L[I]) + } type T9List[A, B, C, D, E, F, G, H, I] = AList[T9K[A, B, C, D, E, F, G, H, I]#l] - def tuple9[A, B, C, D, E, F, G, H, I]: T9List[A, B, C, D, E, F, G, H, I] = new T9List[A, B, C, D, E, F, G, H, I] { - type T9[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H], M[I]) - def transform[M[_], N[_]](t: T9[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7), f(t._8), f(t._9)) - def foldr[M[_], T](t: T9[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, f(t._9, init))))))))) - def traverse[M[_], N[_], P[_]](t: T9[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[T9[P]] = { - val g = (Tuple9.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H], P[I]] _).curried - np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), f(t._5)), f(t._6)), f(t._7)), f(t._8)), f(t._9)) + def tuple9[A, B, C, D, E, F, G, H, I]: T9List[A, B, C, D, E, F, G, H, I] = + new T9List[A, B, C, D, E, F, G, H, I] { + type T9[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H], M[I]) + def transform[M[_], N[_]](t: T9[M], f: M ~> N) = + (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7), f(t._8), f(t._9)) + def foldr[M[_], T](t: T9[M], f: (M[_], T) => T, init: T): T = + f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, f(t._9, init))))))))) + def traverse[M[_], N[_], P[_]](t: T9[M], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[T9[P]] = { + val g = (Tuple9.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H], P[I]] _).curried + np.apply( + np.apply( + np.apply( + np.apply( + np.apply( + np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), + f(t._5) + ), + f(t._6) + ), + f(t._7) + ), + f(t._8) + ), + f(t._9) + ) + } } - } - sealed trait T10K[A, B, C, D, E, F, G, H, I, J] { type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H], L[I], L[J]) } + sealed trait T10K[A, B, C, D, E, F, G, H, I, J] { + type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H], L[I], L[J]) + } type T10List[A, B, C, D, E, F, G, H, I, J] = AList[T10K[A, B, C, D, E, F, G, H, I, J]#l] - def tuple10[A, B, C, D, E, F, G, H, I, J]: T10List[A, B, C, D, E, F, G, H, I, J] = new T10List[A, B, C, D, E, F, G, H, I, J] { - type T10[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H], M[I], M[J]) - def transform[M[_], N[_]](t: T10[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7), f(t._8), f(t._9), f(t._10)) - def foldr[M[_], T](t: T10[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, f(t._9, f(t._10, init)))))))))) - def traverse[M[_], N[_], P[_]](t: T10[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[T10[P]] = { - val g = (Tuple10.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H], P[I], P[J]] _).curried - np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), f(t._5)), f(t._6)), f(t._7)), f(t._8)), f(t._9)), f(t._10)) + def tuple10[A, B, C, D, E, F, G, H, I, J]: T10List[A, B, C, D, E, F, G, H, I, J] = + new T10List[A, B, C, D, E, F, G, H, I, J] { + type T10[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H], M[I], M[J]) + def transform[M[_], N[_]](t: T10[M], f: M ~> N) = + (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7), f(t._8), f(t._9), f(t._10)) + def foldr[M[_], T](t: T10[M], f: (M[_], T) => T, init: T): T = + f( + t._1, + f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, f(t._9, f(t._10, init))))))))) + ) + def traverse[M[_], N[_], P[_]](t: T10[M], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[T10[P]] = { + val g = + (Tuple10.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H], P[I], P[J]] _).curried + np.apply( + np.apply( + np.apply( + np.apply( + np.apply( + np.apply( + np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), + f(t._5) + ), + f(t._6) + ), + f(t._7) + ), + f(t._8) + ), + f(t._9) + ), + f(t._10) + ) + } } - } - sealed trait T11K[A, B, C, D, E, F, G, H, I, J, K] { type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H], L[I], L[J], L[K]) } - type T11List[A, B, C, D, E, F, G, H, I, J, K] = AList[T11K[A, B, C, D, E, F, G, H, I, J, K]#l] - def tuple11[A, B, C, D, E, F, G, H, I, J, K]: T11List[A, B, C, D, E, F, G, H, I, J, K] = new T11List[A, B, C, D, E, F, G, H, I, J, K] { - type T11[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H], M[I], M[J], M[K]) - def transform[M[_], N[_]](t: T11[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7), f(t._8), f(t._9), f(t._10), f(t._11)) - def foldr[M[_], T](t: T11[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, f(t._9, f(t._10, f(t._11, init))))))))))) - def traverse[M[_], N[_], P[_]](t: T11[M], f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[T11[P]] = { - val g = (Tuple11.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H], P[I], P[J], P[K]] _).curried - np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), f(t._5)), f(t._6)), f(t._7)), f(t._8)), f(t._9)), f(t._10)), f(t._11)) - } + sealed trait T11K[A, B, C, D, E, F, G, H, I, J, K] { + type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H], L[I], L[J], L[K]) } + type T11List[A, B, C, D, E, F, G, H, I, J, K] = AList[T11K[A, B, C, D, E, F, G, H, I, J, K]#l] + def tuple11[A, B, C, D, E, F, G, H, I, J, K]: T11List[A, B, C, D, E, F, G, H, I, J, K] = + new T11List[A, B, C, D, E, F, G, H, I, J, K] { + type T11[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H], M[I], M[J], M[K]) + def transform[M[_], N[_]](t: T11[M], f: M ~> N) = + ( + f(t._1), + f(t._2), + f(t._3), + f(t._4), + f(t._5), + f(t._6), + f(t._7), + f(t._8), + f(t._9), + f(t._10), + f(t._11) + ) + def foldr[M[_], T](t: T11[M], f: (M[_], T) => T, init: T): T = + f( + t._1, + f( + t._2, + f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, f(t._9, f(t._10, f(t._11, init))))))))) + ) + ) + def traverse[M[_], N[_], P[_]](t: T11[M], f: M ~> (N ∙ P)#l)( + implicit np: Applicative[N] + ): N[T11[P]] = { + val g = (Tuple11 + .apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H], P[I], P[J], P[K]] _).curried + np.apply( + np.apply( + np.apply( + np.apply( + np.apply( + np.apply( + np.apply( + np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), + f(t._5) + ), + f(t._6) + ), + f(t._7) + ), + f(t._8) + ), + f(t._9) + ), + f(t._10) + ), + f(t._11) + ) + } + } } diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/Attributes.scala b/internal/util-collection/src/main/scala/sbt/internal/util/Attributes.scala index 3e4832f1e..b45eeb4a1 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/Attributes.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/Attributes.scala @@ -71,20 +71,26 @@ object AttributeKey { def apply[T: Manifest: OptJsonWriter](name: String, description: String): AttributeKey[T] = apply(name, description, Nil) - def apply[T: Manifest: OptJsonWriter](name: String, - description: String, - rank: Int): AttributeKey[T] = + def apply[T: Manifest: OptJsonWriter]( + name: String, + description: String, + rank: Int + ): AttributeKey[T] = apply(name, description, Nil, rank) - def apply[T: Manifest: OptJsonWriter](name: String, - description: String, - extend: Seq[AttributeKey[_]]): AttributeKey[T] = + def apply[T: Manifest: OptJsonWriter]( + name: String, + description: String, + extend: Seq[AttributeKey[_]] + ): AttributeKey[T] = apply(name, description, extend, Int.MaxValue) - def apply[T: Manifest: OptJsonWriter](name: String, - description: String, - extend: Seq[AttributeKey[_]], - rank: Int): AttributeKey[T] = + def apply[T: Manifest: OptJsonWriter]( + name: String, + description: String, + extend: Seq[AttributeKey[_]], + rank: Int + ): AttributeKey[T] = make(name, Some(description), extend, rank) private[sbt] def copyWithRank[T](a: AttributeKey[T], rank: Int): AttributeKey[T] = diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/INode.scala b/internal/util-collection/src/main/scala/sbt/internal/util/INode.scala index 939bd9576..2090e0139 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/INode.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/INode.scala @@ -170,8 +170,10 @@ abstract class EvaluateSettings[Scope] { } protected final def setValue(v: T): Unit = { - assert(state != Evaluated, - "Already evaluated (trying to set value to " + v + "): " + toString) + assert( + state != Evaluated, + "Already evaluated (trying to set value to " + v + "): " + toString + ) if (v == null) sys.error("Setting value cannot be null: " + keyString) value = v state = Evaluated diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/Settings.scala b/internal/util-collection/src/main/scala/sbt/internal/util/Settings.scala index 910e1c089..c080fcbe3 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/Settings.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/Settings.scala @@ -357,7 +357,8 @@ trait Init[Scope] { keys.map(u => showUndefined(u, validKeys, delegates)).mkString("\n\n ", "\n\n ", "") new Uninitialized( keys, - prefix + suffix + " to undefined setting" + suffix + ": " + keysString + "\n ") + prefix + suffix + " to undefined setting" + suffix + ": " + keysString + "\n " + ) } final class Compiled[T]( @@ -374,8 +375,9 @@ trait Init[Scope] { val locals = compiled flatMap { case (key, comp) => if (key.key.isLocal) Seq[Compiled[_]](comp) else Nil } - val ordered = Dag.topologicalSort(locals)(_.dependencies.flatMap(dep => - if (dep.key.isLocal) Seq[Compiled[_]](compiled(dep)) else Nil)) + val ordered = Dag.topologicalSort(locals)( + _.dependencies.flatMap(dep => if (dep.key.isLocal) Seq[Compiled[_]](compiled(dep)) else Nil) + ) def flatten( cmap: Map[ScopedKey[_], Flattened], key: ScopedKey[_], @@ -383,7 +385,8 @@ trait Init[Scope] { ): Flattened = new Flattened( key, - deps.flatMap(dep => if (dep.key.isLocal) cmap(dep).dependencies else dep :: Nil)) + deps.flatMap(dep => if (dep.key.isLocal) cmap(dep).dependencies else dep :: Nil) + ) val empty = Map.empty[ScopedKey[_], Flattened] @@ -415,7 +418,8 @@ trait Init[Scope] { * Intersects two scopes, returning the more specific one if they intersect, or None otherwise. */ private[sbt] def intersect(s1: Scope, s2: Scope)( - implicit delegates: Scope => Seq[Scope]): Option[Scope] = + implicit delegates: Scope => Seq[Scope] + ): Option[Scope] = if (delegates(s1).contains(s2)) Some(s1) // s1 is more specific else if (delegates(s2).contains(s1)) Some(s2) // s2 is more specific else None diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala index 406ee5e97..e67ff915c 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala @@ -49,8 +49,9 @@ abstract class JLine extends LineReader { private[this] def readLineDirect(prompt: String, mask: Option[Char]): Option[String] = if (handleCONT) - Signals.withHandler(() => resume(), signal = Signals.CONT)(() => - readLineDirectRaw(prompt, mask)) + Signals.withHandler(() => resume(), signal = Signals.CONT)( + () => readLineDirectRaw(prompt, mask) + ) else readLineDirectRaw(prompt, mask) diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/complete/JLineCompletion.scala b/internal/util-complete/src/main/scala/sbt/internal/util/complete/JLineCompletion.scala index 700dafa35..e0a44deb2 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/complete/JLineCompletion.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/complete/JLineCompletion.scala @@ -91,7 +91,8 @@ object JLineCompletion { def appendNonEmpty(set: Set[String], add: String) = if (add.trim.isEmpty) set else set + add def customCompletor( - f: (String, Int) => (Seq[String], Seq[String])): (ConsoleReader, Int) => Boolean = + f: (String, Int) => (Seq[String], Seq[String]) + ): (ConsoleReader, Int) => Boolean = (reader, level) => { val success = complete(beforeCursor(reader), reader => f(reader, level), reader) reader.flush() diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parser.scala b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parser.scala index 94417e1c4..09bf9a8a3 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parser.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parser.scala @@ -275,8 +275,10 @@ object Parser extends ParserMain { revAcc: List[T] ): Parser[Seq[T]] = { assume(min >= 0, "Minimum must be greater than or equal to zero (was " + min + ")") - assume(max >= min, - "Minimum must be less than or equal to maximum (min: " + min + ", max: " + max + ")") + assume( + max >= min, + "Minimum must be less than or equal to maximum (min: " + min + ", max: " + max + ")" + ) def checkRepeated(invalidButOptional: => Parser[Seq[T]]): Parser[Seq[T]] = repeated match { @@ -836,10 +838,12 @@ private final class ParserWithExamples[T]( ) extends ValidParser[T] { def derive(c: Char) = - examples(delegate derive c, - exampleSource.withAddedPrefix(c.toString), - maxNumberOfExamples, - removeInvalidExamples) + examples( + delegate derive c, + exampleSource.withAddedPrefix(c.toString), + maxNumberOfExamples, + removeInvalidExamples + ) def result = delegate.result diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala index 1120ed173..ce444e229 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala @@ -44,7 +44,8 @@ trait Parsers { /** Parses a single hexadecimal digit (0-9, a-f, A-F). */ lazy val HexDigit = charClass(c => HexDigitSet(c.toUpper), "hex digit") examples HexDigitSet.map( - _.toString) + _.toString + ) /** Parses a single letter, according to Char.isLetter, into a Char. */ lazy val Letter = charClass(_.isLetter, "letter") diff --git a/internal/util-complete/src/test/scala/DefaultParsersSpec.scala b/internal/util-complete/src/test/scala/DefaultParsersSpec.scala index 253a4216b..b62ff8566 100644 --- a/internal/util-complete/src/test/scala/DefaultParsersSpec.scala +++ b/internal/util-complete/src/test/scala/DefaultParsersSpec.scala @@ -14,7 +14,8 @@ object DefaultParsersSpec extends Properties("DefaultParsers") { import DefaultParsers.{ ID, isIDChar, matches, validID } property("∀ s ∈ String: validID(s) == matches(ID, s)") = forAll( - (s: String) => validID(s) == matches(ID, s)) + (s: String) => validID(s) == matches(ID, s) + ) property("∀ s ∈ genID: matches(ID, s)") = forAll(genID)(s => matches(ID, s)) property("∀ s ∈ genID: validID(s)") = forAll(genID)(s => validID(s)) diff --git a/internal/util-complete/src/test/scala/sbt/complete/ParserWithExamplesTest.scala b/internal/util-complete/src/test/scala/sbt/complete/ParserWithExamplesTest.scala index e142e4a66..c2e7c314b 100644 --- a/internal/util-complete/src/test/scala/sbt/complete/ParserWithExamplesTest.scala +++ b/internal/util-complete/src/test/scala/sbt/complete/ParserWithExamplesTest.scala @@ -27,7 +27,8 @@ class ParserWithExamplesTest extends UnitSpec { Set( suggestion("blue"), suggestion("red") - )) + ) + ) parserWithExamples.completions(0) shouldEqual validCompletions } } @@ -38,7 +39,8 @@ class ParserWithExamplesTest extends UnitSpec { val derivedCompletions = Completions( Set( suggestion("lue") - )) + ) + ) parserWithExamples.derive('b').completions(0) shouldEqual derivedCompletions } } @@ -58,7 +60,8 @@ class ParserWithExamplesTest extends UnitSpec { Set( suggestion("lue"), suggestion("lock") - )) + ) + ) parserWithExamples.derive('b').completions(0) shouldEqual derivedCompletions } } diff --git a/main-actions/src/main/scala/sbt/Console.scala b/main-actions/src/main/scala/sbt/Console.scala index c2792d595..0e9d6e313 100644 --- a/main-actions/src/main/scala/sbt/Console.scala +++ b/main-actions/src/main/scala/sbt/Console.scala @@ -20,25 +20,30 @@ final class Console(compiler: AnalyzingCompiler) { def apply(classpath: Seq[File], log: Logger): Try[Unit] = apply(classpath, Nil, "", "", log) - def apply(classpath: Seq[File], - options: Seq[String], - initialCommands: String, - cleanupCommands: String, - log: Logger): Try[Unit] = + def apply( + classpath: Seq[File], + options: Seq[String], + initialCommands: String, + cleanupCommands: String, + log: Logger + ): Try[Unit] = apply(classpath, options, initialCommands, cleanupCommands)(None, Nil)(log) - def apply(classpath: Seq[File], - options: Seq[String], - loader: ClassLoader, - initialCommands: String, - cleanupCommands: String)(bindings: (String, Any)*)(implicit log: Logger): Try[Unit] = + def apply( + classpath: Seq[File], + options: Seq[String], + loader: ClassLoader, + initialCommands: String, + cleanupCommands: String + )(bindings: (String, Any)*)(implicit log: Logger): Try[Unit] = apply(classpath, options, initialCommands, cleanupCommands)(Some(loader), bindings) - def apply(classpath: Seq[File], - options: Seq[String], - initialCommands: String, - cleanupCommands: String)(loader: Option[ClassLoader], bindings: Seq[(String, Any)])( - implicit log: Logger): Try[Unit] = { + def apply( + classpath: Seq[File], + options: Seq[String], + initialCommands: String, + cleanupCommands: String + )(loader: Option[ClassLoader], bindings: Seq[(String, Any)])(implicit log: Logger): Try[Unit] = { def console0() = compiler.console(classpath, options, initialCommands, cleanupCommands, log)(loader, bindings) // TODO: Fix JLine diff --git a/main-actions/src/main/scala/sbt/Doc.scala b/main-actions/src/main/scala/sbt/Doc.scala index 12df12c9e..93eafb1e6 100644 --- a/main-actions/src/main/scala/sbt/Doc.scala +++ b/main-actions/src/main/scala/sbt/Doc.scala @@ -20,18 +20,24 @@ import sbt.internal.util.ManagedLogger object Doc { import RawCompileLike._ - def scaladoc(label: String, - cacheStoreFactory: CacheStoreFactory, - compiler: AnalyzingCompiler): Gen = + def scaladoc( + label: String, + cacheStoreFactory: CacheStoreFactory, + compiler: AnalyzingCompiler + ): Gen = scaladoc(label, cacheStoreFactory, compiler, Seq()) - def scaladoc(label: String, - cacheStoreFactory: CacheStoreFactory, - compiler: AnalyzingCompiler, - fileInputOptions: Seq[String]): Gen = - cached(cacheStoreFactory, - fileInputOptions, - prepare(label + " Scala API documentation", compiler.doc)) + def scaladoc( + label: String, + cacheStoreFactory: CacheStoreFactory, + compiler: AnalyzingCompiler, + fileInputOptions: Seq[String] + ): Gen = + cached( + cacheStoreFactory, + fileInputOptions, + prepare(label + " Scala API documentation", compiler.doc) + ) @deprecated("Going away", "1.1.1") def javadoc( diff --git a/main-actions/src/main/scala/sbt/DotGraph.scala b/main-actions/src/main/scala/sbt/DotGraph.scala index d0be921be..c3543770a 100644 --- a/main-actions/src/main/scala/sbt/DotGraph.scala +++ b/main-actions/src/main/scala/sbt/DotGraph.scala @@ -30,29 +30,37 @@ object DotGraph { val toString = packageOnly compose fToString(sourceRoots) apply(relations, outputDirectory, toString, toString) } - def apply(relations: Relations, - outputDir: File, - sourceToString: File => String, - externalToString: File => String): Unit = { + def apply( + relations: Relations, + outputDir: File, + sourceToString: File => String, + externalToString: File => String + ): Unit = { def file(name: String) = new File(outputDir, name) IO.createDirectory(outputDir) - generateGraph(file("int-class-deps"), - "dependencies", - relations.internalClassDep, - identity[String], - identity[String]) - generateGraph(file("binary-dependencies"), - "externalDependencies", - relations.libraryDep, - externalToString, - sourceToString) + generateGraph( + file("int-class-deps"), + "dependencies", + relations.internalClassDep, + identity[String], + identity[String] + ) + generateGraph( + file("binary-dependencies"), + "externalDependencies", + relations.libraryDep, + externalToString, + sourceToString + ) } - def generateGraph[K, V](file: File, - graphName: String, - relation: Relation[K, V], - keyToString: K => String, - valueToString: V => String): Unit = { + def generateGraph[K, V]( + file: File, + graphName: String, + relation: Relation[K, V], + keyToString: K => String, + valueToString: V => String + ): Unit = { import scala.collection.mutable.{ HashMap, HashSet } val mappedGraph = new HashMap[String, HashSet[String]] for ((key, values) <- relation.forwardMap; keyString = keyToString(key); value <- values) diff --git a/main-actions/src/main/scala/sbt/ForkTests.scala b/main-actions/src/main/scala/sbt/ForkTests.scala index c6eb96a1e..4299f54ac 100755 --- a/main-actions/src/main/scala/sbt/ForkTests.scala +++ b/main-actions/src/main/scala/sbt/ForkTests.scala @@ -20,13 +20,15 @@ import sbt.protocol.testing._ import sbt.internal.util.ConsoleAppender private[sbt] object ForkTests { - def apply(runners: Map[TestFramework, Runner], - tests: Vector[TestDefinition], - config: Execution, - classpath: Seq[File], - fork: ForkOptions, - log: Logger, - tag: Tag): Task[TestOutput] = { + def apply( + runners: Map[TestFramework, Runner], + tests: Vector[TestDefinition], + config: Execution, + classpath: Seq[File], + fork: ForkOptions, + log: Logger, + tag: Tag + ): Task[TestOutput] = { val opts = processOptions(config, tests, log) import std.TaskExtra._ @@ -43,12 +45,14 @@ private[sbt] object ForkTests { } } - private[this] def mainTestTask(runners: Map[TestFramework, Runner], - opts: ProcessedOptions, - classpath: Seq[File], - fork: ForkOptions, - log: Logger, - parallel: Boolean): Task[TestOutput] = + private[this] def mainTestTask( + runners: Map[TestFramework, Runner], + opts: ProcessedOptions, + classpath: Seq[File], + fork: ForkOptions, + log: Logger, + parallel: Boolean + ): Task[TestOutput] = std.TaskExtra.task { val server = new ServerSocket(0) val testListeners = opts.testListeners flatMap { @@ -68,7 +72,8 @@ private[sbt] object ForkTests { } catch { case e: java.net.SocketException => log.error( - "Could not accept connection from test agent: " + e.getClass + ": " + e.getMessage) + "Could not accept connection from test agent: " + e.getClass + ": " + e.getMessage + ) log.trace(e) server.close() return @@ -84,10 +89,13 @@ private[sbt] object ForkTests { val taskdefs = opts.tests.map( t => - new TaskDef(t.name, - forkFingerprint(t.fingerprint), - t.explicitlySpecified, - t.selectors)) + new TaskDef( + t.name, + forkFingerprint(t.fingerprint), + t.explicitlySpecified, + t.selectors + ) + ) os.writeObject(taskdefs.toArray) os.writeInt(runners.size) @@ -117,20 +125,27 @@ private[sbt] object ForkTests { val acceptorThread = new Thread(Acceptor) acceptorThread.start() - val fullCp = classpath ++: Seq(IO.classLocationFile[ForkMain], - IO.classLocationFile[Framework]) - val options = Seq("-classpath", - fullCp mkString File.pathSeparator, - classOf[ForkMain].getCanonicalName, - server.getLocalPort.toString) + val fullCp = classpath ++: Seq( + IO.classLocationFile[ForkMain], + IO.classLocationFile[Framework] + ) + val options = Seq( + "-classpath", + fullCp mkString File.pathSeparator, + classOf[ForkMain].getCanonicalName, + server.getLocalPort.toString + ) val ec = Fork.java(fork, options) val result = if (ec != 0) - TestOutput(TestResult.Error, - Map( - "Running java with options " + options - .mkString(" ") + " failed with exit code " + ec -> SuiteResult.Error), - Iterable.empty) + TestOutput( + TestResult.Error, + Map( + "Running java with options " + options + .mkString(" ") + " failed with exit code " + ec -> SuiteResult.Error + ), + Iterable.empty + ) else { // Need to wait acceptor thread to finish its business acceptorThread.join() @@ -151,11 +166,13 @@ private[sbt] object ForkTests { case _ => sys.error("Unknown fingerprint type: " + f.getClass) } } -private final class React(is: ObjectInputStream, - os: ObjectOutputStream, - log: Logger, - listeners: Seq[TestReportListener], - results: mutable.Map[String, SuiteResult]) { +private final class React( + is: ObjectInputStream, + os: ObjectOutputStream, + log: Logger, + listeners: Seq[TestReportListener], + results: mutable.Map[String, SuiteResult] +) { import ForkTags._ @annotation.tailrec def react(): Unit = is.readObject match { diff --git a/main-actions/src/main/scala/sbt/Package.scala b/main-actions/src/main/scala/sbt/Package.scala index 82ab1072f..8ecb1ccf7 100644 --- a/main-actions/src/main/scala/sbt/Package.scala +++ b/main-actions/src/main/scala/sbt/Package.scala @@ -49,9 +49,11 @@ object Package { } } - final class Configuration(val sources: Seq[(File, String)], - val jar: File, - val options: Seq[PackageOption]) + final class Configuration( + val sources: Seq[(File, String)], + val jar: File, + val options: Seq[PackageOption] + ) def apply(conf: Configuration, cacheStoreFactory: CacheStoreFactory, log: Logger): Unit = { val manifest = new Manifest val main = manifest.getMainAttributes @@ -66,8 +68,10 @@ object Package { setVersion(main) val cachedMakeJar = inputChanged(cacheStoreFactory make "inputs") { - (inChanged, - inputs: Map[File, String] :+: FilesInfo[ModifiedFileInfo] :+: Manifest :+: HNil) => + ( + inChanged, + inputs: Map[File, String] :+: FilesInfo[ModifiedFileInfo] :+: Manifest :+: HNil + ) => import exists.format val sources :+: _ :+: manifest :+: HNil = inputs inputChanged(cacheStoreFactory make "output") { (outChanged, jar: PlainFileInfo) => @@ -95,11 +99,13 @@ object Package { val attribVals = Seq(name, version, orgName) ManifestAttributes(attribKeys zip attribVals: _*) } - def addImplManifestAttributes(name: String, - version: String, - homepage: Option[java.net.URL], - org: String, - orgName: String): PackageOption = { + def addImplManifestAttributes( + name: String, + version: String, + homepage: Option[java.net.URL], + org: String, + orgName: String + ): PackageOption = { import Attributes.Name._ // The ones in Attributes.Name are deprecated saying: diff --git a/main-actions/src/main/scala/sbt/RawCompileLike.scala b/main-actions/src/main/scala/sbt/RawCompileLike.scala index 56fc1b551..cc4c0fe80 100644 --- a/main-actions/src/main/scala/sbt/RawCompileLike.scala +++ b/main-actions/src/main/scala/sbt/RawCompileLike.scala @@ -47,16 +47,20 @@ object RawCompileLike { def cached(cacheStoreFactory: CacheStoreFactory, doCompile: Gen): Gen = cached(cacheStoreFactory, Seq(), doCompile) - def cached(cacheStoreFactory: CacheStoreFactory, - fileInputOpts: Seq[String], - doCompile: Gen): Gen = + def cached( + cacheStoreFactory: CacheStoreFactory, + fileInputOpts: Seq[String], + doCompile: Gen + ): Gen = (sources, classpath, outputDirectory, options, maxErrors, log) => { type Inputs = FilesInfo[HashFileInfo] :+: FilesInfo[ModifiedFileInfo] :+: Seq[File] :+: File :+: Seq[ - String] :+: Int :+: HNil + String + ] :+: Int :+: HNil val inputs : Inputs = hash(sources.toSet ++ optionFiles(options, fileInputOpts)) :+: lastModified( - classpath.toSet) :+: classpath :+: outputDirectory :+: options :+: maxErrors :+: HNil + classpath.toSet + ) :+: classpath :+: outputDirectory :+: options :+: maxErrors :+: HNil val cachedComp = inputChanged(cacheStoreFactory make "inputs") { (inChanged, in: Inputs) => inputChanged(cacheStoreFactory make "output") { (outChanged, outputs: FilesInfo[PlainFileInfo]) => @@ -92,10 +96,12 @@ object RawCompileLike { compiler(sources, classpath, outputDirectory, options) } - def compile(label: String, - cacheStoreFactory: CacheStoreFactory, - instance: ScalaInstance, - cpOptions: ClasspathOptions): Gen = + def compile( + label: String, + cacheStoreFactory: CacheStoreFactory, + instance: ScalaInstance, + cpOptions: ClasspathOptions + ): Gen = cached(cacheStoreFactory, prepare(label + " sources", rawCompile(instance, cpOptions))) val nop: Gen = (_, _, _, _, _, _) => () diff --git a/main-actions/src/main/scala/sbt/Sync.scala b/main-actions/src/main/scala/sbt/Sync.scala index deec0ac34..663cc04fc 100644 --- a/main-actions/src/main/scala/sbt/Sync.scala +++ b/main-actions/src/main/scala/sbt/Sync.scala @@ -85,8 +85,10 @@ object Sync { sys.error("Duplicate mappings:" + dups.mkString) } - implicit def relationFormat[A, B](implicit af: JsonFormat[Map[A, Set[B]]], - bf: JsonFormat[Map[B, Set[A]]]): JsonFormat[Relation[A, B]] = + implicit def relationFormat[A, B]( + implicit af: JsonFormat[Map[A, Set[B]]], + bf: JsonFormat[Map[B, Set[A]]] + ): JsonFormat[Relation[A, B]] = new JsonFormat[Relation[A, B]] { def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Relation[A, B] = jsOpt match { @@ -109,15 +111,18 @@ object Sync { } - def writeInfo[F <: FileInfo](store: CacheStore, - relation: Relation[File, File], - info: Map[File, F])(implicit infoFormat: JsonFormat[F]): Unit = + def writeInfo[F <: FileInfo]( + store: CacheStore, + relation: Relation[File, File], + info: Map[File, F] + )(implicit infoFormat: JsonFormat[F]): Unit = store.write((relation, info)) type RelationInfo[F] = (Relation[File, File], Map[File, F]) - def readInfo[F <: FileInfo](store: CacheStore)( - implicit infoFormat: JsonFormat[F]): RelationInfo[F] = + def readInfo[F <: FileInfo]( + store: CacheStore + )(implicit infoFormat: JsonFormat[F]): RelationInfo[F] = try { readUncaught[F](store)(infoFormat) } catch { case _: IOException => (Relation.empty[File, File], Map.empty[File, F]) case _: ZipException => (Relation.empty[File, File], Map.empty[File, F]) @@ -128,7 +133,8 @@ object Sync { } } - private def readUncaught[F <: FileInfo](store: CacheStore)( - implicit infoFormat: JsonFormat[F]): RelationInfo[F] = + private def readUncaught[F <: FileInfo]( + store: CacheStore + )(implicit infoFormat: JsonFormat[F]): RelationInfo[F] = store.read(default = (Relation.empty[File, File], Map.empty[File, F])) } diff --git a/main-actions/src/main/scala/sbt/TestResultLogger.scala b/main-actions/src/main/scala/sbt/TestResultLogger.scala index 8248946b3..5f196f2b5 100644 --- a/main-actions/src/main/scala/sbt/TestResultLogger.scala +++ b/main-actions/src/main/scala/sbt/TestResultLogger.scala @@ -31,13 +31,17 @@ trait TestResultLogger { def run(log: Logger, results: Output, taskName: String): Unit /** Only allow invocation if certain criteria is met, else use another `TestResultLogger` (defaulting to nothing) . */ - final def onlyIf(f: (Output, String) => Boolean, - otherwise: TestResultLogger = TestResultLogger.Null) = + final def onlyIf( + f: (Output, String) => Boolean, + otherwise: TestResultLogger = TestResultLogger.Null + ) = TestResultLogger.choose(f, this, otherwise) /** Allow invocation unless a certain predicate passes, in which case use another `TestResultLogger` (defaulting to nothing) . */ - final def unless(f: (Output, String) => Boolean, - otherwise: TestResultLogger = TestResultLogger.Null) = + final def unless( + f: (Output, String) => Boolean, + otherwise: TestResultLogger = TestResultLogger.Null + ) = TestResultLogger.choose(f, otherwise, this) } @@ -69,8 +73,10 @@ object TestResultLogger { * @param f The `TestResultLogger` to choose if the predicate fails. */ def choose(cond: (Output, String) => Boolean, t: TestResultLogger, f: TestResultLogger) = - TestResultLogger((log, results, taskName) => - (if (cond(results, taskName)) t else f).run(log, results, taskName)) + TestResultLogger( + (log, results, taskName) => + (if (cond(results, taskName)) t else f).run(log, results, taskName) + ) /** Transforms the input to be completely silent when the subject module doesn't contain any tests. */ def silenceWhenNoTests(d: Defaults.Main) = @@ -127,35 +133,39 @@ object TestResultLogger { results.summaries.size > 1 || results.summaries.headOption.forall(_.summaryText.isEmpty) val printStandard = TestResultLogger((log, results, _) => { - val (skippedCount, - errorsCount, - passedCount, - failuresCount, - ignoredCount, - canceledCount, - pendingCount, + val ( + skippedCount, + errorsCount, + passedCount, + failuresCount, + ignoredCount, + canceledCount, + pendingCount, ) = results.events.foldLeft((0, 0, 0, 0, 0, 0, 0)) { case (acc, (_, testEvent)) => val (skippedAcc, errorAcc, passedAcc, failureAcc, ignoredAcc, canceledAcc, pendingAcc) = acc - (skippedAcc + testEvent.skippedCount, - errorAcc + testEvent.errorCount, - passedAcc + testEvent.passedCount, - failureAcc + testEvent.failureCount, - ignoredAcc + testEvent.ignoredCount, - canceledAcc + testEvent.canceledCount, - pendingAcc + testEvent.pendingCount, + ( + skippedAcc + testEvent.skippedCount, + errorAcc + testEvent.errorCount, + passedAcc + testEvent.passedCount, + failureAcc + testEvent.failureCount, + ignoredAcc + testEvent.ignoredCount, + canceledAcc + testEvent.canceledCount, + pendingAcc + testEvent.pendingCount, ) } val totalCount = failuresCount + errorsCount + skippedCount + passedCount val base = s"Total $totalCount, Failed $failuresCount, Errors $errorsCount, Passed $passedCount" - val otherCounts = Seq("Skipped" -> skippedCount, - "Ignored" -> ignoredCount, - "Canceled" -> canceledCount, - "Pending" -> pendingCount) + val otherCounts = Seq( + "Skipped" -> skippedCount, + "Ignored" -> ignoredCount, + "Canceled" -> canceledCount, + "Pending" -> pendingCount + ) val extra = otherCounts.filter(_._2 > 0).map { case (label, count) => s", $label $count" } val postfix = base + extra.mkString @@ -184,6 +194,7 @@ object TestResultLogger { }) val printNoTests = TestResultLogger( - (log, results, taskName) => log.info("No tests to run for " + taskName)) + (log, results, taskName) => log.info("No tests to run for " + taskName) + ) } } diff --git a/main-actions/src/main/scala/sbt/Tests.scala b/main-actions/src/main/scala/sbt/Tests.scala index 6885a0dc5..cc76b1412 100644 --- a/main-actions/src/main/scala/sbt/Tests.scala +++ b/main-actions/src/main/scala/sbt/Tests.scala @@ -44,9 +44,11 @@ object Tests { * @param events The result of each test group (suite) executed during this test run. * @param summaries Explicit summaries directly provided by test frameworks. This may be empty, in which case a default summary will be generated. */ - final case class Output(overall: TestResult, - events: Map[String, SuiteResult], - summaries: Iterable[Summary]) + final case class Output( + overall: TestResult, + events: Map[String, SuiteResult], + summaries: Iterable[Summary] + ) /** * Summarizes a test run. @@ -138,9 +140,11 @@ object Tests { val cleanup: Vector[ClassLoader => Unit], val testListeners: Vector[TestReportListener] ) - private[sbt] def processOptions(config: Execution, - discovered: Vector[TestDefinition], - log: Logger): ProcessedOptions = { + private[sbt] def processOptions( + config: Execution, + discovered: Vector[TestDefinition], + log: Logger + ): ProcessedOptions = { import collection.mutable.{ HashSet, ListBuffer } val testFilters = new ListBuffer[String => Boolean] var orderedFilters = Seq[String => Boolean]() @@ -168,7 +172,8 @@ object Tests { if (undefinedFrameworks.nonEmpty) log.warn( "Arguments defined for test frameworks that are not present:\n\t" + undefinedFrameworks - .mkString("\n\t")) + .mkString("\n\t") + ) def includeTest(test: TestDefinition) = !excludeTestsSet.contains(test.name) && testFilters.forall(filter => filter(test.name)) @@ -177,10 +182,12 @@ object Tests { if (orderedFilters.isEmpty) filtered0 else orderedFilters.flatMap(f => filtered0.filter(d => f(d.name))).toList.distinct val uniqueTests = distinctBy(tests)(_.name) - new ProcessedOptions(uniqueTests.toVector, - setup.toVector, - cleanup.toVector, - testListeners.toVector) + new ProcessedOptions( + uniqueTests.toVector, + setup.toVector, + cleanup.toVector, + testListeners.toVector + ) } private[this] def distinctBy[T, K](in: Seq[T])(f: T => K): Seq[T] = { @@ -188,33 +195,39 @@ object Tests { in.filter(t => seen.add(f(t))) } - def apply(frameworks: Map[TestFramework, Framework], - testLoader: ClassLoader, - runners: Map[TestFramework, Runner], - discovered: Vector[TestDefinition], - config: Execution, - log: ManagedLogger): Task[Output] = { + def apply( + frameworks: Map[TestFramework, Framework], + testLoader: ClassLoader, + runners: Map[TestFramework, Runner], + discovered: Vector[TestDefinition], + config: Execution, + log: ManagedLogger + ): Task[Output] = { val o = processOptions(config, discovered, log) - testTask(testLoader, - frameworks, - runners, - o.tests, - o.setup, - o.cleanup, - log, - o.testListeners, - config) + testTask( + testLoader, + frameworks, + runners, + o.tests, + o.setup, + o.cleanup, + log, + o.testListeners, + config + ) } - def testTask(loader: ClassLoader, - frameworks: Map[TestFramework, Framework], - runners: Map[TestFramework, Runner], - tests: Vector[TestDefinition], - userSetup: Iterable[ClassLoader => Unit], - userCleanup: Iterable[ClassLoader => Unit], - log: ManagedLogger, - testListeners: Vector[TestReportListener], - config: Execution): Task[Output] = { + def testTask( + loader: ClassLoader, + frameworks: Map[TestFramework, Framework], + runners: Map[TestFramework, Runner], + tests: Vector[TestDefinition], + userSetup: Iterable[ClassLoader => Unit], + userCleanup: Iterable[ClassLoader => Unit], + log: ManagedLogger, + testListeners: Vector[TestReportListener], + config: Execution + ): Task[Output] = { def fj(actions: Iterable[() => Unit]): Task[Unit] = nop.dependsOn(actions.toSeq.fork(_()): _*) def partApp(actions: Iterable[ClassLoader => Unit]) = actions.toSeq map { a => () => a(loader) @@ -239,31 +252,43 @@ object Tests { } type TestRunnable = (String, TestFunction) - private def createNestedRunnables(loader: ClassLoader, - testFun: TestFunction, - nestedTasks: Seq[TestTask]): Seq[(String, TestFunction)] = + private def createNestedRunnables( + loader: ClassLoader, + testFun: TestFunction, + nestedTasks: Seq[TestTask] + ): Seq[(String, TestFunction)] = nestedTasks.view.zipWithIndex map { case (nt, idx) => val testFunDef = testFun.taskDef - (testFunDef.fullyQualifiedName, - TestFramework.createTestFunction(loader, - new TaskDef(testFunDef.fullyQualifiedName + "-" + idx, - testFunDef.fingerprint, - testFunDef.explicitlySpecified, - testFunDef.selectors), - testFun.runner, - nt)) + ( + testFunDef.fullyQualifiedName, + TestFramework.createTestFunction( + loader, + new TaskDef( + testFunDef.fullyQualifiedName + "-" + idx, + testFunDef.fingerprint, + testFunDef.explicitlySpecified, + testFunDef.selectors + ), + testFun.runner, + nt + ) + ) } - def makeParallel(loader: ClassLoader, - runnables: Iterable[TestRunnable], - setupTasks: Task[Unit], - tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] = + def makeParallel( + loader: ClassLoader, + runnables: Iterable[TestRunnable], + setupTasks: Task[Unit], + tags: Seq[(Tag, Int)] + ): Task[Map[String, SuiteResult]] = toTasks(loader, runnables.toSeq, tags).dependsOn(setupTasks) - def toTasks(loader: ClassLoader, - runnables: Seq[TestRunnable], - tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] = { + def toTasks( + loader: ClassLoader, + runnables: Seq[TestRunnable], + tags: Seq[(Tag, Int)] + ): Task[Map[String, SuiteResult]] = { val tasks = runnables.map { case (name, test) => toTask(loader, name, test, tags) } tasks.join.map(_.foldLeft(Map.empty[String, SuiteResult]) { case (sum, e) => @@ -275,10 +300,12 @@ object Tests { }) } - def toTask(loader: ClassLoader, - name: String, - fun: TestFunction, - tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] = { + def toTask( + loader: ClassLoader, + name: String, + fun: TestFunction, + tags: Seq[(Tag, Int)] + ): Task[Map[String, SuiteResult]] = { val base = task { (name, fun.apply()) } val taggedBase = base.tagw(tags: _*).tag(fun.tags.map(ConcurrentRestrictions.Tag(_)): _*) taggedBase flatMap { @@ -310,8 +337,10 @@ object Tests { setupTasks: Task[Unit], ): Task[List[(String, SuiteResult)]] = { @tailrec - def processRunnable(runnableList: List[TestRunnable], - acc: List[(String, SuiteResult)]): List[(String, SuiteResult)] = + def processRunnable( + runnableList: List[TestRunnable], + acc: List[(String, SuiteResult)] + ): List[(String, SuiteResult)] = runnableList match { case hd :: rst => val testFun = hd._2 @@ -361,9 +390,11 @@ object Tests { ((TestResult.Passed: TestResult) /: results) { (acc, result) => if (severity(acc) < severity(result)) result else acc } - def discover(frameworks: Seq[Framework], - analysis: CompileAnalysis, - log: Logger): (Seq[TestDefinition], Set[String]) = + def discover( + frameworks: Seq[Framework], + analysis: CompileAnalysis, + log: Logger + ): (Seq[TestDefinition], Set[String]) = discover(frameworks flatMap TestFramework.getFingerprints, allDefs(analysis), log) def allDefs(analysis: CompileAnalysis) = analysis match { @@ -379,9 +410,11 @@ object Tests { all }.toSeq } - def discover(fingerprints: Seq[Fingerprint], - definitions: Seq[Definition], - log: Logger): (Seq[TestDefinition], Set[String]) = { + def discover( + fingerprints: Seq[Fingerprint], + definitions: Seq[Definition], + log: Logger + ): (Seq[TestDefinition], Set[String]) = { val subclasses = fingerprints collect { case sub: SubclassFingerprint => (sub.superclassName, sub.isModule, sub) }; @@ -392,9 +425,11 @@ object Tests { log.debug("Annotation fingerprints: " + annotations) def firsts[A, B, C](s: Seq[(A, B, C)]): Set[A] = s.map(_._1).toSet - def defined(in: Seq[(String, Boolean, Fingerprint)], - names: Set[String], - IsModule: Boolean): Seq[Fingerprint] = + def defined( + in: Seq[(String, Boolean, Fingerprint)], + names: Set[String], + IsModule: Boolean + ): Seq[Fingerprint] = in collect { case (name, IsModule, print) if names(name) => print } def toFingerprints(d: Discovered): Seq[Fingerprint] = diff --git a/main-actions/src/main/scala/sbt/compiler/Eval.scala b/main-actions/src/main/scala/sbt/compiler/Eval.scala index a9d97246a..a52a682ae 100644 --- a/main-actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main-actions/src/main/scala/sbt/compiler/Eval.scala @@ -33,10 +33,12 @@ final class EvalImports(val strings: Seq[(String, Int)], val srcName: String) * the module from that class loader. `generated` contains the compiled classes and cache files related * to the expression. The name of the auto-generated module wrapping the expression is `enclosingModule`. */ -final class EvalResult(val tpe: String, - val getValue: ClassLoader => Any, - val generated: Seq[File], - val enclosingModule: String) +final class EvalResult( + val tpe: String, + val getValue: ClassLoader => Any, + val generated: Seq[File], + val enclosingModule: String +) /** * The result of evaluating a group of Scala definitions. The definitions are wrapped in an auto-generated, @@ -45,10 +47,12 @@ final class EvalResult(val tpe: String, * from the classpath that the definitions were compiled against. The list of vals with the requested types is `valNames`. * The values for these may be obtained by providing the parent class loader to `values` as is done with `loader`. */ -final class EvalDefinitions(val loader: ClassLoader => ClassLoader, - val generated: Seq[File], - val enclosingModule: String, - val valNames: Seq[String]) { +final class EvalDefinitions( + val loader: ClassLoader => ClassLoader, + val generated: Seq[File], + val enclosingModule: String, + val valNames: Seq[String] +) { def values(parent: ClassLoader): Seq[Any] = { val module = getModule(enclosingModule, loader(parent)) for (n <- valNames) yield module.getClass.getMethod(n).invoke(module) @@ -57,10 +61,12 @@ final class EvalDefinitions(val loader: ClassLoader => ClassLoader, final class EvalException(msg: String) extends RuntimeException(msg) // not thread safe, since it reuses a Global instance -final class Eval(optionsNoncp: Seq[String], - classpath: Seq[File], - mkReporter: Settings => Reporter, - backing: Option[File]) { +final class Eval( + optionsNoncp: Seq[String], + classpath: Seq[File], + mkReporter: Settings => Reporter, + backing: Option[File] +) { def this(mkReporter: Settings => Reporter, backing: Option[File]) = this(Nil, IO.classLocationFile[Product] :: Nil, mkReporter, backing) def this() = this(s => new ConsoleReporter(s), None) @@ -96,11 +102,13 @@ final class Eval(optionsNoncp: Seq[String], private[this] var toUnlinkLater = List[Symbol]() private[this] def unlink(sym: Symbol) = sym.owner.info.decls.unlink(sym) - def eval(expression: String, - imports: EvalImports = noImports, - tpeName: Option[String] = None, - srcName: String = "", - line: Int = DefaultStartLine): EvalResult = { + def eval( + expression: String, + imports: EvalImports = noImports, + tpeName: Option[String] = None, + srcName: String = "", + line: Int = DefaultStartLine + ): EvalResult = { val ev = new EvalType[String] { def makeUnit = mkUnit(srcName, line, expression) def unlink = true @@ -120,11 +128,13 @@ final class Eval(optionsNoncp: Seq[String], val value = (cl: ClassLoader) => getValue[Any](i.enclosingModule, i.loader(cl)) new EvalResult(i.extra, value, i.generated, i.enclosingModule) } - def evalDefinitions(definitions: Seq[(String, scala.Range)], - imports: EvalImports, - srcName: String, - file: Option[File], - valTypes: Seq[String]): EvalDefinitions = { + def evalDefinitions( + definitions: Seq[(String, scala.Range)], + imports: EvalImports, + srcName: String, + file: Option[File], + valTypes: Seq[String] + ): EvalDefinitions = { require(definitions.nonEmpty, "Definitions to evaluate cannot be empty.") val ev = new EvalType[Seq[String]] { lazy val (fullUnit, defUnits) = mkDefsUnit(srcName, definitions) @@ -151,20 +161,27 @@ final class Eval(optionsNoncp: Seq[String], new EvalDefinitions(i.loader, i.generated, i.enclosingModule, i.extra) } - private[this] def evalCommon[T](content: Seq[String], - imports: EvalImports, - tpeName: Option[String], - ev: EvalType[T]): EvalIntermediate[T] = { + private[this] def evalCommon[T]( + content: Seq[String], + imports: EvalImports, + tpeName: Option[String], + ev: EvalType[T] + ): EvalIntermediate[T] = { import Eval._ // TODO - We also encode the source of the setting into the hash to avoid conflicts where the exact SAME setting // is defined in multiple evaluated instances with a backing. This leads to issues with finding a previous // value on the classpath when compiling. val hash = Hash.toHex( - Hash(bytes( - stringSeqBytes(content) :: optBytes(backing)(fileExistsBytes) :: stringSeqBytes(options) :: - seqBytes(classpath)(fileModifiedBytes) :: stringSeqBytes(imports.strings.map(_._1)) :: optBytes( - tpeName)(bytes) :: - bytes(ev.extraHash) :: Nil))) + Hash( + bytes( + stringSeqBytes(content) :: optBytes(backing)(fileExistsBytes) :: stringSeqBytes(options) :: + seqBytes(classpath)(fileModifiedBytes) :: stringSeqBytes(imports.strings.map(_._1)) :: optBytes( + tpeName + )(bytes) :: + bytes(ev.extraHash) :: Nil + ) + ) + ) val moduleName = makeModuleName(hash) lazy val unit = { @@ -192,12 +209,14 @@ final class Eval(optionsNoncp: Seq[String], // location of the cached type or definition information private[this] def cacheFile(base: File, moduleName: String): File = new File(base, moduleName + ".cache") - private[this] def compileAndLoad[T](run: Run, - unit: CompilationUnit, - imports: EvalImports, - backing: Option[File], - moduleName: String, - ev: EvalType[T]): (T, ClassLoader => ClassLoader) = { + private[this] def compileAndLoad[T]( + run: Run, + unit: CompilationUnit, + imports: EvalImports, + backing: Option[File], + moduleName: String, + ev: EvalType[T] + ): (T, ClassLoader => ClassLoader) = { global.curRun = run run.currentUnit = unit val dir = outputDirectory(backing) @@ -242,18 +261,22 @@ final class Eval(optionsNoncp: Seq[String], parent => getValue[Any](moduleName, new URLClassLoader(Array(dir.toURI.toURL), parent)) //wrap tree in object objectName { def WrapValName = } - def augment(parser: global.syntaxAnalyzer.UnitParser, - imports: Seq[Tree], - tree: Tree, - tpt: Tree, - objectName: String): Tree = { + def augment( + parser: global.syntaxAnalyzer.UnitParser, + imports: Seq[Tree], + tree: Tree, + tpt: Tree, + objectName: String + ): Tree = { val method = DefDef(NoMods, newTermName(WrapValName), Nil, Nil, tpt, tree) syntheticModule(parser, imports, method :: Nil, objectName) } - private[this] def syntheticModule(parser: global.syntaxAnalyzer.UnitParser, - imports: Seq[Tree], - definitions: List[Tree], - objectName: String): Tree = { + private[this] def syntheticModule( + parser: global.syntaxAnalyzer.UnitParser, + imports: Seq[Tree], + definitions: List[Tree], + objectName: String + ): Tree = { val emptyTypeName = nme.EMPTY.toTypeName def emptyPkg = parser.atPos(0, 0, 0) { Ident(nme.EMPTY_PACKAGE_NAME) } def emptyInit = DefDef( @@ -262,8 +285,10 @@ final class Eval(optionsNoncp: Seq[String], Nil, List(Nil), TypeTree(), - Block(List(Apply(Select(Super(This(emptyTypeName), emptyTypeName), nme.CONSTRUCTOR), Nil)), - Literal(Constant(()))) + Block( + List(Apply(Select(Super(This(emptyTypeName), emptyTypeName), nme.CONSTRUCTOR), Nil)), + Literal(Constant(())) + ) ) def moduleBody = Template(List(gen.scalaAnyRefConstr), noSelfType, emptyInit :: definitions) @@ -301,10 +326,12 @@ final class Eval(optionsNoncp: Seq[String], private[this] def isTopLevelModule(s: Symbol): Boolean = s.hasFlag(reflect.internal.Flags.MODULE) && s.owner.isPackageClass - private[this] final class EvalIntermediate[T](val extra: T, - val loader: ClassLoader => ClassLoader, - val generated: Seq[File], - val enclosingModule: String) + private[this] final class EvalIntermediate[T]( + val extra: T, + val loader: ClassLoader => ClassLoader, + val generated: Seq[File], + val enclosingModule: String + ) private[this] def classExists(dir: File, name: String) = (new File(dir, name + ".class")).exists // TODO: use the code from Analyzer @@ -318,10 +345,12 @@ final class Eval(optionsNoncp: Seq[String], (s contains moduleName) } - private[this] class ParseErrorStrings(val base: String, - val extraBlank: String, - val missingBlank: String, - val extraSemi: String) + private[this] class ParseErrorStrings( + val base: String, + val extraBlank: String, + val missingBlank: String, + val extraSemi: String + ) private[this] def definitionErrorStrings = new ParseErrorStrings( base = "Error parsing definition.", extraBlank = " Ensure that there are no blank lines within a definition.", @@ -340,9 +369,11 @@ final class Eval(optionsNoncp: Seq[String], * Parses the provided compilation `unit` according to `f` and then performs checks on the final parser state * to catch errors that are common when the content is embedded in a blank-line-delimited format. */ - private[this] def parse[T](unit: CompilationUnit, - errors: ParseErrorStrings, - f: syntaxAnalyzer.UnitParser => T): (syntaxAnalyzer.UnitParser, T) = { + private[this] def parse[T]( + unit: CompilationUnit, + errors: ParseErrorStrings, + f: syntaxAnalyzer.UnitParser => T + ): (syntaxAnalyzer.UnitParser, T) = { val parser = new syntaxAnalyzer.UnitParser(unit) val tree = f(parser) @@ -443,7 +474,8 @@ final class Eval(optionsNoncp: Seq[String], */ private[this] def mkDefsUnit( srcName: String, - definitions: Seq[(String, scala.Range)]): (CompilationUnit, Seq[CompilationUnit]) = { + definitions: Seq[(String, scala.Range)] + ): (CompilationUnit, Seq[CompilationUnit]) = { def fragmentUnit(content: String, lineMap: Array[Int]) = new CompilationUnit(fragmentSourceFile(srcName, content, lineMap)) diff --git a/main-actions/src/test/scala/sbt/CacheIvyTest.scala b/main-actions/src/test/scala/sbt/CacheIvyTest.scala index 1827bc5db..73f145764 100644 --- a/main-actions/src/test/scala/sbt/CacheIvyTest.scala +++ b/main-actions/src/test/scala/sbt/CacheIvyTest.scala @@ -37,19 +37,21 @@ class CacheIvyTest extends Properties("CacheIvy") { content = converter.toJsonUnsafe(value) } - private def testCache[T: JsonFormat, U](f: (SingletonCache[T], CacheStore) => U)( - implicit cache: SingletonCache[T]): U = { + private def testCache[T: JsonFormat, U]( + f: (SingletonCache[T], CacheStore) => U + )(implicit cache: SingletonCache[T]): U = { val store = new InMemoryStore(Converter) f(cache, store) } - private def cachePreservesEquality[T: JsonFormat](m: T, - eq: (T, T) => Prop, - str: T => String): Prop = testCache[T, Prop] { - (cache, store) => - cache.write(store, m) - val out = cache.read(store) - eq(out, m) :| s"Expected: ${str(m)}" :| s"Got: ${str(out)}" + private def cachePreservesEquality[T: JsonFormat]( + m: T, + eq: (T, T) => Prop, + str: T => String + ): Prop = testCache[T, Prop] { (cache, store) => + cache.write(store, m) + val out = cache.read(store) + eq(out, m) :| s"Expected: ${str(m)}" :| s"Got: ${str(out)}" } implicit val arbConfigRef: Arbitrary[ConfigRef] = Arbitrary( diff --git a/main-actions/src/test/scala/sbt/compiler/EvalTest.scala b/main-actions/src/test/scala/sbt/compiler/EvalTest.scala index 10600a9a8..a5d6eb387 100644 --- a/main-actions/src/test/scala/sbt/compiler/EvalTest.scala +++ b/main-actions/src/test/scala/sbt/compiler/EvalTest.scala @@ -38,7 +38,8 @@ class EvalTest extends Properties("eval") { val line = math.abs(l) val src = "mismatch" throws(classOf[RuntimeException])( - eval.eval(i.toString, tpeName = Some(BooleanType), line = line, srcName = src)) && + eval.eval(i.toString, tpeName = Some(BooleanType), line = line, srcName = src) + ) && hasErrors(line + 1, src) } @@ -78,14 +79,17 @@ val p = { property("explicit import") = forAll(testImport("import math.abs" :: Nil)) property("wildcard import") = forAll(testImport("import math._" :: Nil)) property("comma-separated imports") = forAll( - testImport("import annotation._, math._, meta._" :: Nil)) + testImport("import annotation._, math._, meta._" :: Nil) + ) property("multiple imports") = forAll( - testImport("import annotation._" :: "import math._" :: "import meta._" :: Nil)) + testImport("import annotation._" :: "import math._" :: "import meta._" :: Nil) + ) private[this] def testImport(imports: Seq[String]): Int => Prop = i => value(eval.eval("abs(" + i + ")", new EvalImports(imports.zipWithIndex, "imp"))) == math.abs( - i) + i + ) private[this] def local(i: Int) = "{ class ETest(val i: Int); new ETest(" + i + ") }" val LocalType = "AnyRef{val i: Int}" diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index abc2bc9c1..6baee9509 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -21,8 +21,10 @@ object BasicCommandStrings { val TerminateAction: String = Exit def helpBrief = - (HelpCommand, - s"Displays this help message or prints detailed help on requested commands (run '$HelpCommand ').") + ( + HelpCommand, + s"Displays this help message or prints detailed help on requested commands (run '$HelpCommand ')." + ) def helpDetailed = s"""$HelpCommand Prints a help summary. @@ -131,8 +133,10 @@ $HelpCommand def Multi = ";" def MultiBrief = - (Multi + " (" + Multi + " )*", - "Runs the provided semicolon-separated commands.") + ( + Multi + " (" + Multi + " )*", + "Runs the provided semicolon-separated commands." + ) def MultiDetailed = Multi + " command1 " + Multi + """ command2 ... diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index cd095f0b5..e9d5b8a19 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -80,7 +80,8 @@ object BasicCommands { val h = (Help.empty /: s.definedCommands)( (a, b) => a ++ (try b.help(s) - catch { case NonFatal(_) => Help.empty })) + catch { case NonFatal(_) => Help.empty }) + ) val helpCommands = h.detail.keySet val spacedArg = singleArgument(helpCommands).? applyEffect(spacedArg)(runHelp(s, h)) @@ -95,7 +96,8 @@ object BasicCommands { def completionsCommand: Command = Command(CompletionsCommand, CompletionsBrief, CompletionsDetailed)(_ => completionsParser)( - runCompletions(_)(_)) + runCompletions(_)(_) + ) @deprecated("No longer public", "1.1.1") def completionsParser(state: State): Parser[String] = completionsParser @@ -118,8 +120,9 @@ object BasicCommands { def multiParser(s: State): Parser[List[String]] = { val nonSemi = token(charClass(_ != ';').+, hide = const(true)) val semi = token(';' ~> OptSpace) - val part = semi flatMap (_ => - matched((s.combinedParser & nonSemi) | nonSemi) <~ token(OptSpace)) + val part = semi flatMap ( + _ => matched((s.combinedParser & nonSemi) | nonSemi) <~ token(OptSpace) + ) (part map (_.trim)).+ map (_.toList) } @@ -135,16 +138,19 @@ object BasicCommands { matched(s.combinedParser | token(any, hide = const(true))) def ifLast: Command = - Command(IfLast, Help.more(IfLast, IfLastDetailed))(otherCommandParser)((s, arg) => - if (s.remainingCommands.isEmpty) arg :: s else s) + Command(IfLast, Help.more(IfLast, IfLastDetailed))(otherCommandParser)( + (s, arg) => if (s.remainingCommands.isEmpty) arg :: s else s + ) def append: Command = Command(AppendCommand, Help.more(AppendCommand, AppendLastDetailed))(otherCommandParser)( - (s, arg) => s.copy(remainingCommands = s.remainingCommands :+ Exec(arg, s.source))) + (s, arg) => s.copy(remainingCommands = s.remainingCommands :+ Exec(arg, s.source)) + ) def setOnFailure: Command = - Command(OnFailure, Help.more(OnFailure, OnFailureDetailed))(otherCommandParser)((s, arg) => - s.copy(onFailure = Some(Exec(arg, s.source)))) + Command(OnFailure, Help.more(OnFailure, OnFailureDetailed))(otherCommandParser)( + (s, arg) => s.copy(onFailure = Some(Exec(arg, s.source))) + ) private[sbt] def compatCommands = Seq( Command.command(Compat.ClearOnFailure) { s => @@ -154,7 +160,8 @@ object BasicCommands { Command.arb( s => token(Compat.OnFailure, hide = const(true)) - .flatMap(_ => otherCommandParser(s))) { (s, arg) => + .flatMap(_ => otherCommandParser(s)) + ) { (s, arg) => s.log.warn(Compat.OnFailureDeprecated) s.copy(onFailure = Some(Exec(arg, s.source))) }, @@ -167,8 +174,9 @@ object BasicCommands { def clearOnFailure: Command = Command.command(ClearOnFailure)(s => s.copy(onFailure = None)) def stashOnFailure: Command = - Command.command(StashOnFailure)(s => - s.copy(onFailure = None).update(OnFailureStack)(s.onFailure :: _.toList.flatten)) + Command.command(StashOnFailure)( + s => s.copy(onFailure = None).update(OnFailureStack)(s.onFailure :: _.toList.flatten) + ) def popOnFailure: Command = Command.command(PopOnFailure) { s => val stack = s.get(OnFailureStack).getOrElse(Nil) @@ -213,8 +221,9 @@ object BasicCommands { private[this] def className: Parser[String] = { val base = StringBasic & not('-' ~> any.*, "Class name cannot start with '-'.") def single(s: String) = Completions.single(Completion.displayOnly(s)) - val compl = TokenCompletions.fixed((seen, _) => - if (seen.startsWith("-")) Completions.nil else single("")) + val compl = TokenCompletions.fixed( + (seen, _) => if (seen.startsWith("-")) Completions.nil else single("") + ) token(base, compl) } @@ -402,7 +411,8 @@ object BasicCommands { } def delegateToAlias(name: String, orElse: Parser[() => State])( - state: State): Parser[() => State] = + state: State + ): Parser[() => State] = aliases(state, (nme, _) => nme == name).headOption match { case None => orElse case Some((n, v)) => aliasBody(n, v)(state) diff --git a/main-command/src/main/scala/sbt/BasicKeys.scala b/main-command/src/main/scala/sbt/BasicKeys.scala index 5ceeec21b..d1fc63f7a 100644 --- a/main-command/src/main/scala/sbt/BasicKeys.scala +++ b/main-command/src/main/scala/sbt/BasicKeys.scala @@ -18,11 +18,13 @@ object BasicKeys { val historyPath = AttributeKey[Option[File]]( "history", "The location where command line history is persisted.", - 40) + 40 + ) val shellPrompt = AttributeKey[State => String]( "shell-prompt", "The function that constructs the command prompt from the current build state.", - 10000) + 10000 + ) val watch = AttributeKey[Watched]("watch", "Continuous execution configuration.", 1000) val serverPort = AttributeKey[Int]("server-port", "The port number used by server command.", 10000) @@ -31,25 +33,32 @@ object BasicKeys { AttributeKey[String]("serverHost", "The host used by server command.", 10000) val serverAuthentication = - AttributeKey[Set[ServerAuthentication]]("serverAuthentication", - "Method of authenticating server command.", - 10000) + AttributeKey[Set[ServerAuthentication]]( + "serverAuthentication", + "Method of authenticating server command.", + 10000 + ) val serverConnectionType = - AttributeKey[ConnectionType]("serverConnectionType", - "The wire protocol for the server command.", - 10000) + AttributeKey[ConnectionType]( + "serverConnectionType", + "The wire protocol for the server command.", + 10000 + ) val fullServerHandlers = - AttributeKey[Seq[ServerHandler]]("fullServerHandlers", - "Combines default server handlers and user-defined handlers.", - 10000) + AttributeKey[Seq[ServerHandler]]( + "fullServerHandlers", + "Combines default server handlers and user-defined handlers.", + 10000 + ) val autoStartServer = AttributeKey[Boolean]( "autoStartServer", "If true, the sbt server will startup automatically during interactive sessions.", - 10000) + 10000 + ) // Unlike other BasicKeys, this is not used directly as a setting key, // and severLog / logLevel is used instead. @@ -62,23 +71,28 @@ object BasicKeys { private[sbt] val interactive = AttributeKey[Boolean]( "interactive", "True if commands are currently being entered from an interactive environment.", - 10) + 10 + ) private[sbt] val classLoaderCache = AttributeKey[ClassLoaderCache]( "class-loader-cache", "Caches class loaders based on the classpath entries and last modified times.", - 10) + 10 + ) private[sbt] val OnFailureStack = AttributeKey[List[Option[Exec]]]( "on-failure-stack", "Stack that remembers on-failure handlers.", - 10) + 10 + ) private[sbt] val explicitGlobalLogLevels = AttributeKey[Boolean]( "explicit-global-log-levels", "True if the global logging levels were explicitly set by the user.", - 10) + 10 + ) private[sbt] val templateResolverInfos = AttributeKey[Seq[TemplateResolverInfo]]( "templateResolverInfos", "List of template resolver infos.", - 1000) + 1000 + ) } case class TemplateResolverInfo(module: ModuleID, implementationClass: String) diff --git a/main-command/src/main/scala/sbt/Command.scala b/main-command/src/main/scala/sbt/Command.scala index 6ee8e0e44..49cda7fec 100644 --- a/main-command/src/main/scala/sbt/Command.scala +++ b/main-command/src/main/scala/sbt/Command.scala @@ -67,18 +67,21 @@ object Command { new SimpleCommand(name, help, parser, AttributeMap.empty) def make(name: String, briefHelp: (String, String), detail: String)( - parser: State => Parser[() => State]): Command = + parser: State => Parser[() => State] + ): Command = make(name, Help(name, briefHelp, detail))(parser) // General command construction /** Construct a command with the given name, parser and effect. */ - def apply[T](name: String, help: Help = Help.empty)(parser: State => Parser[T])( - effect: (State, T) => State): Command = + def apply[T](name: String, help: Help = Help.empty)( + parser: State => Parser[T] + )(effect: (State, T) => State): Command = make(name, help)(applyEffect(parser)(effect)) def apply[T](name: String, briefHelp: (String, String), detail: String)( - parser: State => Parser[T])(effect: (State, T) => State): Command = + parser: State => Parser[T] + )(effect: (State, T) => State): Command = apply(name, Help(name, briefHelp, detail))(parser)(effect) // No-argument command construction @@ -97,18 +100,21 @@ object Command { make(name, help)(state => token(trimmed(spacedAny(name)) map apply1(f, state))) def single(name: String, briefHelp: (String, String), detail: String)( - f: (State, String) => State): Command = + f: (State, String) => State + ): Command = single(name, Help(name, briefHelp, detail))(f) // Multi-argument command construction /** Construct a multi-argument command with the given name, tab completion display and effect. */ def args(name: String, display: String, help: Help = Help.empty)( - f: (State, Seq[String]) => State): Command = + f: (State, Seq[String]) => State + ): Command = make(name, help)(state => spaceDelimited(display) map apply1(f, state)) def args(name: String, briefHelp: (String, String), detail: String, display: String)( - f: (State, Seq[String]) => State): Command = + f: (State, Seq[String]) => State + ): Command = args(name, display, Help(name, briefHelp, detail))(f) // create ArbitraryCommand @@ -120,7 +126,8 @@ object Command { customHelp(parser, const(help)) def arb[T](parser: State => Parser[T], help: Help = Help.empty)( - effect: (State, T) => State): Command = + effect: (State, T) => State + ): Command = custom(applyEffect(parser)(effect), help) // misc Command object utilities @@ -129,8 +136,9 @@ object Command { def applyEffect[T](p: Parser[T])(f: T => State): Parser[() => State] = p map (t => () => f(t)) - def applyEffect[T](parser: State => Parser[T])( - effect: (State, T) => State): State => Parser[() => State] = + def applyEffect[T]( + parser: State => Parser[T] + )(effect: (State, T) => State): State => Parser[() => State] = s => applyEffect(parser(s))(t => effect(s, t)) def combine(cmds: Seq[Command]): State => Parser[() => State] = { @@ -140,7 +148,8 @@ object Command { } private[this] def separateCommands( - cmds: Seq[Command]): (Seq[SimpleCommand], Seq[ArbitraryCommand]) = + cmds: Seq[Command] + ): (Seq[SimpleCommand], Seq[ArbitraryCommand]) = Util.separate(cmds) { case s: SimpleCommand => Left(s); case a: ArbitraryCommand => Right(a) } private[this] def apply1[A, B, C](f: (A, B) => C, a: A): B => () => C = b => () => f(a, b) @@ -155,13 +164,16 @@ object Command { } def simpleParser( - commandMap: Map[String, State => Parser[() => State]]): State => Parser[() => State] = + commandMap: Map[String, State => Parser[() => State]] + ): State => Parser[() => State] = state => - token(OpOrID examples commandMap.keys.toSet) flatMap (id => - (commandMap get id) match { - case None => failure(invalidValue("command", commandMap.keys)(id)) - case Some(c) => c(state) - }) + token(OpOrID examples commandMap.keys.toSet) flatMap ( + id => + (commandMap get id) match { + case None => failure(invalidValue("command", commandMap.keys)(id)) + case Some(c) => c(state) + } + ) def process(command: String, state: State): State = { val parser = combine(state.definedCommands) @@ -181,10 +193,12 @@ object Command { if (suggested.isEmpty) "" else suggested.mkString(" (similar: ", ", ", ")") } - def suggestions(a: String, - bs: Seq[String], - maxDistance: Int = 3, - maxSuggestions: Int = 3): Seq[String] = + def suggestions( + a: String, + bs: Seq[String], + maxDistance: Int = 3, + maxSuggestions: Int = 3 + ): Seq[String] = bs map (b => (b, distance(a, b))) filter (_._2 <= maxDistance) sortBy (_._2) take (maxSuggestions) map (_._1) def distance(a: String, b: String): Int = @@ -233,9 +247,11 @@ object Help { def apply(briefHelp: Seq[(String, String)], detailedHelp: Map[String, String]): Help = apply(briefHelp, detailedHelp, Set.empty[String]) - def apply(briefHelp: Seq[(String, String)], - detailedHelp: Map[String, String], - more: Set[String]): Help = + def apply( + briefHelp: Seq[(String, String)], + detailedHelp: Map[String, String], + more: Set[String] + ): Help = new Help0(briefHelp, detailedHelp, more) def more(name: String, detailedHelp: String): Help = diff --git a/main-command/src/main/scala/sbt/MainControl.scala b/main-command/src/main/scala/sbt/MainControl.scala index e728dd765..e24c657e2 100644 --- a/main-command/src/main/scala/sbt/MainControl.scala +++ b/main-command/src/main/scala/sbt/MainControl.scala @@ -12,21 +12,23 @@ import java.io.File final case class Exit(code: Int) extends xsbti.Exit { require(code >= 0) } -final case class Reboot(scalaVersion: String, - argsList: Seq[String], - app: xsbti.ApplicationID, - baseDirectory: File) - extends xsbti.Reboot { +final case class Reboot( + scalaVersion: String, + argsList: Seq[String], + app: xsbti.ApplicationID, + baseDirectory: File +) extends xsbti.Reboot { def arguments = argsList.toArray } -final case class ApplicationID(groupID: String, - name: String, - version: String, - mainClass: String, - components: Seq[String], - crossVersionedValue: xsbti.CrossValue, - extra: Seq[File]) - extends xsbti.ApplicationID { +final case class ApplicationID( + groupID: String, + name: String, + version: String, + mainClass: String, + components: Seq[String], + crossVersionedValue: xsbti.CrossValue, + extra: Seq[File] +) extends xsbti.ApplicationID { def mainComponents = components.toArray def classpathExtra = extra.toArray def crossVersioned = crossVersionedValue != xsbti.CrossValue.Disabled @@ -35,11 +37,13 @@ object ApplicationID { def apply(delegate: xsbti.ApplicationID, newVersion: String): ApplicationID = apply(delegate).copy(version = newVersion) def apply(delegate: xsbti.ApplicationID): ApplicationID = - ApplicationID(delegate.groupID, - delegate.name, - delegate.version, - delegate.mainClass, - delegate.mainComponents, - delegate.crossVersionedValue, - delegate.classpathExtra) + ApplicationID( + delegate.groupID, + delegate.name, + delegate.version, + delegate.mainClass, + delegate.mainComponents, + delegate.crossVersionedValue, + delegate.classpathExtra + ) } diff --git a/main-command/src/main/scala/sbt/State.scala b/main-command/src/main/scala/sbt/State.scala index a3e178dc6..e65e58a03 100644 --- a/main-command/src/main/scala/sbt/State.scala +++ b/main-command/src/main/scala/sbt/State.scala @@ -287,8 +287,9 @@ object State { def fail = { import BasicCommandStrings.Compat.{ FailureWall => CompatFailureWall } val remaining = - s.remainingCommands.dropWhile(c => - c.commandLine != FailureWall && c.commandLine != CompatFailureWall) + s.remainingCommands.dropWhile( + c => c.commandLine != FailureWall && c.commandLine != CompatFailureWall + ) if (remaining.isEmpty) applyOnFailure(s, Nil, exit(ok = false)) else diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index fad1b6a8d..6ff60a135 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -113,7 +113,8 @@ object Watched { } catch { case e: Exception => s.log.error( - "Error occurred obtaining files to watch. Terminating continuous execution...") + "Error occurred obtaining files to watch. Terminating continuous execution..." + ) s.handleError(e) (false, watchState) } @@ -133,8 +134,10 @@ object Watched { AttributeKey[WatchState]("watch state", "Internal: tracks state for continuous execution.") val ContinuousWatchService = - AttributeKey[WatchService]("watch service", - "Internal: tracks watch service for continuous execution.") + AttributeKey[WatchService]( + "watch service", + "Internal: tracks watch service for continuous execution." + ) val Configuration = AttributeKey[Watched]("watched-configuration", "Configures continuous execution.") diff --git a/main-command/src/main/scala/sbt/internal/server/Server.scala b/main-command/src/main/scala/sbt/internal/server/Server.scala index 95ef32c95..c3d130470 100644 --- a/main-command/src/main/scala/sbt/internal/server/Server.scala +++ b/main-command/src/main/scala/sbt/internal/server/Server.scala @@ -40,9 +40,11 @@ private[sbt] object Server { with TokenFileFormats object JsonProtocol extends JsonProtocol - def start(connection: ServerConnection, - onIncomingSocket: (Socket, ServerInstance) => Unit, - log: Logger): ServerInstance = + def start( + connection: ServerConnection, + onIncomingSocket: (Socket, ServerInstance) => Unit, + log: Logger + ): ServerInstance = new ServerInstance { self => import connection._ val running = new AtomicBoolean(false) @@ -67,7 +69,8 @@ private[sbt] object Server { "socket file absolute path too long; " + "either switch to another connection type " + "or define a short \"SBT_GLOBAL_SERVER_DIR\" value. " + - s"Current path: ${path}") + s"Current path: ${path}" + ) tryClient(new UnixDomainSocket(path)) prepareSocketfile() addServerError(new UnixDomainServerSocket(path)) 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 47b4f4fbf..61969a466 100644 --- a/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala +++ b/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala @@ -34,14 +34,18 @@ object ServerHandler { }) } -final class ServerIntent(val onRequest: PartialFunction[JsonRpcRequestMessage, Unit], - val onNotification: PartialFunction[JsonRpcNotificationMessage, Unit]) { +final class ServerIntent( + val onRequest: PartialFunction[JsonRpcRequestMessage, Unit], + val onNotification: PartialFunction[JsonRpcNotificationMessage, Unit] +) { override def toString: String = s"ServerIntent(...)" } object ServerIntent { - def apply(onRequest: PartialFunction[JsonRpcRequestMessage, Unit], - onNotification: PartialFunction[JsonRpcNotificationMessage, Unit]): ServerIntent = + def apply( + onRequest: PartialFunction[JsonRpcRequestMessage, Unit], + onNotification: PartialFunction[JsonRpcNotificationMessage, Unit] + ): ServerIntent = new ServerIntent(onRequest, onNotification) def request(onRequest: PartialFunction[JsonRpcRequestMessage, Unit]): ServerIntent = diff --git a/main-settings/src/main/scala/sbt/Append.scala b/main-settings/src/main/scala/sbt/Append.scala index c9116de97..fb08b5e33 100644 --- a/main-settings/src/main/scala/sbt/Append.scala +++ b/main-settings/src/main/scala/sbt/Append.scala @@ -18,12 +18,14 @@ import sbt.io.{ AllPassFilter, NothingFilter } object Append { @implicitNotFound( - msg = "No implicit for Append.Value[${A}, ${B}] found,\n so ${B} cannot be appended to ${A}") + msg = "No implicit for Append.Value[${A}, ${B}] found,\n so ${B} cannot be appended to ${A}" + ) trait Value[A, B] { def appendValue(a: A, b: B): A } @implicitNotFound( - msg = "No implicit for Append.Values[${A}, ${B}] found,\n so ${B} cannot be appended to ${A}") + msg = "No implicit for Append.Values[${A}, ${B}] found,\n so ${B} cannot be appended to ${A}" + ) trait Values[A, -B] { def appendValues(a: A, b: B): A } diff --git a/main-settings/src/main/scala/sbt/Def.scala b/main-settings/src/main/scala/sbt/Def.scala index d03b4ba10..0fd53e281 100644 --- a/main-settings/src/main/scala/sbt/Def.scala +++ b/main-settings/src/main/scala/sbt/Def.scala @@ -27,11 +27,13 @@ object Def extends Init[Scope] with TaskMacroExtra { val resolvedScoped = SettingKey[ScopedKey[_]]( "resolved-scoped", "The ScopedKey for the referencing setting or task.", - KeyRanks.DSetting) + KeyRanks.DSetting + ) private[sbt] val taskDefinitionKey = AttributeKey[ScopedKey[_]]( "task-definition-key", "Internal: used to map a task back to its ScopedKey.", - Invisible) + Invisible + ) lazy val showFullKey: Show[ScopedKey[_]] = showFullKey(None) @@ -56,7 +58,8 @@ object Def extends Init[Scope] with TaskMacroExtra { key.scope, withColor(key.key.label, keyNameColor), ref => displayRelative2(current, ref) - )) + ) + ) @deprecated("Use showBuildRelativeKey2 which doesn't take the unused multi param", "1.1.1") def showBuildRelativeKey( @@ -76,7 +79,8 @@ object Def extends Init[Scope] with TaskMacroExtra { key.scope, withColor(key.key.label, keyNameColor), ref => displayBuildRelative(currentBuild, ref) - )) + ) + ) /** * Returns a String expression for the given [[Reference]] (BuildRef, [[ProjectRef]], etc) @@ -96,9 +100,11 @@ object Def extends Init[Scope] with TaskMacroExtra { * Constructs the String of a given [[Reference]] relative to current. * Note that this no longer takes "multi" parameter, and omits the subproject id at all times. */ - private[sbt] def displayRelative(current: ProjectRef, - project: Reference, - trailingSlash: Boolean): String = { + private[sbt] def displayRelative( + current: ProjectRef, + project: Reference, + trailingSlash: Boolean + ): String = { val trailing = if (trailingSlash) " /" else "" project match { case BuildRef(current.build) => "ThisBuild" + trailing @@ -145,11 +151,14 @@ object Def extends Init[Scope] with TaskMacroExtra { else None) orElse s.dependencies .find(k => k.scope != ThisScope) - .map(k => - s"Scope cannot be defined for dependency ${k.key.label} of ${definedSettingString(s)}") + .map( + k => + s"Scope cannot be defined for dependency ${k.key.label} of ${definedSettingString(s)}" + ) override def intersect(s1: Scope, s2: Scope)( - implicit delegates: Scope => Seq[Scope]): Option[Scope] = + implicit delegates: Scope => Seq[Scope] + ): Option[Scope] = if (s2 == GlobalScope) Some(s1) // s1 is more specific else if (s1 == GlobalScope) Some(s2) // s2 is more specific else super.intersect(s1, s2) @@ -230,7 +239,8 @@ object Def extends Init[Scope] with TaskMacroExtra { private[sbt] def dummyTask[T](name: String): Task[T] = { import std.TaskExtra.{ task => newTask, _ } val base: Task[T] = newTask( - sys.error("Dummy task '" + name + "' did not get converted to a full task.")) named name + sys.error("Dummy task '" + name + "' did not get converted to a full task.") + ) named name base.copy(info = base.info.set(isDummyTask, true)) } @@ -240,13 +250,15 @@ object Def extends Init[Scope] with TaskMacroExtra { private[sbt] val isDummyTask = AttributeKey[Boolean]( "is-dummy-task", "Internal: used to identify dummy tasks. sbt injects values for these tasks at the start of task execution.", - Invisible) + Invisible + ) private[sbt] val (stateKey, dummyState) = dummy[State]("state", "Current build state.") private[sbt] val (streamsManagerKey, dummyStreamsManager) = Def.dummy[std.Streams[ScopedKey[_]]]( "streams-manager", - "Streams manager, which provides streams for different contexts.") + "Streams manager, which provides streams for different contexts." + ) } // these need to be mixed into the sbt package object diff --git a/main-settings/src/main/scala/sbt/DelegateIndex.scala b/main-settings/src/main/scala/sbt/DelegateIndex.scala index a5a6f64c7..4915a7886 100644 --- a/main-settings/src/main/scala/sbt/DelegateIndex.scala +++ b/main-settings/src/main/scala/sbt/DelegateIndex.scala @@ -26,6 +26,8 @@ private final class DelegateIndex0(refs: Map[ProjectRef, ProjectDelegates]) exte case None => Select(conf) :: Zero :: Nil } } -private final class ProjectDelegates(val ref: ProjectRef, - val refs: Seq[ScopeAxis[ResolvedReference]], - val confs: Map[ConfigKey, Seq[ScopeAxis[ConfigKey]]]) +private final class ProjectDelegates( + val ref: ProjectRef, + val refs: Seq[ScopeAxis[ResolvedReference]], + val confs: Map[ConfigKey, Seq[ScopeAxis[ConfigKey]]] +) diff --git a/main-settings/src/main/scala/sbt/InputTask.scala b/main-settings/src/main/scala/sbt/InputTask.scala index 2a722de45..c10315d0c 100644 --- a/main-settings/src/main/scala/sbt/InputTask.scala +++ b/main-settings/src/main/scala/sbt/InputTask.scala @@ -22,13 +22,15 @@ final class InputTask[T] private (val parser: State => Parser[Task[T]]) { new InputTask[T](s => Parser(parser(s))(in)) def fullInput(in: String): InputTask[T] = - new InputTask[T](s => - Parser.parse(in, parser(s)) match { - case Right(v) => Parser.success(v) - case Left(msg) => - val indented = msg.lines.map(" " + _).mkString("\n") - Parser.failure(s"Invalid programmatic input:\n$indented") - }) + new InputTask[T]( + s => + Parser.parse(in, parser(s)) match { + case Right(v) => Parser.success(v) + case Left(msg) => + val indented = msg.lines.map(" " + _).mkString("\n") + Parser.failure(s"Invalid programmatic input:\n$indented") + } + ) } object InputTask { @@ -38,14 +40,18 @@ object InputTask { import std.FullInstance._ def toTask(in: String): Initialize[Task[T]] = flatten( - (Def.stateKey zipWith i)((sTask, it) => - sTask map (s => - Parser.parse(in, it.parser(s)) match { - case Right(t) => Def.value(t) - case Left(msg) => - val indented = msg.lines.map(" " + _).mkString("\n") - sys.error(s"Invalid programmatic input:\n$indented") - })) + (Def.stateKey zipWith i)( + (sTask, it) => + sTask map ( + s => + Parser.parse(in, it.parser(s)) match { + case Right(t) => Def.value(t) + case Left(msg) => + val indented = msg.lines.map(" " + _).mkString("\n") + sys.error(s"Invalid programmatic input:\n$indented") + } + ) + ) ) } @@ -67,12 +73,14 @@ object InputTask { def free[I, T](p: State => Parser[I])(c: I => Task[T]): InputTask[T] = free(s => p(s) map c) - def separate[I, T](p: State => Parser[I])( - action: Initialize[I => Task[T]]): Initialize[InputTask[T]] = + def separate[I, T]( + p: State => Parser[I] + )(action: Initialize[I => Task[T]]): Initialize[InputTask[T]] = separate(Def value p)(action) - def separate[I, T](p: Initialize[State => Parser[I]])( - action: Initialize[I => Task[T]]): Initialize[InputTask[T]] = + def separate[I, T]( + p: Initialize[State => Parser[I]] + )(action: Initialize[I => Task[T]]): Initialize[InputTask[T]] = p.zipWith(action)((parser, act) => free(parser)(act)) /** Constructs an InputTask that accepts no user input. */ @@ -86,8 +94,9 @@ object InputTask { * a) a Parser constructed using other Settings, but not Tasks * b) a dynamically constructed Task that uses Settings, Tasks, and the result of parsing. */ - def createDyn[I, T](p: Initialize[State => Parser[I]])( - action: Initialize[Task[I => Initialize[Task[T]]]]): Initialize[InputTask[T]] = + def createDyn[I, T]( + p: Initialize[State => Parser[I]] + )(action: Initialize[Task[I => Initialize[Task[T]]]]): Initialize[InputTask[T]] = separate(p)(std.FullInstance.flattenFun[I, T](action)) /** A dummy parser that consumes no input and produces nothing useful (unit).*/ @@ -103,8 +112,9 @@ object InputTask { i(Types.const) @deprecated("Use another InputTask constructor or the `Def.inputTask` macro.", "0.13.0") - def apply[I, T](p: Initialize[State => Parser[I]])( - action: TaskKey[I] => Initialize[Task[T]]): Initialize[InputTask[T]] = { + def apply[I, T]( + p: Initialize[State => Parser[I]] + )(action: TaskKey[I] => Initialize[Task[T]]): Initialize[InputTask[T]] = { val dummyKey = localKey[Task[I]] val (marker, dummy) = dummyTask[I] val it = action(TaskKey(dummyKey)) mapConstant subResultForDummy(dummyKey, dummy) @@ -141,9 +151,11 @@ object InputTask { (key, t) } - private[this] def subForDummy[I, T](marker: AttributeKey[Option[I]], - value: I, - task: Task[T]): Task[T] = { + private[this] def subForDummy[I, T]( + marker: AttributeKey[Option[I]], + value: I, + task: Task[T] + ): Task[T] = { val seen = new java.util.IdentityHashMap[Task[_], Task[_]] lazy val f: Task ~> Task = new (Task ~> Task) { def apply[A](t: Task[A]): Task[A] = { diff --git a/main-settings/src/main/scala/sbt/Previous.scala b/main-settings/src/main/scala/sbt/Previous.scala index 62bfd705f..a23afbcbf 100644 --- a/main-settings/src/main/scala/sbt/Previous.scala +++ b/main-settings/src/main/scala/sbt/Previous.scala @@ -53,11 +53,13 @@ object Previous { private[sbt] val references = SettingKey[References]( "previous-references", "Collects all static references to previous values of tasks.", - KeyRanks.Invisible) + KeyRanks.Invisible + ) private[sbt] val cache = TaskKey[Previous]( "previous-cache", "Caches previous values of tasks read from disk for the duration of a task execution.", - KeyRanks.Invisible) + KeyRanks.Invisible + ) /** Records references to previous task value. This should be completely populated after settings finish loading. */ private[sbt] final class References { @@ -72,9 +74,11 @@ object Previous { } /** Persists values of tasks t where there is some task referencing it via t.previous. */ - private[sbt] def complete(referenced: References, - results: RMap[Task, Result], - streams: Streams): Unit = { + private[sbt] def complete( + referenced: References, + results: RMap[Task, Result], + streams: Streams + ): Unit = { val map = referenced.getReferences def impl[T](key: ScopedKey[_], result: T): Unit = for (i <- map.get(key.asInstanceOf[ScopedTaskKey[T]])) { diff --git a/main-settings/src/main/scala/sbt/Remove.scala b/main-settings/src/main/scala/sbt/Remove.scala index ad2a4a0fe..60c395050 100644 --- a/main-settings/src/main/scala/sbt/Remove.scala +++ b/main-settings/src/main/scala/sbt/Remove.scala @@ -11,12 +11,14 @@ import scala.annotation.implicitNotFound object Remove { @implicitNotFound( - msg = "No implicit for Remove.Value[${A}, ${B}] found,\n so ${B} cannot be removed from ${A}") + msg = "No implicit for Remove.Value[${A}, ${B}] found,\n so ${B} cannot be removed from ${A}" + ) trait Value[A, B] extends Any { def removeValue(a: A, b: B): A } @implicitNotFound( - msg = "No implicit for Remove.Values[${A}, ${B}] found,\n so ${B} cannot be removed from ${A}") + msg = "No implicit for Remove.Values[${A}, ${B}] found,\n so ${B} cannot be removed from ${A}" + ) trait Values[A, -B] extends Any { def removeValues(a: A, b: B): A } diff --git a/main-settings/src/main/scala/sbt/Scope.scala b/main-settings/src/main/scala/sbt/Scope.scala index 09eafc92b..4e8747d10 100644 --- a/main-settings/src/main/scala/sbt/Scope.scala +++ b/main-settings/src/main/scala/sbt/Scope.scala @@ -13,10 +13,12 @@ import sbt.internal.util.{ AttributeKey, AttributeMap, Dag } import sbt.io.IO -final case class Scope(project: ScopeAxis[Reference], - config: ScopeAxis[ConfigKey], - task: ScopeAxis[AttributeKey[_]], - extra: ScopeAxis[AttributeMap]) { +final case class Scope( + project: ScopeAxis[Reference], + config: ScopeAxis[ConfigKey], + task: ScopeAxis[AttributeKey[_]], + extra: ScopeAxis[AttributeMap] +) { def in(project: Reference, config: ConfigKey): Scope = copy(project = Select(project), config = Select(config)) def in(config: ConfigKey, task: AttributeKey[_]): Scope = @@ -106,17 +108,21 @@ object Scope { else IO.directoryURI(current resolve uri) - def resolveReference(current: URI, - rootProject: URI => String, - ref: Reference): ResolvedReference = + def resolveReference( + current: URI, + rootProject: URI => String, + ref: Reference + ): ResolvedReference = ref match { case br: BuildReference => resolveBuildRef(current, br) case pr: ProjectReference => resolveProjectRef(current, rootProject, pr) } - def resolveProjectRef(current: URI, - rootProject: URI => String, - ref: ProjectReference): ProjectRef = + def resolveProjectRef( + current: URI, + rootProject: URI => String, + ref: ProjectReference + ): ProjectRef = ref match { case LocalRootProject => ProjectRef(current, rootProject(current)) case LocalProject(id) => ProjectRef(current, id) @@ -164,10 +170,12 @@ object Scope { def displayMasked(scope: Scope, sep: String, mask: ScopeMask, showZeroConfig: Boolean): String = displayMasked(scope, sep, showProject, mask, showZeroConfig) - def displayMasked(scope: Scope, - sep: String, - showProject: Reference => String, - mask: ScopeMask): String = + def displayMasked( + scope: Scope, + sep: String, + showProject: Reference => String, + mask: ScopeMask + ): String = displayMasked(scope, sep, showProject, mask, false) /** @@ -177,11 +185,13 @@ object Scope { * Technically speaking an unspecified configuration axis defaults to * the scope delegation (first configuration defining the key, then Zero). */ - def displayMasked(scope: Scope, - sep: String, - showProject: Reference => String, - mask: ScopeMask, - showZeroConfig: Boolean): String = { + def displayMasked( + scope: Scope, + sep: String, + showProject: Reference => String, + mask: ScopeMask, + showZeroConfig: Boolean + ): String = { import scope.{ project, config, task, extra } val zeroConfig = if (showZeroConfig) "Zero /" else "" val configPrefix = config.foldStrict(display, zeroConfig, "./") @@ -190,11 +200,13 @@ object Scope { val postfix = if (extras.isEmpty) "" else extras.mkString("(", ", ", ")") if (scope == GlobalScope) "Global / " + sep + postfix else - mask.concatShow(appendSpace(projectPrefix(project, showProject)), - appendSpace(configPrefix), - appendSpace(taskPrefix), - sep, - postfix) + mask.concatShow( + appendSpace(projectPrefix(project, showProject)), + appendSpace(configPrefix), + appendSpace(taskPrefix), + sep, + postfix + ) } private[sbt] def appendSpace(s: String): String = @@ -207,12 +219,16 @@ object Scope { (!mask.task || a.task == b.task) && (!mask.extra || a.extra == b.extra) - def projectPrefix(project: ScopeAxis[Reference], - show: Reference => String = showProject): String = + def projectPrefix( + project: ScopeAxis[Reference], + show: Reference => String = showProject + ): String = project.foldStrict(show, "Zero /", "./") - def projectPrefix012Style(project: ScopeAxis[Reference], - show: Reference => String = showProject): String = + def projectPrefix012Style( + project: ScopeAxis[Reference], + show: Reference => String = showProject + ): String = project.foldStrict(show, "*/", "./") def showProject = (ref: Reference) => Reference.display(ref) + " /" @@ -332,27 +348,32 @@ object Scope { } private[this] def delegateIndex(ref: ProjectRef, confs: Seq[ConfigKey])( projectInherit: ProjectRef => Seq[ProjectRef], - configInherit: (ResolvedReference, ConfigKey) => Seq[ConfigKey]): ProjectDelegates = { + configInherit: (ResolvedReference, ConfigKey) => Seq[ConfigKey] + ): ProjectDelegates = { val refDelegates = withRawBuilds(linearize(Select(ref), false)(projectInherit)) val configs = confs map { c => axisDelegates(configInherit, ref, c) } new ProjectDelegates(ref, refDelegates, configs.toMap) } - def axisDelegates[T](direct: (ResolvedReference, T) => Seq[T], - ref: ResolvedReference, - init: T): (T, Seq[ScopeAxis[T]]) = + def axisDelegates[T]( + direct: (ResolvedReference, T) => Seq[T], + ref: ResolvedReference, + init: T + ): (T, Seq[ScopeAxis[T]]) = (init, linearize(Select(init))(direct(ref, _))) def linearize[T](axis: ScopeAxis[T], appendZero: Boolean = true)( - inherit: T => Seq[T]): Seq[ScopeAxis[T]] = + inherit: T => Seq[T] + ): Seq[ScopeAxis[T]] = axis match { case Select(x) => topologicalSort[T](x, appendZero)(inherit) case Zero | This => if (appendZero) Zero :: Nil else Nil } def topologicalSort[T](node: T, appendZero: Boolean)( - dependencies: T => Seq[T]): Seq[ScopeAxis[T]] = { + dependencies: T => Seq[T] + ): Seq[ScopeAxis[T]] = { val o = Dag.topologicalSortUnchecked(node)(dependencies).map(Select.apply) if (appendZero) o ::: Zero :: Nil else o diff --git a/main-settings/src/main/scala/sbt/Structure.scala b/main-settings/src/main/scala/sbt/Structure.scala index a855a6014..4830cfc54 100644 --- a/main-settings/src/main/scala/sbt/Structure.scala +++ b/main-settings/src/main/scala/sbt/Structure.scala @@ -337,8 +337,10 @@ object Scoped { def transform(f: S => S, source: SourcePosition): Setting[Task[S]] = set(scopedKey(_ map f), source) - @deprecated("No longer needed with new task syntax and SettingKey inheriting from Initialize.", - "0.13.2") + @deprecated( + "No longer needed with new task syntax and SettingKey inheriting from Initialize.", + "0.13.2" + ) def task: SettingKey[Task[S]] = scopedSetting(scope, key) def toSettingKey: SettingKey[Task[S]] = scopedSetting(scope, key) @@ -401,8 +403,9 @@ object Scoped { def dependsOn(tasks: AnyInitTask*): Initialize[InputTask[S]] = { import TupleSyntax._ - (i, Initialize.joinAny[Task](tasks))((thisTask, deps) => - thisTask.mapTask(_.dependsOn(deps: _*))) + (i, Initialize.joinAny[Task](tasks))( + (thisTask, deps) => thisTask.mapTask(_.dependsOn(deps: _*)) + ) } } @@ -429,23 +432,27 @@ object Scoped { @deprecated( "Use the `result` method to create a task that returns the full Result of this task. Then, call `flatMap` on the new task.", - "0.13.0") + "0.13.0" + ) def flatMapR[T](f: Result[S] => Task[T]): Initialize[R[T]] = onTask(_.result flatMap f) @deprecated( "Use the `result` method to create a task that returns the full Result of this task. Then, call `map` on the new task.", - "0.13.0") + "0.13.0" + ) def mapR[T](f: Result[S] => T): Initialize[R[T]] = onTask(_.result map f) @deprecated( "Use the `failure` method to create a task that returns Incomplete when this task fails and then call `flatMap` on the new task.", - "0.13.0") + "0.13.0" + ) def flatFailure[T](f: Incomplete => Task[T]): Initialize[R[T]] = onTask(_.result flatMap (f compose failM)) @deprecated( "Use the `failure` method to create a task that returns Incomplete when this task fails and then call `map` on the new task.", - "0.13.0") + "0.13.0" + ) def mapFailure[T](f: Incomplete => T): Initialize[R[T]] = onTask(_.result map (f compose failM)) } diff --git a/main-settings/src/main/scala/sbt/std/InputWrapper.scala b/main-settings/src/main/scala/sbt/std/InputWrapper.scala index be34721ae..c1cc5dbac 100644 --- a/main-settings/src/main/scala/sbt/std/InputWrapper.scala +++ b/main-settings/src/main/scala/sbt/std/InputWrapper.scala @@ -31,27 +31,33 @@ object InputWrapper { private[std] final val WrapPreviousName = "wrapPrevious_\u2603\u2603" @compileTimeOnly( - "`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task.") + "`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task." + ) def wrapTask_\u2603\u2603[T](@deprecated("unused", "") in: Any): T = implDetailError @compileTimeOnly( - "`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.") + "`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting." + ) def wrapInit_\u2603\u2603[T](@deprecated("unused", "") in: Any): T = implDetailError @compileTimeOnly( - "`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task.") + "`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task." + ) def wrapInitTask_\u2603\u2603[T](@deprecated("unused", "") in: Any): T = implDetailError @compileTimeOnly( - "`value` can only be called on an input task within a task definition macro, such as := or Def.inputTask.") + "`value` can only be called on an input task within a task definition macro, such as := or Def.inputTask." + ) def wrapInputTask_\u2603\u2603[T](@deprecated("unused", "") in: Any): T = implDetailError @compileTimeOnly( - "`value` can only be called on an input task within a task definition macro, such as := or Def.inputTask.") + "`value` can only be called on an input task within a task definition macro, such as := or Def.inputTask." + ) def wrapInitInputTask_\u2603\u2603[T](@deprecated("unused", "") in: Any): T = implDetailError @compileTimeOnly( - "`previous` can only be called on a task within a task or input task definition macro, such as :=, +=, ++=, Def.task, or Def.inputTask.") + "`previous` can only be called on a task within a task or input task definition macro, such as :=, +=, ++=, Def.task, or Def.inputTask." + ) def wrapPrevious_\u2603\u2603[T](@deprecated("unused", "") in: Any): T = implDetailError private[this] def implDetailError = @@ -161,8 +167,9 @@ object InputWrapper { } /** Translates .previous(format) to Previous.runtime()(format).value*/ - def previousMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)( - format: c.Expr[sjsonnew.JsonFormat[T]]): c.Expr[Option[T]] = { + def previousMacroImpl[T: c.WeakTypeTag]( + c: blackbox.Context + )(format: c.Expr[sjsonnew.JsonFormat[T]]): c.Expr[Option[T]] = { import c.universe._ c.macroApplication match { case a @ Apply(Select(Apply(_, t :: Nil), _), _) => @@ -182,35 +189,42 @@ object InputWrapper { sealed abstract class MacroTaskValue[T] { @compileTimeOnly( - "`taskValue` can only be used within a setting macro, such as :=, +=, ++=, or Def.setting.") + "`taskValue` can only be used within a setting macro, such as :=, +=, ++=, or Def.setting." + ) def taskValue: Task[T] = macro InputWrapper.taskValueMacroImpl[T] } sealed abstract class MacroValue[T] { @compileTimeOnly( - "`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.") + "`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting." + ) def value: T = macro InputWrapper.valueMacroImpl[T] } sealed abstract class ParserInput[T] { @compileTimeOnly( - "`parsed` can only be used within an input task macro, such as := or Def.inputTask.") + "`parsed` can only be used within an input task macro, such as := or Def.inputTask." + ) def parsed: T = macro ParserInput.parsedMacroImpl[T] } sealed abstract class InputEvaluated[T] { @compileTimeOnly( - "`evaluated` can only be used within an input task macro, such as := or Def.inputTask.") + "`evaluated` can only be used within an input task macro, such as := or Def.inputTask." + ) def evaluated: T = macro InputWrapper.valueMacroImpl[T] @compileTimeOnly( - "`inputTaskValue` can only be used within an input task macro, such as := or Def.inputTask.") + "`inputTaskValue` can only be used within an input task macro, such as := or Def.inputTask." + ) def inputTaskValue: InputTask[T] = macro InputWrapper.inputTaskValueMacroImpl[T] } sealed abstract class ParserInputTask[T] { @compileTimeOnly( - "`parsed` can only be used within an input task macro, such as := or Def.inputTask.") + "`parsed` can only be used within an input task macro, such as := or Def.inputTask." + ) def parsed: Task[T] = macro ParserInput.parsedInputMacroImpl[T] } sealed abstract class MacroPrevious[T] { @compileTimeOnly( - "`previous` can only be used within a task macro, such as :=, +=, ++=, or Def.task.") + "`previous` can only be used within a task macro, such as :=, +=, ++=, or Def.task." + ) def previous(implicit format: sjsonnew.JsonFormat[T]): Option[T] = macro InputWrapper.previousMacroImpl[T] } @@ -224,24 +238,29 @@ object ParserInput { private[std] val WrapInitName = "initParser_\u2603\u2603" @compileTimeOnly( - "`parsed` can only be used within an input task macro, such as := or Def.inputTask.") + "`parsed` can only be used within an input task macro, such as := or Def.inputTask." + ) def parser_\u2603\u2603[T](@deprecated("unused", "") i: Any): T = sys.error("This method is an implementation detail and should not be referenced.") @compileTimeOnly( - "`parsed` can only be used within an input task macro, such as := or Def.inputTask.") + "`parsed` can only be used within an input task macro, such as := or Def.inputTask." + ) def initParser_\u2603\u2603[T](@deprecated("unused", "") i: Any): T = sys.error("This method is an implementation detail and should not be referenced.") - private[std] def wrap[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any], - pos: c.Position): c.Expr[T] = + private[std] def wrap[T: c.WeakTypeTag]( + c: blackbox.Context + )(ts: c.Expr[Any], pos: c.Position): c.Expr[T] = InputWrapper.wrapImpl[T, ParserInput.type](c, ParserInput, WrapName)(ts, pos) - private[std] def wrapInit[T: c.WeakTypeTag](c: blackbox.Context)(ts: c.Expr[Any], - pos: c.Position): c.Expr[T] = + private[std] def wrapInit[T: c.WeakTypeTag]( + c: blackbox.Context + )(ts: c.Expr[Any], pos: c.Position): c.Expr[T] = InputWrapper.wrapImpl[T, ParserInput.type](c, ParserInput, WrapInitName)(ts, pos) - private[std] def inputParser[T: c.WeakTypeTag](c: blackbox.Context)( - t: c.Expr[InputTask[T]]): c.Expr[State => Parser[Task[T]]] = + private[std] def inputParser[T: c.WeakTypeTag]( + c: blackbox.Context + )(t: c.Expr[InputTask[T]]): c.Expr[State => Parser[Task[T]]] = c.universe.reify(t.splice.parser) def parsedInputMacroImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[Task[T]] = @@ -261,8 +280,9 @@ object ParserInput { wrap[Task[T]](c)(inputParser(c)(e), pos) } - private def wrapInitInputTask[T: c.WeakTypeTag](c: blackbox.Context)(tree: c.Tree, - pos: c.Position) = { + private def wrapInitInputTask[T: c.WeakTypeTag]( + c: blackbox.Context + )(tree: c.Tree, pos: c.Position) = { val e = c.Expr[Initialize[InputTask[T]]](tree) wrapInit[Task[T]](c)(c.universe.reify { Def.toIParser(e.splice) }, pos) } diff --git a/main-settings/src/main/scala/sbt/std/KeyMacro.scala b/main-settings/src/main/scala/sbt/std/KeyMacro.scala index 697963ab0..4a57fbb2d 100644 --- a/main-settings/src/main/scala/sbt/std/KeyMacro.scala +++ b/main-settings/src/main/scala/sbt/std/KeyMacro.scala @@ -14,18 +14,21 @@ import scala.reflect.macros._ import sbt.util.OptJsonWriter private[sbt] object KeyMacro { - def settingKeyImpl[T: c.WeakTypeTag](c: blackbox.Context)( - description: c.Expr[String]): c.Expr[SettingKey[T]] = + def settingKeyImpl[T: c.WeakTypeTag]( + c: blackbox.Context + )(description: c.Expr[String]): c.Expr[SettingKey[T]] = keyImpl2[T, SettingKey[T]](c) { (name, mf, ojw) => c.universe.reify { SettingKey[T](name.splice, description.splice)(mf.splice, ojw.splice) } } - def taskKeyImpl[T: c.WeakTypeTag](c: blackbox.Context)( - description: c.Expr[String]): c.Expr[TaskKey[T]] = + def taskKeyImpl[T: c.WeakTypeTag]( + c: blackbox.Context + )(description: c.Expr[String]): c.Expr[TaskKey[T]] = keyImpl[T, TaskKey[T]](c) { (name, mf) => c.universe.reify { TaskKey[T](name.splice, description.splice)(mf.splice) } } - def inputKeyImpl[T: c.WeakTypeTag](c: blackbox.Context)( - description: c.Expr[String]): c.Expr[InputKey[T]] = + def inputKeyImpl[T: c.WeakTypeTag]( + c: blackbox.Context + )(description: c.Expr[String]): c.Expr[InputKey[T]] = keyImpl[T, InputKey[T]](c) { (name, mf) => c.universe.reify { InputKey[T](name.splice, description.splice)(mf.splice) } } @@ -45,7 +48,8 @@ private[sbt] object KeyMacro { val enclosingValName = definingValName( c, methodName => - s"""$methodName must be directly assigned to a val, such as `val x = $methodName[Int]("description")`.""") + s"""$methodName must be directly assigned to a val, such as `val x = $methodName[Int]("description")`.""" + ) c.Expr[String](Literal(Constant(enclosingValName))) } diff --git a/main-settings/src/main/scala/sbt/std/SettingMacro.scala b/main-settings/src/main/scala/sbt/std/SettingMacro.scala index 23b9e51e0..d80b85242 100644 --- a/main-settings/src/main/scala/sbt/std/SettingMacro.scala +++ b/main-settings/src/main/scala/sbt/std/SettingMacro.scala @@ -46,11 +46,13 @@ object InitializeConvert extends Convert { Converted.Success(t) } - private def failTask[C <: blackbox.Context with Singleton](c: C)( - pos: c.Position): Converted[c.type] = + private def failTask[C <: blackbox.Context with Singleton]( + c: C + )(pos: c.Position): Converted[c.type] = Converted.Failure(pos, "A setting cannot depend on a task") - private def failPrevious[C <: blackbox.Context with Singleton](c: C)( - pos: c.Position): Converted[c.type] = + private def failPrevious[C <: blackbox.Context with Singleton]( + c: C + )(pos: c.Position): Converted[c.type] = Converted.Failure(pos, "A setting cannot depend on a task's previous value.") } @@ -59,11 +61,14 @@ object SettingMacro { def settingMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[Initialize[T]] = Instance.contImpl[T, Id](c, InitializeInstance, InitializeConvert, MixedBuilder, EmptyLinter)( Left(t), - Instance.idTransform[c.type]) + Instance.idTransform[c.type] + ) - def settingDynMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)( - t: c.Expr[Initialize[T]]): c.Expr[Initialize[T]] = + def settingDynMacroImpl[T: c.WeakTypeTag]( + c: blackbox.Context + )(t: c.Expr[Initialize[T]]): c.Expr[Initialize[T]] = Instance.contImpl[T, Id](c, InitializeInstance, InitializeConvert, MixedBuilder, EmptyLinter)( Right(t), - Instance.idTransform[c.type]) + Instance.idTransform[c.type] + ) } diff --git a/main-settings/src/main/scala/sbt/std/TaskMacro.scala b/main-settings/src/main/scala/sbt/std/TaskMacro.scala index 562a685f5..e77658925 100644 --- a/main-settings/src/main/scala/sbt/std/TaskMacro.scala +++ b/main-settings/src/main/scala/sbt/std/TaskMacro.scala @@ -56,9 +56,11 @@ object FullInstance extends Instance.Composed[Initialize, Task](InitializeInstance, TaskInstance) with MonadInstance { type SS = sbt.internal.util.Settings[Scope] - val settingsData = TaskKey[SS]("settings-data", - "Provides access to the project data for the build.", - KeyRanks.DTask) + val settingsData = TaskKey[SS]( + "settings-data", + "Provides access to the project data for the build.", + KeyRanks.DTask + ) def flatten[T](in: Initialize[Task[Initialize[Task[T]]]]): Initialize[Task[T]] = { import TupleSyntax._ @@ -98,29 +100,35 @@ object TaskMacro { import LinterDSL.{ Empty => EmptyLinter } - def taskMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)( - t: c.Expr[T]): c.Expr[Initialize[Task[T]]] = + def taskMacroImpl[T: c.WeakTypeTag]( + c: blackbox.Context + )(t: c.Expr[T]): c.Expr[Initialize[Task[T]]] = Instance.contImpl[T, Id](c, FullInstance, FullConvert, MixedBuilder, TaskLinterDSL)( Left(t), - Instance.idTransform[c.type]) + Instance.idTransform[c.type] + ) - def taskDynMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)( - t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[Task[T]]] = + def taskDynMacroImpl[T: c.WeakTypeTag]( + c: blackbox.Context + )(t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[Task[T]]] = Instance.contImpl[T, Id](c, FullInstance, FullConvert, MixedBuilder, TaskDynLinterDSL)( Right(t), - Instance.idTransform[c.type]) + Instance.idTransform[c.type] + ) /** Implementation of := macro for settings. */ - def settingAssignMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)( - v: c.Expr[T]): c.Expr[Setting[T]] = { + def settingAssignMacroImpl[T: c.WeakTypeTag]( + c: blackbox.Context + )(v: c.Expr[T]): c.Expr[Setting[T]] = { val init = SettingMacro.settingMacroImpl[T](c)(v) val assign = transformMacroImpl(c)(init.tree)(AssignInitName) c.Expr[Setting[T]](assign) } /** Implementation of := macro for tasks. */ - def taskAssignMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)( - v: c.Expr[T]): c.Expr[Setting[Task[T]]] = { + def taskAssignMacroImpl[T: c.WeakTypeTag]( + c: blackbox.Context + )(v: c.Expr[T]): c.Expr[Setting[Task[T]]] = { val init = taskMacroImpl[T](c)(v) val assign = transformMacroImpl(c)(init.tree)(AssignInitName) c.Expr[Setting[Task[T]]](assign) @@ -134,14 +142,16 @@ object TaskMacro { ): c.Expr[Setting[T]] = ContextUtil.selectMacroImpl[Setting[T]](c)((_, pos) => c.abort(pos, assignMigration)) - def fakeSettingAppend1Position[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)( - @deprecated("unused", "") v: c.Expr[Initialize[V]])( + def fakeSettingAppend1Position[S: c.WeakTypeTag, V: c.WeakTypeTag]( + c: blackbox.Context + )(@deprecated("unused", "") v: c.Expr[Initialize[V]])( @deprecated("unused", "") a: c.Expr[Append.Value[S, V]] ): c.Expr[Setting[S]] = ContextUtil.selectMacroImpl[Setting[S]](c)((_, pos) => c.abort(pos, append1Migration)) - def fakeSettingAppendNPosition[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)( - @deprecated("unused", "") vs: c.Expr[Initialize[V]])( + def fakeSettingAppendNPosition[S: c.WeakTypeTag, V: c.WeakTypeTag]( + c: blackbox.Context + )(@deprecated("unused", "") vs: c.Expr[Initialize[V]])( @deprecated("unused", "") a: c.Expr[Append.Values[S, V]] ): c.Expr[Setting[S]] = ContextUtil.selectMacroImpl[Setting[S]](c)((_, pos) => c.abort(pos, appendNMigration)) @@ -151,14 +161,16 @@ object TaskMacro { ): c.Expr[Setting[Task[T]]] = ContextUtil.selectMacroImpl[Setting[Task[T]]](c)((_, pos) => c.abort(pos, assignMigration)) - def fakeTaskAppend1Position[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)( - @deprecated("unused", "") v: c.Expr[Initialize[Task[V]]])( + def fakeTaskAppend1Position[S: c.WeakTypeTag, V: c.WeakTypeTag]( + c: blackbox.Context + )(@deprecated("unused", "") v: c.Expr[Initialize[Task[V]]])( @deprecated("unused", "") a: c.Expr[Append.Value[S, V]] ): c.Expr[Setting[Task[S]]] = ContextUtil.selectMacroImpl[Setting[Task[S]]](c)((_, pos) => c.abort(pos, append1Migration)) - def fakeTaskAppendNPosition[S: c.WeakTypeTag, V: c.WeakTypeTag](c: blackbox.Context)( - @deprecated("unused", "") vs: c.Expr[Initialize[Task[V]]])( + def fakeTaskAppendNPosition[S: c.WeakTypeTag, V: c.WeakTypeTag]( + c: blackbox.Context + )(@deprecated("unused", "") vs: c.Expr[Initialize[Task[V]]])( @deprecated("unused", "") a: c.Expr[Append.Values[S, V]] ): c.Expr[Setting[Task[S]]] = ContextUtil.selectMacroImpl[Setting[Task[S]]](c)((_, pos) => c.abort(pos, appendNMigration)) @@ -166,56 +178,66 @@ object TaskMacro { // Implementations of <<= macro variations for tasks and settings. // These just get the source position of the call site. - def itaskAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)( - app: c.Expr[Initialize[Task[T]]]): c.Expr[Setting[Task[T]]] = + def itaskAssignPosition[T: c.WeakTypeTag]( + c: blackbox.Context + )(app: c.Expr[Initialize[Task[T]]]): c.Expr[Setting[Task[T]]] = settingAssignPosition(c)(app) - def taskAssignPositionT[T: c.WeakTypeTag](c: blackbox.Context)( - app: c.Expr[Task[T]]): c.Expr[Setting[Task[T]]] = + def taskAssignPositionT[T: c.WeakTypeTag]( + c: blackbox.Context + )(app: c.Expr[Task[T]]): c.Expr[Setting[Task[T]]] = itaskAssignPosition(c)(c.universe.reify { Def.valueStrict(app.splice) }) - def taskAssignPositionPure[T: c.WeakTypeTag](c: blackbox.Context)( - app: c.Expr[T]): c.Expr[Setting[Task[T]]] = + def taskAssignPositionPure[T: c.WeakTypeTag]( + c: blackbox.Context + )(app: c.Expr[T]): c.Expr[Setting[Task[T]]] = taskAssignPositionT(c)(c.universe.reify { TaskExtra.constant(app.splice) }) - def taskTransformPosition[S: c.WeakTypeTag](c: blackbox.Context)( - f: c.Expr[S => S]): c.Expr[Setting[Task[S]]] = + def taskTransformPosition[S: c.WeakTypeTag]( + c: blackbox.Context + )(f: c.Expr[S => S]): c.Expr[Setting[Task[S]]] = c.Expr[Setting[Task[S]]](transformMacroImpl(c)(f.tree)(TransformInitName)) - def settingTransformPosition[S: c.WeakTypeTag](c: blackbox.Context)( - f: c.Expr[S => S]): c.Expr[Setting[S]] = + def settingTransformPosition[S: c.WeakTypeTag]( + c: blackbox.Context + )(f: c.Expr[S => S]): c.Expr[Setting[S]] = c.Expr[Setting[S]](transformMacroImpl(c)(f.tree)(TransformInitName)) - def itaskTransformPosition[S: c.WeakTypeTag](c: blackbox.Context)( - f: c.Expr[S => S]): c.Expr[Setting[S]] = + def itaskTransformPosition[S: c.WeakTypeTag]( + c: blackbox.Context + )(f: c.Expr[S => S]): c.Expr[Setting[S]] = c.Expr[Setting[S]](transformMacroImpl(c)(f.tree)(TransformInitName)) def settingAssignPure[T: c.WeakTypeTag](c: blackbox.Context)(app: c.Expr[T]): c.Expr[Setting[T]] = settingAssignPosition(c)(c.universe.reify { Def.valueStrict(app.splice) }) - def settingAssignPosition[T: c.WeakTypeTag](c: blackbox.Context)( - app: c.Expr[Initialize[T]]): c.Expr[Setting[T]] = + def settingAssignPosition[T: c.WeakTypeTag]( + c: blackbox.Context + )(app: c.Expr[Initialize[T]]): c.Expr[Setting[T]] = c.Expr[Setting[T]](transformMacroImpl(c)(app.tree)(AssignInitName)) /** Implementation of := macro for tasks. */ - def inputTaskAssignMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)( - v: c.Expr[T]): c.Expr[Setting[InputTask[T]]] = { + def inputTaskAssignMacroImpl[T: c.WeakTypeTag]( + c: blackbox.Context + )(v: c.Expr[T]): c.Expr[Setting[InputTask[T]]] = { val init = inputTaskMacroImpl[T](c)(v) val assign = transformMacroImpl(c)(init.tree)(AssignInitName) c.Expr[Setting[InputTask[T]]](assign) } /** Implementation of += macro for tasks. */ - def taskAppend1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[U])( - a: c.Expr[Append.Value[T, U]]): c.Expr[Setting[Task[T]]] = { + def taskAppend1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag]( + c: blackbox.Context + )(v: c.Expr[U])(a: c.Expr[Append.Value[T, U]]): c.Expr[Setting[Task[T]]] = { val init = taskMacroImpl[U](c)(v) val append = appendMacroImpl(c)(init.tree, a.tree)(Append1InitName) c.Expr[Setting[Task[T]]](append) } /** Implementation of += macro for settings. */ - def settingAppend1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[U])( - a: c.Expr[Append.Value[T, U]]): c.Expr[Setting[T]] = { + def settingAppend1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag]( + c: blackbox.Context + )(v: c.Expr[U])(a: c.Expr[Append.Value[T, U]]): c.Expr[Setting[T]] = { import c.universe._ val ttpe = c.weakTypeOf[T] val typeArgs = ttpe.typeArgs @@ -228,7 +250,8 @@ object TaskMacro { case Apply(Apply(TypeApply(Select(preT, _), _), _), _) => val tree = Apply( TypeApply(Select(preT, TermName("+=").encodedName), TypeTree(typeArgs.head) :: Nil), - Select(v.tree, TermName("taskValue").encodedName) :: Nil) + Select(v.tree, TermName("taskValue").encodedName) :: Nil + ) c.Expr[Setting[T]](tree) case x => ContextUtil.unexpectedTree(x) } @@ -240,55 +263,62 @@ object TaskMacro { } /** Implementation of ++= macro for tasks. */ - def taskAppendNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[U])( - a: c.Expr[Append.Values[T, U]]): c.Expr[Setting[Task[T]]] = { + def taskAppendNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag]( + c: blackbox.Context + )(vs: c.Expr[U])(a: c.Expr[Append.Values[T, U]]): c.Expr[Setting[Task[T]]] = { val init = taskMacroImpl[U](c)(vs) val append = appendMacroImpl(c)(init.tree, a.tree)(AppendNInitName) c.Expr[Setting[Task[T]]](append) } /** Implementation of ++= macro for settings. */ - def settingAppendNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[U])( - a: c.Expr[Append.Values[T, U]]): c.Expr[Setting[T]] = { + def settingAppendNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag]( + c: blackbox.Context + )(vs: c.Expr[U])(a: c.Expr[Append.Values[T, U]]): c.Expr[Setting[T]] = { val init = SettingMacro.settingMacroImpl[U](c)(vs) val append = appendMacroImpl(c)(init.tree, a.tree)(AppendNInitName) c.Expr[Setting[T]](append) } /** Implementation of -= macro for tasks. */ - def taskRemove1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[U])( - r: c.Expr[Remove.Value[T, U]]): c.Expr[Setting[Task[T]]] = { + def taskRemove1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag]( + c: blackbox.Context + )(v: c.Expr[U])(r: c.Expr[Remove.Value[T, U]]): c.Expr[Setting[Task[T]]] = { val init = taskMacroImpl[U](c)(v) val remove = removeMacroImpl(c)(init.tree, r.tree)(Remove1InitName) c.Expr[Setting[Task[T]]](remove) } /** Implementation of -= macro for settings. */ - def settingRemove1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(v: c.Expr[U])( - r: c.Expr[Remove.Value[T, U]]): c.Expr[Setting[T]] = { + def settingRemove1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag]( + c: blackbox.Context + )(v: c.Expr[U])(r: c.Expr[Remove.Value[T, U]]): c.Expr[Setting[T]] = { val init = SettingMacro.settingMacroImpl[U](c)(v) val remove = removeMacroImpl(c)(init.tree, r.tree)(Remove1InitName) c.Expr[Setting[T]](remove) } /** Implementation of --= macro for tasks. */ - def taskRemoveNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[U])( - r: c.Expr[Remove.Values[T, U]]): c.Expr[Setting[Task[T]]] = { + def taskRemoveNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag]( + c: blackbox.Context + )(vs: c.Expr[U])(r: c.Expr[Remove.Values[T, U]]): c.Expr[Setting[Task[T]]] = { val init = taskMacroImpl[U](c)(vs) val remove = removeMacroImpl(c)(init.tree, r.tree)(RemoveNInitName) c.Expr[Setting[Task[T]]](remove) } /** Implementation of --= macro for settings. */ - def settingRemoveNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)(vs: c.Expr[U])( - r: c.Expr[Remove.Values[T, U]]): c.Expr[Setting[T]] = { + def settingRemoveNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag]( + c: blackbox.Context + )(vs: c.Expr[U])(r: c.Expr[Remove.Values[T, U]]): c.Expr[Setting[T]] = { val init = SettingMacro.settingMacroImpl[U](c)(vs) val remove = removeMacroImpl(c)(init.tree, r.tree)(RemoveNInitName) c.Expr[Setting[T]](remove) } - private[this] def appendMacroImpl(c: blackbox.Context)(init: c.Tree, append: c.Tree)( - newName: String): c.Tree = { + private[this] def appendMacroImpl( + c: blackbox.Context + )(init: c.Tree, append: c.Tree)(newName: String): c.Tree = { import c.universe._ c.macroApplication match { case Apply(Apply(TypeApply(Select(preT, _), targs), _), _) => @@ -303,8 +333,9 @@ object TaskMacro { } } - private[this] def removeMacroImpl(c: blackbox.Context)(init: c.Tree, remove: c.Tree)( - newName: String): c.Tree = { + private[this] def removeMacroImpl( + c: blackbox.Context + )(init: c.Tree, remove: c.Tree)(newName: String): c.Tree = { import c.universe._ c.macroApplication match { case Apply(Apply(TypeApply(Select(preT, _), targs), _), _) => @@ -328,8 +359,10 @@ object TaskMacro { case Apply(Select(prefix, _), _) => prefix case x => ContextUtil.unexpectedTree(x) } - Apply.apply(Select(target, TermName(newName).encodedName), - init :: sourcePosition(c).tree :: Nil) + Apply.apply( + Select(target, TermName(newName).encodedName), + init :: sourcePosition(c).tree :: Nil + ) } private[this] def sourcePosition(c: blackbox.Context): c.Expr[SourcePosition] = { @@ -347,7 +380,8 @@ object TaskMacro { private[this] def settingSource(c: blackbox.Context, path: String, name: String): String = { @tailrec def inEmptyPackage(s: c.Symbol): Boolean = s != c.universe.NoSymbol && ( s.owner == c.mirror.EmptyPackage || s.owner == c.mirror.EmptyPackageClass || inEmptyPackage( - s.owner) + s.owner + ) ) c.internal.enclosingOwner match { case ec if !ec.isStatic => name @@ -361,16 +395,19 @@ object TaskMacro { c.Expr[T](Literal(Constant(t))) } - def inputTaskMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)( - t: c.Expr[T]): c.Expr[Initialize[InputTask[T]]] = + def inputTaskMacroImpl[T: c.WeakTypeTag]( + c: blackbox.Context + )(t: c.Expr[T]): c.Expr[Initialize[InputTask[T]]] = inputTaskMacro0[T](c)(t) - def inputTaskDynMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)( - t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[InputTask[T]]] = + def inputTaskDynMacroImpl[T: c.WeakTypeTag]( + c: blackbox.Context + )(t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[InputTask[T]]] = inputTaskDynMacro0[T](c)(t) - private[this] def inputTaskMacro0[T: c.WeakTypeTag](c: blackbox.Context)( - t: c.Expr[T]): c.Expr[Initialize[InputTask[T]]] = + private[this] def inputTaskMacro0[T: c.WeakTypeTag]( + c: blackbox.Context + )(t: c.Expr[T]): c.Expr[Initialize[InputTask[T]]] = iInitializeMacro(c)(t) { et => val pt = iParserMacro(c)(et) { pt => iTaskMacro(c)(pt) @@ -379,8 +416,8 @@ object TaskMacro { } private[this] def iInitializeMacro[M[_], T](c: blackbox.Context)(t: c.Expr[T])( - f: c.Expr[T] => c.Expr[M[T]])(implicit tt: c.WeakTypeTag[T], - mt: c.WeakTypeTag[M[T]]): c.Expr[Initialize[M[T]]] = { + f: c.Expr[T] => c.Expr[M[T]] + )(implicit tt: c.WeakTypeTag[T], mt: c.WeakTypeTag[M[T]]): c.Expr[Initialize[M[T]]] = { val inner: Transform[c.type, M] = new Transform[c.type, M] { def apply(in: c.Tree): c.Tree = f(c.Expr[T](in)).tree } @@ -388,7 +425,8 @@ object TaskMacro { Instance .contImpl[T, M](c, InitializeInstance, InputInitConvert, MixedBuilder, EmptyLinter)( Left(cond), - inner) + inner + ) } private[this] def conditionInputTaskTree(c: blackbox.Context)(t: c.Tree): c.Tree = { @@ -424,25 +462,29 @@ object TaskMacro { } private[this] def iParserMacro[M[_], T](c: blackbox.Context)(t: c.Expr[T])( - f: c.Expr[T] => c.Expr[M[T]])(implicit tt: c.WeakTypeTag[T], - mt: c.WeakTypeTag[M[T]]): c.Expr[State => Parser[M[T]]] = { + f: c.Expr[T] => c.Expr[M[T]] + )(implicit tt: c.WeakTypeTag[T], mt: c.WeakTypeTag[M[T]]): c.Expr[State => Parser[M[T]]] = { val inner: Transform[c.type, M] = new Transform[c.type, M] { def apply(in: c.Tree): c.Tree = f(c.Expr[T](in)).tree } Instance.contImpl[T, M](c, ParserInstance, ParserConvert, MixedBuilder, LinterDSL.Empty)( Left(t), - inner) + inner + ) } - private[this] def iTaskMacro[T: c.WeakTypeTag](c: blackbox.Context)( - t: c.Expr[T]): c.Expr[Task[T]] = + private[this] def iTaskMacro[T: c.WeakTypeTag]( + c: blackbox.Context + )(t: c.Expr[T]): c.Expr[Task[T]] = Instance .contImpl[T, Id](c, TaskInstance, TaskConvert, MixedBuilder, EmptyLinter)( Left(t), - Instance.idTransform) + Instance.idTransform + ) - private[this] def inputTaskDynMacro0[T: c.WeakTypeTag](c: blackbox.Context)( - t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[InputTask[T]]] = { + private[this] def inputTaskDynMacro0[T: c.WeakTypeTag]( + c: blackbox.Context + )(t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[InputTask[T]]] = { import c.universe.{ Apply => ApplyTree, _ } import internal.decorators._ @@ -467,7 +509,8 @@ object TaskMacro { if (result.isDefined) { c.error( qual.pos, - "Implementation restriction: a dynamic InputTask can only have a single input parser.") + "Implementation restriction: a dynamic InputTask can only have a single input parser." + ) EmptyTree } else { qual.foreach(checkQual) @@ -526,11 +569,13 @@ object PlainTaskMacro { def taskImpl[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[Task[T]] = Instance.contImpl[T, Id](c, TaskInstance, TaskConvert, MixedBuilder, OnlyTaskLinterDSL)( Left(t), - Instance.idTransform[c.type]) + Instance.idTransform[c.type] + ) def taskDyn[T](t: Task[T]): Task[T] = macro taskDynImpl[T] def taskDynImpl[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[Task[T]]): c.Expr[Task[T]] = Instance.contImpl[T, Id](c, TaskInstance, TaskConvert, MixedBuilder, OnlyTaskDynLinterDSL)( Right(t), - Instance.idTransform[c.type]) + Instance.idTransform[c.type] + ) } diff --git a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala index b3b77c71d..45d61b6de 100644 --- a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala +++ b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala @@ -85,8 +85,10 @@ object SlashSyntaxSpec extends Properties("SlashSyntax") with SlashSyntax { } property("Reference? / ConfigKey? / key == key in ThisScope.copy(..)") = { - forAll((r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], k: Key) => - expectValue(k in ThisScope.copy(project = r, config = c))(r / c / k)) + forAll( + (r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], k: Key) => + expectValue(k in ThisScope.copy(project = r, config = c))(r / c / k) + ) } // property("Reference? / AttributeKey? / key == key in ThisScope.copy(..)") = { diff --git a/main/src/main/scala/sbt/BackgroundJobService.scala b/main/src/main/scala/sbt/BackgroundJobService.scala index 5af16058e..d06288b22 100644 --- a/main/src/main/scala/sbt/BackgroundJobService.scala +++ b/main/src/main/scala/sbt/BackgroundJobService.scala @@ -23,7 +23,8 @@ abstract class BackgroundJobService extends Closeable { * then you could process.destroy() for example. */ def runInBackground(spawningTask: ScopedKey[_], state: State)( - start: (Logger, File) => Unit): JobHandle + start: (Logger, File) => Unit + ): JobHandle /** Same as shutown. */ def close(): Unit @@ -51,7 +52,8 @@ object BackgroundJobService { { val stringIdParser: Parser[Seq[String]] = Space ~> token( NotSpace examples handles.map(_.id.toString).toSet, - description = "").+ + description = "" + ).+ stringIdParser.map { strings => strings.map(Integer.parseInt(_)).flatMap(id => handles.find(_.id == id)) } diff --git a/main/src/main/scala/sbt/BuildPaths.scala b/main/src/main/scala/sbt/BuildPaths.scala index b83fe2311..b74bfde44 100644 --- a/main/src/main/scala/sbt/BuildPaths.scala +++ b/main/src/main/scala/sbt/BuildPaths.scala @@ -17,19 +17,25 @@ object BuildPaths { val globalBaseDirectory = AttributeKey[File]( "global-base-directory", "The base directory for global sbt configuration and staging.", - DSetting) - val globalPluginsDirectory = AttributeKey[File]("global-plugins-directory", - "The base directory for global sbt plugins.", - DSetting) - val globalSettingsDirectory = AttributeKey[File]("global-settings-directory", - "The base directory for global sbt settings.", - DSetting) + DSetting + ) + val globalPluginsDirectory = AttributeKey[File]( + "global-plugins-directory", + "The base directory for global sbt plugins.", + DSetting + ) + val globalSettingsDirectory = AttributeKey[File]( + "global-settings-directory", + "The base directory for global sbt settings.", + DSetting + ) val stagingDirectory = AttributeKey[File]("staging-directory", "The directory for staging remote projects.", DSetting) val dependencyBaseDirectory = AttributeKey[File]( "dependency-base-directory", "The base directory for caching dependency resolution.", - DSetting) + DSetting + ) val globalZincDirectory = AttributeKey[File]("global-zinc-directory", "The base directory for Zinc internals.", DSetting) @@ -56,7 +62,8 @@ object BuildPaths { def getGlobalPluginsDirectory(state: State, globalBase: File): File = fileSetting(globalPluginsDirectory, GlobalPluginsProperty, defaultGlobalPlugins(globalBase))( - state) + state + ) def getGlobalSettingsDirectory(state: State, globalBase: File): File = fileSetting(globalSettingsDirectory, GlobalSettingsProperty, globalBase)(state) @@ -70,11 +77,13 @@ object BuildPaths { fileSetting(globalZincDirectory, GlobalZincProperty, defaultGlobalZinc(globalBase))(state) private[this] def fileSetting(stateKey: AttributeKey[File], property: String, default: File)( - state: State): File = + state: State + ): File = getFileSetting(stateKey, property, default)(state) def getFileSetting(stateKey: AttributeKey[File], property: String, default: => File)( - state: State): File = + state: State + ): File = state get stateKey orElse getFileProperty(property) getOrElse default def getFileProperty(name: String): Option[File] = Option(System.getProperty(name)) flatMap { diff --git a/main/src/main/scala/sbt/Cross.scala b/main/src/main/scala/sbt/Cross.scala index dcff5d449..c1257db5a 100644 --- a/main/src/main/scala/sbt/Cross.scala +++ b/main/src/main/scala/sbt/Cross.scala @@ -150,7 +150,8 @@ object Cross { "configuration. This could result in subprojects cross building against Scala versions that they are " + "not compatible with. Try issuing cross building command with tasks instead, since sbt will be able " + "to ensure that cross building is only done using configured project and Scala version combinations " + - "that are configured.") + "that are configured." + ) state.log.debug("Scala versions configuration is:") projCrossVersions.foreach { case (project, versions) => state.log.debug(s"$project: $versions") @@ -174,12 +175,14 @@ object Cross { case (version, projects) if aggCommand.contains(" ") => // If the command contains a space, then the `all` command won't work because it doesn't support issuing // commands with spaces, so revert to running the command on each project one at a time - s"$SwitchCommand $verbose $version" :: projects.map(project => - s"$project/$aggCommand") + s"$SwitchCommand $verbose $version" :: projects + .map(project => s"$project/$aggCommand") case (version, projects) => // First switch scala version, then use the all command to run the command on each project concurrently - Seq(s"$SwitchCommand $verbose $version", - projects.map(_ + "/" + aggCommand).mkString("all ", " ", "")) + Seq( + s"$SwitchCommand $verbose $version", + projects.map(_ + "/" + aggCommand).mkString("all ", " ", "") + ) } } @@ -188,8 +191,9 @@ object Cross { } def crossRestoreSession: Command = - Command.arb(_ => crossRestoreSessionParser, crossRestoreSessionHelp)((s, _) => - crossRestoreSessionImpl(s)) + Command.arb(_ => crossRestoreSessionParser, crossRestoreSessionHelp)( + (s, _) => crossRestoreSessionImpl(s) + ) private def crossRestoreSessionImpl(state: State): State = { restoreCapturedSession(state, Project.extract(state)) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 67dc6997e..10c561540 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -139,7 +139,8 @@ object Defaults extends BuildCommon { private[sbt] lazy val globalCore: Seq[Setting[_]] = globalDefaults( defaultTestTasks(test) ++ defaultTestTasks(testOnly) ++ defaultTestTasks(testQuick) ++ Seq( excludeFilter :== HiddenFileFilter - ) ++ globalIvyCore ++ globalJvmCore) ++ globalSbtCore + ) ++ globalIvyCore ++ globalJvmCore + ) ++ globalSbtCore private[sbt] lazy val globalJvmCore: Seq[Setting[_]] = Seq( @@ -167,7 +168,8 @@ object Defaults extends BuildCommon { artifactClassifier in packageDoc :== Some(DocClassifier), includeFilter :== NothingFilter, includeFilter in unmanagedSources :== ("*.java" | "*.scala") && new SimpleFileFilter( - _.isFile), + _.isFile + ), includeFilter in unmanagedJars :== "*.jar" | "*.so" | "*.dll" | "*.jnilib" | "*.zip", includeFilter in unmanagedResources :== AllPassFilter, bgList := { bgJobService.value.jobs }, @@ -247,8 +249,10 @@ object Defaults extends BuildCommon { () => { IO.delete(dir); IO.createDirectory(dir) } }, - Previous.cache := new Previous(Def.streamsManagerKey.value, - Previous.references.value.getReferences), + Previous.cache := new Previous( + Def.streamsManagerKey.value, + Previous.references.value.getReferences + ), Previous.references :== new Previous.References, concurrentRestrictions := defaultRestrictions.value, parallelExecution :== true, @@ -289,7 +293,8 @@ object Defaults extends BuildCommon { ++ Vector(ServerHandler.fallback)) }, insideCI :== sys.env.contains("BUILD_NUMBER") || sys.env.contains("CI"), - )) + ) + ) def defaultTestTasks(key: Scoped): Seq[Setting[_]] = inTask(key)( @@ -323,18 +328,24 @@ object Defaults extends BuildCommon { scalaSource := sourceDirectory.value / "scala", javaSource := sourceDirectory.value / "java", unmanagedSourceDirectories := { - makeCrossSources(scalaSource.value, - javaSource.value, - scalaBinaryVersion.value, - crossPaths.value) ++ - makePluginCrossSources(sbtPlugin.value, - scalaSource.value, - (sbtBinaryVersion in pluginCrossBuild).value, - crossPaths.value) + makeCrossSources( + scalaSource.value, + javaSource.value, + scalaBinaryVersion.value, + crossPaths.value + ) ++ + makePluginCrossSources( + sbtPlugin.value, + scalaSource.value, + (sbtBinaryVersion in pluginCrossBuild).value, + crossPaths.value + ) }, - unmanagedSources := collectFiles(unmanagedSourceDirectories, - includeFilter in unmanagedSources, - excludeFilter in unmanagedSources).value, + unmanagedSources := collectFiles( + unmanagedSourceDirectories, + includeFilter in unmanagedSources, + excludeFilter in unmanagedSources + ).value, watchSources in ConfigGlobal ++= { val baseDir = baseDirectory.value val bases = unmanagedSourceDirectories.value @@ -361,9 +372,11 @@ object Defaults extends BuildCommon { resourceDirectories := Classpaths .concatSettings(unmanagedResourceDirectories, managedResourceDirectories) .value, - unmanagedResources := collectFiles(unmanagedResourceDirectories, - includeFilter in unmanagedResources, - excludeFilter in unmanagedResources).value, + unmanagedResources := collectFiles( + unmanagedResourceDirectories, + includeFilter in unmanagedResources, + excludeFilter in unmanagedResources + ).value, watchSources in ConfigGlobal ++= { val bases = unmanagedResourceDirectories.value val include = (includeFilter in unmanagedResources).value @@ -395,19 +408,24 @@ object Defaults extends BuildCommon { def compileBase = inTask(console)(compilersSetting :: Nil) ++ compileBaseGlobal ++ Seq( incOptions := incOptions.value .withClassfileManagerType( - Option(TransactionalManagerType - .of(crossTarget.value / "classes.bak", sbt.util.Logger.Null): ClassFileManagerType).toOptional + Option( + TransactionalManagerType + .of(crossTarget.value / "classes.bak", sbt.util.Logger.Null): ClassFileManagerType + ).toOptional ), scalaInstance := scalaInstanceTask.value, crossVersion := (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled), sbtBinaryVersion in pluginCrossBuild := binarySbtVersion( - (sbtVersion in pluginCrossBuild).value), + (sbtVersion in pluginCrossBuild).value + ), crossSbtVersions := Vector((sbtVersion in pluginCrossBuild).value), - crossTarget := makeCrossTarget(target.value, - scalaBinaryVersion.value, - (sbtBinaryVersion in pluginCrossBuild).value, - sbtPlugin.value, - crossPaths.value), + crossTarget := makeCrossTarget( + target.value, + scalaBinaryVersion.value, + (sbtBinaryVersion in pluginCrossBuild).value, + sbtPlugin.value, + crossPaths.value + ), clean := { val _ = clean.value IvyActions.cleanCachedResolutionCache(ivyModule.value, streams.value.log) @@ -427,7 +445,8 @@ object Defaults extends BuildCommon { derive(crossScalaVersions := Seq(scalaVersion.value)), derive(compilersSetting), derive(scalaBinaryVersion := binaryScalaVersion(scalaVersion.value)) - )) + ) + ) def makeCrossSources( scalaSrcDir: File, @@ -441,10 +460,12 @@ object Defaults extends BuildCommon { Seq(scalaSrcDir, javaSrcDir) } - def makePluginCrossSources(isPlugin: Boolean, - scalaSrcDir: File, - sbtBinaryV: String, - cross: Boolean): Seq[File] = { + def makePluginCrossSources( + isPlugin: Boolean, + scalaSrcDir: File, + sbtBinaryV: String, + cross: Boolean + ): Seq[File] = { if (cross && isPlugin) Vector(scalaSrcDir.getParentFile / s"${scalaSrcDir.name}-sbt-$sbtBinaryV") else Vector() @@ -473,10 +494,12 @@ object Defaults extends BuildCommon { scalaJarsTarget = zincDir, log = streams.value.log ) - val compilers = ZincUtil.compilers(instance = scalaInstance.value, - classpathOptions = classpathOptions.value, - javaHome = javaHome.value, - scalac) + val compilers = ZincUtil.compilers( + instance = scalaInstance.value, + classpathOptions = classpathOptions.value, + javaHome = javaHome.value, + scalac + ) val classLoaderCache = state.value.classLoaderCache if (java.lang.Boolean.getBoolean("sbt.disable.interface.classloader.cache")) compilers else { @@ -494,7 +517,8 @@ object Defaults extends BuildCommon { globalDefaults(enableBinaryCompileAnalysis := true) lazy val configTasks: Seq[Setting[_]] = docTaskSettings(doc) ++ inTask(compile)( - compileInputsSettings) ++ configGlobal ++ defaultCompileSettings ++ compileAnalysisSettings ++ Seq( + compileInputsSettings + ) ++ configGlobal ++ defaultCompileSettings ++ compileAnalysisSettings ++ Seq( compile := compileTask.value, manipulateBytecode := compileIncremental.value, compileIncremental := (compileIncrementalTask tag (Tags.Compile, Tags.CPU)).value, @@ -532,7 +556,8 @@ object Defaults extends BuildCommon { initialCommands :== "", cleanupCommands :== "", asciiGraphWidth :== 40 - )) + ) + ) lazy val projectTasks: Seq[Setting[_]] = Seq( cleanFiles := cleanFilesTask.value, @@ -659,14 +684,17 @@ object Defaults extends BuildCommon { testOptions :== Nil, testResultLogger :== TestResultLogger.Default, testFilter in testOnly :== (selectedFilter _) - )) + ) + ) lazy val testTasks : Seq[Setting[_]] = testTaskOptions(test) ++ testTaskOptions(testOnly) ++ testTaskOptions( - testQuick) ++ testDefaults ++ Seq( + testQuick + ) ++ testDefaults ++ Seq( testLoader := TestFramework.createTestLoader( data(fullClasspath.value), scalaInstance.value, - IO.createUniqueDirectory(taskTemporaryDirectory.value)), + IO.createUniqueDirectory(taskTemporaryDirectory.value) + ), loadedTestFrameworks := { val loader = testLoader.value val log = streams.value.log @@ -714,10 +742,14 @@ object Defaults extends BuildCommon { def testTaskOptions(key: Scoped): Seq[Setting[_]] = inTask(key)( testListeners := { - TestLogger.make(streams.value.log, - closeableTestLogger(streamsManager.value, - test in resolvedScoped.value.scope, - logBuffered.value)) +: + TestLogger.make( + streams.value.log, + closeableTestLogger( + streamsManager.value, + test in resolvedScoped.value.scope, + logBuffered.value + ) + ) +: new TestStatusReporter(succeededFile(streams.in(test).value.cacheDirectory)) +: testListeners.in(TaskZero).value }, @@ -728,7 +760,8 @@ object Defaults extends BuildCommon { ) private[this] def closeableTestLogger(manager: Streams, baseKey: Scoped, buffered: Boolean)( - tdef: TestDefinition): TestLogger.PerTest = { + tdef: TestDefinition + ): TestLogger.PerTest = { val scope = baseKey.scope val extra = scope.extra match { case Select(x) => x; case _ => AttributeMap.empty } val key = ScopedKey(scope.copy(extra = Select(testExtra(extra, tdef))), baseKey.key) @@ -769,9 +802,11 @@ object Defaults extends BuildCommon { def testExecutionTask(task: Scoped): Initialize[Task[Tests.Execution]] = Def.task { - new Tests.Execution((testOptions in task).value, - (parallelExecution in task).value, - (tags in task).value) + new Tests.Execution( + (testOptions in task).value, + (parallelExecution in task).value, + (tags in task).value + ) } def testQuickFilter: Initialize[Task[Seq[String] => Seq[String => Boolean]]] = @@ -849,9 +884,11 @@ object Defaults extends BuildCommon { } } - def createTestRunners(frameworks: Map[TestFramework, Framework], - loader: ClassLoader, - config: Tests.Execution): Map[TestFramework, Runner] = { + def createTestRunners( + frameworks: Map[TestFramework, Framework], + loader: ClassLoader, + config: Tests.Execution + ): Map[TestFramework, Runner] = { import Tests.Argument val opts = config.options.toList frameworks.map { @@ -873,14 +910,16 @@ object Defaults extends BuildCommon { config: Tests.Execution, cp: Classpath, ): Initialize[Task[Tests.Output]] = { - allTestGroupsTask(s, - frameworks, - loader, - groups, - config, - cp, - forkedParallelExecution = false, - javaOptions = Nil) + allTestGroupsTask( + s, + frameworks, + loader, + groups, + config, + cp, + forkedParallelExecution = false, + javaOptions = Nil + ) } private[sbt] def allTestGroupsTask( @@ -890,25 +929,30 @@ object Defaults extends BuildCommon { groups: Seq[Tests.Group], config: Tests.Execution, cp: Classpath, - forkedParallelExecution: Boolean): Initialize[Task[Tests.Output]] = { - allTestGroupsTask(s, - frameworks, - loader, - groups, - config, - cp, - forkedParallelExecution, - javaOptions = Nil) + forkedParallelExecution: Boolean + ): Initialize[Task[Tests.Output]] = { + allTestGroupsTask( + s, + frameworks, + loader, + groups, + config, + cp, + forkedParallelExecution, + javaOptions = Nil + ) } - private[sbt] def allTestGroupsTask(s: TaskStreams, - frameworks: Map[TestFramework, Framework], - loader: ClassLoader, - groups: Seq[Tests.Group], - config: Tests.Execution, - cp: Classpath, - forkedParallelExecution: Boolean, - javaOptions: Seq[String]): Initialize[Task[Tests.Output]] = { + private[sbt] def allTestGroupsTask( + s: TaskStreams, + frameworks: Map[TestFramework, Framework], + loader: ClassLoader, + groups: Seq[Tests.Group], + config: Tests.Execution, + cp: Classpath, + forkedParallelExecution: Boolean, + javaOptions: Seq[String] + ): Initialize[Task[Tests.Output]] = { val runners = createTestRunners(frameworks, loader, config) val groupTasks = groups map { case Tests.Group(_, tests, runPolicy) => @@ -917,13 +961,15 @@ object Defaults extends BuildCommon { s.log.debug(s"javaOptions: ${opts.runJVMOptions}") val forkedConfig = config.copy(parallel = config.parallel && forkedParallelExecution) s.log.debug(s"Forking tests - parallelism = ${forkedConfig.parallel}") - ForkTests(runners, - tests.toVector, - forkedConfig, - cp.files, - opts, - s.log, - Tags.ForkedTestGroup) + ForkTests( + runners, + tests.toVector, + forkedConfig, + cp.files, + opts, + s.log, + Tags.ForkedTestGroup + ) case Tests.InProcess => if (javaOptions.nonEmpty) { s.log.warn("javaOptions will be ignored, fork is set to false") @@ -978,7 +1024,8 @@ object Defaults extends BuildCommon { Seq( packageOptions :== Nil, artifactName :== (Artifact.artifactName _) - )) + ) + ) lazy val packageConfig: Seq[Setting[_]] = inTask(packageBin)( @@ -998,7 +1045,8 @@ object Defaults extends BuildCommon { packageOptions := Package.addSpecManifestAttributes( name.value, version.value, - organizationName.value) +: packageOptions.value + organizationName.value + ) +: packageOptions.value ) ++ packageTaskSettings(packageBin, packageBinMappings) ++ packageTaskSettings(packageSrc, packageSrcMappings) ++ @@ -1022,26 +1070,34 @@ object Defaults extends BuildCommon { (srcs --- sdirs --- base) pair (relativeTo(sdirs) | relativeTo(base) | flat) } def resourceMappings = relativeMappings(unmanagedResources, unmanagedResourceDirectories) - def relativeMappings(files: ScopedTaskable[Seq[File]], - dirs: ScopedTaskable[Seq[File]]): Initialize[Task[Seq[(File, String)]]] = + def relativeMappings( + files: ScopedTaskable[Seq[File]], + dirs: ScopedTaskable[Seq[File]] + ): Initialize[Task[Seq[(File, String)]]] = Def.task { val rs = files.toTask.value val rdirs = dirs.toTask.value (rs --- rdirs) pair (relativeTo(rdirs) | flat) } - def collectFiles(dirs: ScopedTaskable[Seq[File]], - filter: ScopedTaskable[FileFilter], - excludes: ScopedTaskable[FileFilter]): Initialize[Task[Seq[File]]] = + def collectFiles( + dirs: ScopedTaskable[Seq[File]], + filter: ScopedTaskable[FileFilter], + excludes: ScopedTaskable[FileFilter] + ): Initialize[Task[Seq[File]]] = Def.task { dirs.toTask.value.descendantsExcept(filter.toTask.value, excludes.toTask.value).get } def artifactPathSetting(art: SettingKey[Artifact]): Initialize[File] = Def.setting { val f = artifactName.value - (crossTarget.value / f(ScalaVersion((scalaVersion in artifactName).value, - (scalaBinaryVersion in artifactName).value), - projectID.value, - art.value)).asFile + (crossTarget.value / f( + ScalaVersion( + (scalaVersion in artifactName).value, + (scalaBinaryVersion in artifactName).value + ), + projectID.value, + art.value + )).asFile } def artifactSetting: Initialize[Artifact] = @@ -1066,9 +1122,11 @@ object Defaults extends BuildCommon { } @deprecated("The configuration(s) should not be decided based on the classifier.", "1.0.0") - def artifactConfigurations(base: Artifact, - scope: Configuration, - classifier: Option[String]): Iterable[Configuration] = + def artifactConfigurations( + base: Artifact, + scope: Configuration, + classifier: Option[String] + ): Iterable[Configuration] = classifier match { case Some(c) => Artifact.classifierConf(c) :: Nil case None => scope :: Nil @@ -1107,7 +1165,8 @@ object Defaults extends BuildCommon { classes match { case multiple if multiple.size > 1 => logger.warn( - "Multiple main classes detected. Run 'show discoveredMainClasses' to see the list") + "Multiple main classes detected. Run 'show discoveredMainClasses' to see the list" + ) case _ => } pickMainClass(classes) @@ -1126,7 +1185,8 @@ object Defaults extends BuildCommon { case xs if xs.isEmpty => () case xs => sys.error( - s"cleanKeepFiles contains directory/file that are not directly under cleanFiles: $xs") + s"cleanKeepFiles contains directory/file that are not directly under cleanFiles: $xs" + ) } val toClean = (dirItems filterNot { preserveSet(_) }) ++ fs toClean @@ -1138,8 +1198,9 @@ object Defaults extends BuildCommon { copyClasspath: Initialize[Boolean], scalaRun: Initialize[Task[ScalaRun]] ): Initialize[InputTask[JobHandle]] = { - val parser = Defaults.loadForParser(discoveredMainClasses)((s, names) => - Defaults.runMainParser(s, names getOrElse Nil)) + val parser = Defaults.loadForParser(discoveredMainClasses)( + (s, names) => Defaults.runMainParser(s, names getOrElse Nil) + ) Def.inputTask { val service = bgJobService.value val (mainClass, args) = parser.parsed @@ -1232,12 +1293,16 @@ object Defaults extends BuildCommon { } else { if (options.nonEmpty) { val mask = ScopeMask(project = false) - val showJavaOptions = Scope.displayMasked((javaOptions in resolvedScope).scopedKey.scope, - (javaOptions in resolvedScope).key.label, - mask) - val showFork = Scope.displayMasked((fork in resolvedScope).scopedKey.scope, - (fork in resolvedScope).key.label, - mask) + val showJavaOptions = Scope.displayMasked( + (javaOptions in resolvedScope).scopedKey.scope, + (javaOptions in resolvedScope).key.label, + mask + ) + val showFork = Scope.displayMasked( + (fork in resolvedScope).scopedKey.scope, + (fork in resolvedScope).key.label, + mask + ) s.log.warn(s"$showJavaOptions will be ignored, $showFork is set to false") } new Run(si, trap, tmp) @@ -1312,13 +1377,15 @@ object Defaults extends BuildCommon { case (_, true) => val javadoc = sbt.inc.Doc.cachedJavadoc(label, s.cacheStoreFactory sub "java", cs.javaTools) - javadoc.run(srcs.toList, - cp, - out, - javacOptions.value.toList, - IncToolOptionsUtil.defaultIncToolOptions(), - s.log, - reporter) + javadoc.run( + srcs.toList, + cp, + out, + javacOptions.value.toList, + IncToolOptionsUtil.defaultIncToolOptions(), + s.log, + reporter + ) case _ => () // do nothing } out @@ -1514,15 +1581,17 @@ object Defaults extends BuildCommon { val max = maxErrors.value val spms = sourcePositionMappers.value val problems = - analysis.infos.allInfos.values.flatMap(i => - i.getReportedProblems ++ i.getUnreportedProblems) + analysis.infos.allInfos.values + .flatMap(i => i.getReportedProblems ++ i.getUnreportedProblems) val reporter = new ManagedLoggedReporter(max, streams.value.log, foldMappers(spms)) problems.foreach(p => reporter.log(p)) } def sbtPluginExtra(m: ModuleID, sbtV: String, scalaV: String): ModuleID = - m.extra(PomExtraDependencyAttributes.SbtVersionKey -> sbtV, - PomExtraDependencyAttributes.ScalaVersionKey -> scalaV) + m.extra( + PomExtraDependencyAttributes.SbtVersionKey -> sbtV, + PomExtraDependencyAttributes.ScalaVersionKey -> scalaV + ) .withCrossVersion(Disabled()) def discoverSbtPluginNames: Initialize[Task[PluginDiscovery.DiscoveredNames]] = Def.taskDyn { @@ -1636,8 +1705,10 @@ object Classpaths { import Keys._ import Defaults._ - def concatDistinct[T](a: ScopedTaskable[Seq[T]], - b: ScopedTaskable[Seq[T]]): Initialize[Task[Seq[T]]] = Def.task { + def concatDistinct[T]( + a: ScopedTaskable[Seq[T]], + b: ScopedTaskable[Seq[T]] + ): Initialize[Task[Seq[T]]] = Def.task { (a.toTask.value ++ b.toTask.value).distinct } def concat[T](a: ScopedTaskable[Seq[T]], b: ScopedTaskable[Seq[T]]): Initialize[Task[Seq[T]]] = @@ -1648,10 +1719,12 @@ object Classpaths { lazy val configSettings: Seq[Setting[_]] = classpaths ++ Seq( products := makeProducts.value, productDirectories := classDirectory.value :: Nil, - classpathConfiguration := findClasspathConfig(internalConfigurationMap.value, - configuration.value, - classpathConfiguration.?.value, - update.value) + classpathConfiguration := findClasspathConfig( + internalConfigurationMap.value, + configuration.value, + classpathConfiguration.?.value, + update.value + ) ) private[this] def classpaths: Seq[Setting[_]] = Seq( @@ -1660,9 +1733,11 @@ object Classpaths { fullClasspath := concatDistinct(exportedProducts, dependencyClasspath).value, internalDependencyClasspath := internalDependencies.value, unmanagedClasspath := unmanagedDependencies.value, - managedClasspath := managedJars(classpathConfiguration.value, - classpathTypes.value, - update.value), + managedClasspath := managedJars( + classpathConfiguration.value, + classpathTypes.value, + update.value + ), exportedProducts := trackedExportedProducts(TrackLevel.TrackAlways).value, exportedProductsIfMissing := trackedExportedProducts(TrackLevel.TrackIfMissing).value, exportedProductsNoTracking := trackedExportedProducts(TrackLevel.NoTracking).value, @@ -1672,10 +1747,12 @@ object Classpaths { internalDependencyAsJars := internalDependencyJarsTask.value, dependencyClasspathAsJars := concat(internalDependencyAsJars, externalDependencyClasspath).value, fullClasspathAsJars := concatDistinct(exportedProductJars, dependencyClasspathAsJars).value, - unmanagedJars := findUnmanagedJars(configuration.value, - unmanagedBase.value, - includeFilter in unmanagedJars value, - excludeFilter in unmanagedJars value) + unmanagedJars := findUnmanagedJars( + configuration.value, + unmanagedBase.value, + includeFilter in unmanagedJars value, + excludeFilter in unmanagedJars value + ) ).map(exportClasspath) private[this] def exportClasspath(s: Setting[Task[Classpath]]): Setting[Task[Classpath]] = @@ -1692,15 +1769,18 @@ object Classpaths { for (task <- defaultPackageKeys; conf <- Seq(Compile, Test)) yield (task in conf) lazy val defaultArtifactTasks: Seq[TaskKey[File]] = makePom +: defaultPackages - def findClasspathConfig(map: Configuration => Configuration, - thisConfig: Configuration, - delegated: Option[Configuration], - report: UpdateReport): Configuration = { + def findClasspathConfig( + map: Configuration => Configuration, + thisConfig: Configuration, + delegated: Option[Configuration], + report: UpdateReport + ): Configuration = { val defined = report.allConfigurations.toSet val search = map(thisConfig) +: (delegated.toList ++ Seq(Compile, Configurations.Default)) def notFound = sys.error( - "Configuration to use for managed classpath must be explicitly defined when default configurations are not present.") + "Configuration to use for managed classpath must be explicitly defined when default configurations are not present." + ) search find { c => defined contains ConfigRef(c.name) } getOrElse notFound @@ -1729,7 +1809,8 @@ object Classpaths { publishMavenStyle :== true, publishArtifact :== true, publishArtifact in Test :== false - )) + ) + ) val jvmPublishSettings: Seq[Setting[_]] = Seq( artifacts := artifactDefs(defaultArtifactTasks).value, @@ -1799,7 +1880,8 @@ object Classpaths { CrossVersion(scalaVersion, binVersion)(base).withCrossVersion(Disabled()) }, shellPrompt := shellPromptFromState - )) + ) + ) val ivyBaseSettings: Seq[Setting[_]] = baseGlobalDefaults ++ sbtClassifiersTasks ++ Seq( conflictWarning := conflictWarning.value.copy(label = Reference.display(thisProjectRef.value)), @@ -1822,10 +1904,12 @@ object Classpaths { developers.value.toVector ), overrideBuildResolvers := appConfiguration(isOverrideRepositories).value, - externalResolvers := ((externalResolvers.?.value, - resolvers.value, - appResolvers.value, - useJCenter.value) match { + externalResolvers := (( + externalResolvers.?.value, + resolvers.value, + appResolvers.value, + useJCenter.value + ) match { case (Some(delegated), Seq(), _, _) => delegated case (_, rs, Some(ars), _) => ars ++ rs case (_, rs, _, uj) => Resolver.combineDefaultResolvers(rs.toVector, uj, mavenCentral = true) @@ -1877,7 +1961,8 @@ object Classpaths { checkExplicit = true, overrideScalaVersion = true ).withScalaOrganization(scalaOrganization.value) - .withScalaArtifacts(scalaArtifacts.value.toVector)) + .withScalaArtifacts(scalaArtifacts.value.toVector) + ) } )).value, artifactPath in makePom := artifactPathSetting(artifact in makePom).value, @@ -1903,7 +1988,8 @@ object Classpaths { .withRetrieveDirectory(managedDirectory.value) .withOutputPattern(retrievePattern.value) .withSync(retrieveManagedSync.value) - .withConfigurationsToRetrieve(configurationsToRetrieve.value map { _.toVector })) + .withConfigurationsToRetrieve(configurationsToRetrieve.value map { _.toVector }) + ) else None }, dependencyResolution := IvyDependencyResolution(ivyConfiguration.value), @@ -1997,7 +2083,8 @@ object Classpaths { }, dependencyPositions := dependencyPositionsTask.value, unresolvedWarningConfiguration in update := UnresolvedWarningConfiguration( - dependencyPositions.value), + dependencyPositions.value + ), update := (updateTask tag (Tags.Update, Tags.Network)).value, update := { val report = update.value @@ -2066,7 +2153,8 @@ object Classpaths { autoScalaLibrary.value && scalaHome.value.isEmpty && managedScalaInstance.value, sbtPlugin.value, scalaOrganization.value, - scalaVersion.value), + scalaVersion.value + ), // Override the default to handle mixing in the sbtPlugin + scala dependencies. allDependencies := { val base = projectDependencies.value ++ libraryDependencies.value @@ -2091,7 +2179,8 @@ object Classpaths { val resset = ress.toSet for ((name, r) <- resset groupBy (_.name) if r.size > 1) { log.warn( - "Multiple resolvers having different access mechanism configured with same name '" + name + "'. To avoid conflict, Remove duplicate project resolvers (`resolvers`) or rename publishing resolver (`publishTo`).") + "Multiple resolvers having different access mechanism configured with same name '" + name + "'. To avoid conflict, Remove duplicate project resolvers (`resolvers`) or rename publishing resolver (`publishTo`)." + ) } } @@ -2107,9 +2196,11 @@ object Classpaths { def pluginProjectID: Initialize[ModuleID] = Def.setting { if (sbtPlugin.value) - sbtPluginExtra(projectID.value, - (sbtBinaryVersion in pluginCrossBuild).value, - (scalaBinaryVersion in pluginCrossBuild).value) + sbtPluginExtra( + projectID.value, + (sbtBinaryVersion in pluginCrossBuild).value, + (scalaBinaryVersion in pluginCrossBuild).value + ) else projectID.value } private[sbt] def ivySbt0: Initialize[Task[IvySbt]] = @@ -2134,7 +2225,8 @@ object Classpaths { Defaults.globalDefaults( Seq( transitiveClassifiers in updateSbtClassifiers ~= (_.filter(_ != DocClassifier)) - )) + ) + ) def sbtClassifiersTasks = sbtClassifiersGlobalDefaults ++ inTask(updateSbtClassifiers)( @@ -2173,7 +2265,9 @@ object Classpaths { Vector(), checkExplicit = false, filterImplicit = false, - overrideScalaVersion = true).withScalaOrganization(scalaOrganization.value)) + overrideScalaVersion = true + ).withScalaOrganization(scalaOrganization.value) + ) }, updateSbtClassifiers in TaskGlobal := (Def.task { val lm = dependencyResolution.value @@ -2262,7 +2356,8 @@ object Classpaths { } def withExcludes(out: File, classifiers: Seq[String], lock: xsbti.GlobalLock)( - f: Map[ModuleID, Vector[ConfigRef]] => UpdateReport): UpdateReport = { + f: Map[ModuleID, Vector[ConfigRef]] => UpdateReport + ): UpdateReport = { import sbt.librarymanagement.LibraryManagementCodec._ import sbt.util.FileBasedStore implicit val isoString: sjsonnew.IsoString[JValue] = @@ -2281,15 +2376,15 @@ object Classpaths { val excludes = store .read[Map[ModuleID, Vector[ConfigRef]]]( - default = Map.empty[ModuleID, Vector[ConfigRef]]) + default = Map.empty[ModuleID, Vector[ConfigRef]] + ) val report = f(excludes) val allExcludes: Map[ModuleID, Vector[ConfigRef]] = excludes ++ IvyActions .extractExcludes(report) .mapValues(cs => cs.map(c => ConfigRef(c)).toVector) store.write(allExcludes) - IvyActions.addExcluded(report, - classifiers.toVector, - allExcludes.mapValues(_.map(_.name).toSet)) + IvyActions + .addExcluded(report, classifiers.toVector, allExcludes.mapValues(_.map(_.name).toSet)) } } ) @@ -2474,42 +2569,50 @@ object Classpaths { def getPublishTo(repo: Option[Resolver]): Resolver = repo getOrElse sys.error("Repository for publishing is not specified.") - def publishConfig(publishMavenStyle: Boolean, - deliverIvyPattern: String, - status: String, - configurations: Vector[ConfigRef], - artifacts: Vector[(Artifact, File)], - checksums: Vector[String], - resolverName: String = "local", - logging: UpdateLogging = UpdateLogging.DownloadOnly, - overwrite: Boolean = false) = - PublishConfiguration(publishMavenStyle, - deliverIvyPattern, - status, - configurations, - resolverName, - artifacts, - checksums, - logging, - overwrite) + def publishConfig( + publishMavenStyle: Boolean, + deliverIvyPattern: String, + status: String, + configurations: Vector[ConfigRef], + artifacts: Vector[(Artifact, File)], + checksums: Vector[String], + resolverName: String = "local", + logging: UpdateLogging = UpdateLogging.DownloadOnly, + overwrite: Boolean = false + ) = + PublishConfiguration( + publishMavenStyle, + deliverIvyPattern, + status, + configurations, + resolverName, + artifacts, + checksums, + logging, + overwrite + ) - def makeIvyXmlConfig(publishMavenStyle: Boolean, - deliverIvyPattern: String, - status: String, - configurations: Vector[ConfigRef], - checksums: Vector[String], - logging: sbt.librarymanagement.UpdateLogging = UpdateLogging.DownloadOnly, - overwrite: Boolean = false, - optResolverName: Option[String] = None) = - PublishConfiguration(publishMavenStyle, - Some(deliverIvyPattern), - Some(status), - Some(configurations), - optResolverName, - Vector.empty, - checksums, - Some(logging), - overwrite) + def makeIvyXmlConfig( + publishMavenStyle: Boolean, + deliverIvyPattern: String, + status: String, + configurations: Vector[ConfigRef], + checksums: Vector[String], + logging: sbt.librarymanagement.UpdateLogging = UpdateLogging.DownloadOnly, + overwrite: Boolean = false, + optResolverName: Option[String] = None + ) = + PublishConfiguration( + publishMavenStyle, + Some(deliverIvyPattern), + Some(status), + Some(configurations), + optResolverName, + Vector.empty, + checksums, + Some(logging), + overwrite + ) def deliverPattern(outputPath: File): String = (outputPath / "[artifact]-[revision](-[classifier]).[ext]").absolutePath @@ -2528,14 +2631,18 @@ object Classpaths { private[sbt] def depMap: Initialize[Task[Map[ModuleRevisionId, ModuleDescriptor]]] = Def.taskDyn { - depMap(buildDependencies.value classpathTransitiveRefs thisProjectRef.value, - settingsData.value, - streams.value.log) + depMap( + buildDependencies.value classpathTransitiveRefs thisProjectRef.value, + settingsData.value, + streams.value.log + ) } - private[sbt] def depMap(projects: Seq[ProjectRef], - data: Settings[Scope], - log: Logger): Initialize[Task[Map[ModuleRevisionId, ModuleDescriptor]]] = + private[sbt] def depMap( + projects: Seq[ProjectRef], + data: Settings[Scope], + log: Logger + ): Initialize[Task[Map[ModuleRevisionId, ModuleDescriptor]]] = Def.value { projects.flatMap(ivyModule in _ get data).join.map { mod => mod map { _.dependencyMapping(log) } toMap; @@ -2580,14 +2687,16 @@ object Classpaths { .put(configuration.key, config) } private[this] def trackedExportedProductsImplTask( - track: TrackLevel): Initialize[Task[Seq[(File, CompileAnalysis)]]] = + track: TrackLevel + ): Initialize[Task[Seq[(File, CompileAnalysis)]]] = Def.taskDyn { val useJars = exportJars.value if (useJars) trackedJarProductsImplTask(track) else trackedNonJarProductsImplTask(track) } private[this] def trackedNonJarProductsImplTask( - track: TrackLevel): Initialize[Task[Seq[(File, CompileAnalysis)]]] = + track: TrackLevel + ): Initialize[Task[Seq[(File, CompileAnalysis)]]] = Def.taskDyn { val dirs = productDirectories.value def containsClassFile(fs: List[File]): Boolean = @@ -2609,15 +2718,18 @@ object Classpaths { Def.task { val analysisOpt = previousCompile.value.analysis.toOption dirs map { x => - (x, - if (analysisOpt.isDefined) analysisOpt.get - else Analysis.empty) + ( + x, + if (analysisOpt.isDefined) analysisOpt.get + else Analysis.empty + ) } } } } private[this] def trackedJarProductsImplTask( - track: TrackLevel): Initialize[Task[Seq[(File, CompileAnalysis)]]] = + track: TrackLevel + ): Initialize[Task[Seq[(File, CompileAnalysis)]]] = Def.taskDyn { val jar = (artifactPath in packageBin).value TrackLevel.intersection(track, exportToInternal.value) match { @@ -2633,9 +2745,11 @@ object Classpaths { Def.task { val analysisOpt = previousCompile.value.analysis.toOption Seq(jar) map { x => - (x, - if (analysisOpt.isDefined) analysisOpt.get - else Analysis.empty) + ( + x, + if (analysisOpt.isDefined) analysisOpt.get + else Analysis.empty + ) } } } @@ -2646,28 +2760,34 @@ object Classpaths { def internalDependencies: Initialize[Task[Classpath]] = Def.taskDyn { - internalDependenciesImplTask(thisProjectRef.value, - classpathConfiguration.value, - configuration.value, - settingsData.value, - buildDependencies.value, - trackInternalDependencies.value) + internalDependenciesImplTask( + thisProjectRef.value, + classpathConfiguration.value, + configuration.value, + settingsData.value, + buildDependencies.value, + trackInternalDependencies.value + ) } def internalDependencyJarsTask: Initialize[Task[Classpath]] = Def.taskDyn { - internalDependencyJarsImplTask(thisProjectRef.value, - classpathConfiguration.value, - configuration.value, - settingsData.value, - buildDependencies.value, - trackInternalDependencies.value) + internalDependencyJarsImplTask( + thisProjectRef.value, + classpathConfiguration.value, + configuration.value, + settingsData.value, + buildDependencies.value, + trackInternalDependencies.value + ) } def unmanagedDependencies: Initialize[Task[Classpath]] = Def.taskDyn { - unmanagedDependencies0(thisProjectRef.value, - configuration.value, - settingsData.value, - buildDependencies.value) + unmanagedDependencies0( + thisProjectRef.value, + configuration.value, + settingsData.value, + buildDependencies.value + ) } def mkIvyConfiguration: Initialize[Task[IvyConfiguration]] = Def.task { @@ -2688,10 +2808,12 @@ object Classpaths { import java.util.LinkedHashSet import collection.JavaConverters._ - def interSort(projectRef: ProjectRef, - conf: Configuration, - data: Settings[Scope], - deps: BuildDependencies): Seq[(ProjectRef, String)] = { + def interSort( + projectRef: ProjectRef, + conf: Configuration, + data: Settings[Scope], + deps: BuildDependencies + ): Seq[(ProjectRef, String)] = { val visited = (new LinkedHashSet[(ProjectRef, String)]).asScala def visit(p: ProjectRef, c: Configuration): Unit = { val applicableConfigs = allConfigs(c) @@ -2725,10 +2847,12 @@ object Classpaths { case (projectRef, configName) => (projectRef, ConfigRef(configName)) } - private[sbt] def unmanagedDependencies0(projectRef: ProjectRef, - conf: Configuration, - data: Settings[Scope], - deps: BuildDependencies): Initialize[Task[Classpath]] = + private[sbt] def unmanagedDependencies0( + projectRef: ProjectRef, + conf: Configuration, + data: Settings[Scope], + deps: BuildDependencies + ): Initialize[Task[Classpath]] = Def.value { interDependencies( projectRef, @@ -2741,19 +2865,23 @@ object Classpaths { (dep, conf, data, _) => unmanagedLibs(dep, conf, data), ) } - private[sbt] def internalDependenciesImplTask(projectRef: ProjectRef, - conf: Configuration, - self: Configuration, - data: Settings[Scope], - deps: BuildDependencies, - track: TrackLevel): Initialize[Task[Classpath]] = + private[sbt] def internalDependenciesImplTask( + projectRef: ProjectRef, + conf: Configuration, + self: Configuration, + data: Settings[Scope], + deps: BuildDependencies, + track: TrackLevel + ): Initialize[Task[Classpath]] = Def.value { interDependencies(projectRef, deps, conf, self, data, track, false, productsTask) } - private[sbt] def internalDependencyJarsImplTask(projectRef: ProjectRef, - conf: Configuration, - self: Configuration, - data: Settings[Scope], - deps: BuildDependencies, - track: TrackLevel): Initialize[Task[Classpath]] = + private[sbt] def internalDependencyJarsImplTask( + projectRef: ProjectRef, + conf: Configuration, + self: Configuration, + data: Settings[Scope], + deps: BuildDependencies, + track: TrackLevel + ): Initialize[Task[Classpath]] = Def.value { interDependencies(projectRef, deps, conf, self, data, track, false, jarProductsTask) } @@ -2765,7 +2893,8 @@ object Classpaths { data: Settings[Scope], track: TrackLevel, includeSelf: Boolean, - f: (ProjectRef, String, Settings[Scope], TrackLevel) => Task[Classpath]): Task[Classpath] = { + f: (ProjectRef, String, Settings[Scope], TrackLevel) => Task[Classpath] + ): Task[Classpath] = { val visited = interSort(projectRef, conf, data, deps) val tasks = (new LinkedHashSet[Task[Classpath]]).asScala for ((dep, c) <- visited) @@ -2775,23 +2904,28 @@ object Classpaths { (tasks.toSeq.join).map(_.flatten.distinct) } - def mapped(confString: Option[String], - masterConfs: Seq[String], - depConfs: Seq[String], - default: String, - defaultMapping: String): String => Seq[String] = { + def mapped( + confString: Option[String], + masterConfs: Seq[String], + depConfs: Seq[String], + default: String, + defaultMapping: String + ): String => Seq[String] = { lazy val defaultMap = parseMapping(defaultMapping, masterConfs, depConfs, _ :: Nil) parseMapping(confString getOrElse default, masterConfs, depConfs, defaultMap) } - def parseMapping(confString: String, - masterConfs: Seq[String], - depConfs: Seq[String], - default: String => Seq[String]): String => Seq[String] = + def parseMapping( + confString: String, + masterConfs: Seq[String], + depConfs: Seq[String], + default: String => Seq[String] + ): String => Seq[String] = union(confString.split(";") map parseSingleMapping(masterConfs, depConfs, default)) def parseSingleMapping( masterConfs: Seq[String], depConfs: Seq[String], - default: String => Seq[String])(confString: String): String => Seq[String] = { + default: String => Seq[String] + )(confString: String): String => Seq[String] = { val ms: Seq[(String, Seq[String])] = trim(confString.split("->", 2)) match { case x :: Nil => for (a <- parseList(x, masterConfs)) yield (a, default(a)) @@ -2826,19 +2960,23 @@ object Classpaths { ivyConfigurations in p get data getOrElse Nil def confOpt(configurations: Seq[Configuration], conf: String): Option[Configuration] = configurations.find(_.name == conf) - private[sbt] def productsTask(dep: ResolvedReference, - conf: String, - data: Settings[Scope], - track: TrackLevel): Task[Classpath] = + private[sbt] def productsTask( + dep: ResolvedReference, + conf: String, + data: Settings[Scope], + track: TrackLevel + ): Task[Classpath] = track match { case TrackLevel.NoTracking => getClasspath(exportedProductsNoTracking, dep, conf, data) case TrackLevel.TrackIfMissing => getClasspath(exportedProductsIfMissing, dep, conf, data) case TrackLevel.TrackAlways => getClasspath(exportedProducts, dep, conf, data) } - private[sbt] def jarProductsTask(dep: ResolvedReference, - conf: String, - data: Settings[Scope], - track: TrackLevel): Task[Classpath] = + private[sbt] def jarProductsTask( + dep: ResolvedReference, + conf: String, + data: Settings[Scope], + track: TrackLevel + ): Task[Classpath] = track match { case TrackLevel.NoTracking => getClasspath(exportedProductJarsNoTracking, dep, conf, data) case TrackLevel.TrackIfMissing => getClasspath(exportedProductJarsIfMissing, dep, conf, data) @@ -2848,10 +2986,12 @@ object Classpaths { def unmanagedLibs(dep: ResolvedReference, conf: String, data: Settings[Scope]): Task[Classpath] = getClasspath(unmanagedJars, dep, conf, data) - def getClasspath(key: TaskKey[Classpath], - dep: ResolvedReference, - conf: String, - data: Settings[Scope]): Task[Classpath] = + def getClasspath( + key: TaskKey[Classpath], + dep: ResolvedReference, + conf: String, + data: Settings[Scope] + ): Task[Classpath] = (key in (dep, ConfigKey(conf))) get data getOrElse constant(Nil) def defaultConfigurationTask(p: ResolvedReference, data: Settings[Scope]): Configuration = @@ -2866,10 +3006,12 @@ object Classpaths { def modifyForPlugin(plugin: Boolean, dep: ModuleID): ModuleID = if (plugin) dep.withConfigurations(Some(Provided.name)) else dep - def autoLibraryDependency(auto: Boolean, - plugin: Boolean, - org: String, - version: String): Seq[ModuleID] = + def autoLibraryDependency( + auto: Boolean, + plugin: Boolean, + org: String, + version: String + ): Seq[ModuleID] = if (auto) modifyForPlugin(plugin, ModuleID(org, ScalaArtifacts.LibraryID, version)) :: Nil else @@ -2899,10 +3041,12 @@ object Classpaths { } .distinct - def findUnmanagedJars(config: Configuration, - base: File, - filter: FileFilter, - excl: FileFilter): Classpath = + def findUnmanagedJars( + config: Configuration, + base: File, + filter: FileFilter, + excl: FileFilter + ): Classpath = (base * (filter -- excl) +++ (base / config.name).descendantsExcept(filter, excl)).classpath def autoPlugins(report: UpdateReport, internalPluginClasspath: Seq[File]): Seq[String] = { @@ -2916,12 +3060,14 @@ object Classpaths { val ref = thisProjectRef.value val data = settingsData.value val deps = buildDependencies.value - internalDependenciesImplTask(ref, - CompilerPlugin, - CompilerPlugin, - data, - deps, - TrackLevel.TrackAlways) + internalDependenciesImplTask( + ref, + CompilerPlugin, + CompilerPlugin, + data, + deps, + TrackLevel.TrackAlways + ) } lazy val compilerPluginConfig = Seq( @@ -2934,7 +3080,8 @@ object Classpaths { ) def substituteScalaFiles(scalaOrg: String, report: UpdateReport)( - scalaJars: String => Seq[File]): UpdateReport = + scalaJars: String => Seq[File] + ): UpdateReport = report.substitute { (configuration, module, arts) => if (module.organization == scalaOrg) { val jarName = module.name + ".jar" @@ -2986,11 +3133,13 @@ object Classpaths { repo match { case m: xsbti.MavenRepository => MavenRepository(m.id, m.url.toString) case i: xsbti.IvyRepository => - val patterns = Patterns(Vector(i.ivyPattern), - Vector(i.artifactPattern), - mavenCompatible(i), - descriptorOptional(i), - skipConsistencyCheck(i)) + val patterns = Patterns( + Vector(i.ivyPattern), + Vector(i.artifactPattern), + mavenCompatible(i), + descriptorOptional(i), + skipConsistencyCheck(i) + ) i.url.getProtocol match { case "file" => // This hackery is to deal suitably with UNC paths on Windows. Once we can assume Java7, Paths should save us from this. @@ -3009,7 +3158,8 @@ object Classpaths { case Predefined.SonatypeOSSSnapshots => Resolver.sonatypeRepo("snapshots") case unknown => sys.error( - "Unknown predefined resolver '" + unknown + "'. This resolver may only be supported in newer sbt versions.") + "Unknown predefined resolver '" + unknown + "'. This resolver may only be supported in newer sbt versions." + ) } } } @@ -3048,15 +3198,18 @@ trait BuildExtra extends BuildCommon with DefExtra { libraryDependencies += sbtPluginExtra( ModuleID("org.scala-sbt", "sbt-maven-resolver", sbtVersion.value), sbtBinaryVersion.value, - scalaBinaryVersion.value) + scalaBinaryVersion.value + ) /** * Adds `dependency` as an sbt plugin for the specific sbt version `sbtVersion` and Scala version `scalaVersion`. * Typically, use the default values for these versions instead of specifying them explicitly. */ - def addSbtPlugin(dependency: ModuleID, - sbtVersion: String, - scalaVersion: String): Setting[Seq[ModuleID]] = + def addSbtPlugin( + dependency: ModuleID, + sbtVersion: String, + scalaVersion: String + ): Setting[Seq[ModuleID]] = libraryDependencies += sbtPluginExtra(dependency, sbtVersion, scalaVersion) /** @@ -3095,8 +3248,10 @@ trait BuildExtra extends BuildCommon with DefExtra { } /** Constructs a setting that declares a new artifact `artifact` that is generated by `taskDef`. */ - def addArtifact(artifact: Initialize[Artifact], - taskDef: Initialize[Task[File]]): SettingsDefinition = { + def addArtifact( + artifact: Initialize[Artifact], + taskDef: Initialize[Task[File]] + ): SettingsDefinition = { val artLocal = SettingKey.local[Artifact] val taskLocal = TaskKey.local[File] val art = artifacts := artLocal.value +: artifacts.value @@ -3104,22 +3259,30 @@ trait BuildExtra extends BuildCommon with DefExtra { Seq(artLocal := artifact.value, taskLocal := taskDef.value, art, pkgd) } - def externalIvySettings(file: Initialize[File] = inBase("ivysettings.xml"), - addMultiResolver: Boolean = true): Setting[Task[IvyConfiguration]] = + def externalIvySettings( + file: Initialize[File] = inBase("ivysettings.xml"), + addMultiResolver: Boolean = true + ): Setting[Task[IvyConfiguration]] = externalIvySettingsURI(file(_.toURI), addMultiResolver) - def externalIvySettingsURL(url: URL, - addMultiResolver: Boolean = true): Setting[Task[IvyConfiguration]] = + def externalIvySettingsURL( + url: URL, + addMultiResolver: Boolean = true + ): Setting[Task[IvyConfiguration]] = externalIvySettingsURI(Def.value(url.toURI), addMultiResolver) - def externalIvySettingsURI(uri: Initialize[URI], - addMultiResolver: Boolean = true): Setting[Task[IvyConfiguration]] = { + def externalIvySettingsURI( + uri: Initialize[URI], + addMultiResolver: Boolean = true + ): Setting[Task[IvyConfiguration]] = { val other = Def.task { - (baseDirectory.value, - appConfiguration.value, - projectResolver.value, - updateOptions.value, - streams.value) + ( + baseDirectory.value, + appConfiguration.value, + projectResolver.value, + updateOptions.value, + streams.value + ) } ivyConfiguration := ((uri zipWith other) { case (u, otherTask) => @@ -3141,13 +3304,16 @@ trait BuildExtra extends BuildCommon with DefExtra { baseDirectory.value / name } - def externalIvyFile(file: Initialize[File] = inBase("ivy.xml"), - iScala: Initialize[Option[ScalaModuleInfo]] = scalaModuleInfo) - : Setting[Task[ModuleSettings]] = - moduleSettings := IvyFileConfiguration(ivyValidate.value, - iScala.value, - file.value, - managedScalaInstance.value) + def externalIvyFile( + file: Initialize[File] = inBase("ivy.xml"), + iScala: Initialize[Option[ScalaModuleInfo]] = scalaModuleInfo + ): Setting[Task[ModuleSettings]] = + moduleSettings := IvyFileConfiguration( + ivyValidate.value, + iScala.value, + file.value, + managedScalaInstance.value + ) def externalPom( file: Initialize[File] = inBase("pom.xml"), @@ -3160,9 +3326,11 @@ trait BuildExtra extends BuildCommon with DefExtra { managedScalaInstance.value, ) - def runInputTask(config: Configuration, - mainClass: String, - baseArguments: String*): Initialize[InputTask[Unit]] = + def runInputTask( + config: Configuration, + mainClass: String, + baseArguments: String* + ): Initialize[InputTask[Unit]] = Def.inputTask { import Def._ val r = (runner in (config, run)).value @@ -3171,9 +3339,11 @@ trait BuildExtra extends BuildCommon with DefExtra { r.run(mainClass, data(cp), baseArguments ++ args, streams.value.log).get } - def runTask(config: Configuration, - mainClass: String, - arguments: String*): Initialize[Task[Unit]] = + def runTask( + config: Configuration, + mainClass: String, + arguments: String* + ): Initialize[Task[Unit]] = Def.task { val cp = (fullClasspath in config).value val r = (runner in (config, run)).value @@ -3183,10 +3353,12 @@ trait BuildExtra extends BuildCommon with DefExtra { // public API /** Returns a vector of settings that create custom run input task. */ - def fullRunInputTask(scoped: InputKey[Unit], - config: Configuration, - mainClass: String, - baseArguments: String*): Vector[Setting[_]] = { + def fullRunInputTask( + scoped: InputKey[Unit], + config: Configuration, + mainClass: String, + baseArguments: String* + ): Vector[Setting[_]] = { // TODO: Re-write to avoid InputTask.apply which is deprecated // I tried "Def.spaceDelimited().parsed" (after importing Def.parserToInput) // but it broke actions/run-task @@ -3210,10 +3382,12 @@ trait BuildExtra extends BuildCommon with DefExtra { // public API /** Returns a vector of settings that create custom run task. */ - def fullRunTask(scoped: TaskKey[Unit], - config: Configuration, - mainClass: String, - arguments: String*): Vector[Setting[_]] = + def fullRunTask( + scoped: TaskKey[Unit], + config: Configuration, + mainClass: String, + arguments: String* + ): Vector[Setting[_]] = Vector( scoped := ((initScoped(scoped.scopedKey, runnerInit) .zipWith(Def.task { ((fullClasspath in config).value, streams.value) })) { @@ -3239,7 +3413,8 @@ trait BuildExtra extends BuildCommon with DefExtra { inConfig(config)(definedTests := detectTests.value).head def filterKeys(ss: Seq[Setting[_]], transitive: Boolean = false)( - f: ScopedKey[_] => Boolean): Seq[Setting[_]] = + f: ScopedKey[_] => Boolean + ): Seq[Setting[_]] = ss filter (s => f(s.key) && (!transitive || s.dependencies.forall(f))) } @@ -3295,24 +3470,29 @@ trait BuildCommon { SessionVar.get(SessionVar.resolveContext(task.scopedKey, context.scope, s), s) def loadFromContext[T](task: TaskKey[T], context: ScopedKey[_], s: State)( - implicit f: JsonFormat[T]): Option[T] = + implicit f: JsonFormat[T] + ): Option[T] = SessionVar.load(SessionVar.resolveContext(task.scopedKey, context.scope, s), s) // intended for use in constructing InputTasks - def loadForParser[P, T](task: TaskKey[T])(f: (State, Option[T]) => Parser[P])( - implicit format: JsonFormat[T]): Initialize[State => Parser[P]] = + def loadForParser[P, T](task: TaskKey[T])( + f: (State, Option[T]) => Parser[P] + )(implicit format: JsonFormat[T]): Initialize[State => Parser[P]] = loadForParserI(task)(Def value f)(format) - def loadForParserI[P, T](task: TaskKey[T])(init: Initialize[(State, Option[T]) => Parser[P]])( - implicit format: JsonFormat[T]): Initialize[State => Parser[P]] = + def loadForParserI[P, T](task: TaskKey[T])( + init: Initialize[(State, Option[T]) => Parser[P]] + )(implicit format: JsonFormat[T]): Initialize[State => Parser[P]] = Def.setting { (s: State) => init.value(s, loadFromContext(task, resolvedScoped.value, s)(format)) } - def getForParser[P, T](task: TaskKey[T])( - init: (State, Option[T]) => Parser[P]): Initialize[State => Parser[P]] = + def getForParser[P, T]( + task: TaskKey[T] + )(init: (State, Option[T]) => Parser[P]): Initialize[State => Parser[P]] = getForParserI(task)(Def value init) - def getForParserI[P, T](task: TaskKey[T])( - init: Initialize[(State, Option[T]) => Parser[P]]): Initialize[State => Parser[P]] = + def getForParserI[P, T]( + task: TaskKey[T] + )(init: Initialize[(State, Option[T]) => Parser[P]]): Initialize[State => Parser[P]] = Def.setting { (s: State) => init.value(s, getFromContext(task, resolvedScoped.value, s)) } diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index fed8b4acb..479530f15 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -172,9 +172,11 @@ object EvaluateTask { val SystemProcessors = Runtime.getRuntime.availableProcessors - def extractedTaskConfig(extracted: Extracted, - structure: BuildStructure, - state: State): EvaluateTaskConfig = { + def extractedTaskConfig( + extracted: Extracted, + structure: BuildStructure, + state: State + ): EvaluateTaskConfig = { val rs = restrictions(extracted, structure) val canceller = cancelStrategy(extracted, structure, state) val progress = executeProgress(extracted, structure, state) @@ -193,10 +195,12 @@ object EvaluateTask { } def restrictions(extracted: Extracted, structure: BuildStructure): Seq[Tags.Rule] = - getSetting(Keys.concurrentRestrictions, - defaultRestrictions(extracted, structure), - extracted, - structure) + getSetting( + Keys.concurrentRestrictions, + defaultRestrictions(extracted, structure), + extracted, + structure + ) def maxWorkers(extracted: Extracted, structure: BuildStructure): Int = if (getSetting(Keys.parallelExecution, true, extracted, structure)) @@ -207,22 +211,27 @@ object EvaluateTask { def cancelable(extracted: Extracted, structure: BuildStructure): Boolean = getSetting(Keys.cancelable, false, extracted, structure) - def cancelStrategy(extracted: Extracted, - structure: BuildStructure, - state: State): TaskCancellationStrategy = + def cancelStrategy( + extracted: Extracted, + structure: BuildStructure, + state: State + ): TaskCancellationStrategy = getSetting(Keys.taskCancelStrategy, { (_: State) => TaskCancellationStrategy.Null }, extracted, structure)(state) - private[sbt] def executeProgress(extracted: Extracted, - structure: BuildStructure, - state: State): ExecuteProgress[Task] = { + private[sbt] def executeProgress( + extracted: Extracted, + structure: BuildStructure, + state: State + ): ExecuteProgress[Task] = { import Types.const val maker: State => Keys.TaskProgress = getSetting( Keys.executeProgress, const(new Keys.TaskProgress(defaultProgress)), extracted, - structure) + structure + ) maker(state).progress } // TODO - Should this pull from Global or from the project itself? @@ -230,15 +239,19 @@ object EvaluateTask { getSetting(Keys.forcegc in Global, GCUtil.defaultForceGarbageCollection, extracted, structure) // TODO - Should this pull from Global or from the project itself? private[sbt] def minForcegcInterval(extracted: Extracted, structure: BuildStructure): Duration = - getSetting(Keys.minForcegcInterval in Global, - GCUtil.defaultMinForcegcInterval, - extracted, - structure) + getSetting( + Keys.minForcegcInterval in Global, + GCUtil.defaultMinForcegcInterval, + extracted, + structure + ) - def getSetting[T](key: SettingKey[T], - default: T, - extracted: Extracted, - structure: BuildStructure): T = + def getSetting[T]( + key: SettingKey[T], + default: T, + extracted: Extracted, + structure: BuildStructure + ): T = key in extracted.currentRef get structure.data getOrElse default def injectSettings: Seq[Setting[_]] = Seq( @@ -258,7 +271,8 @@ object EvaluateTask { val evaluated = apply(pluginDef, ScopedKey(pluginKey.scope, pluginKey.key), state, root, config) val (newS, result) = evaluated getOrElse sys.error( - "Plugin data does not exist for plugin definition at " + pluginDef.root) + "Plugin data does not exist for plugin definition at " + pluginDef.root + ) Project.runUnloadHooks(newS) // discard states processResult2(result) } @@ -268,26 +282,32 @@ object EvaluateTask { * If the task is not defined, None is returned. The provided task key is resolved against the current project `ref`. * Task execution is configured according to settings defined in the loaded project. */ - def apply[T](structure: BuildStructure, - taskKey: ScopedKey[Task[T]], - state: State, - ref: ProjectRef): Option[(State, Result[T])] = - apply[T](structure, - taskKey, - state, - ref, - extractedTaskConfig(Project.extract(state), structure, state)) + def apply[T]( + structure: BuildStructure, + taskKey: ScopedKey[Task[T]], + state: State, + ref: ProjectRef + ): Option[(State, Result[T])] = + apply[T]( + structure, + taskKey, + state, + ref, + extractedTaskConfig(Project.extract(state), structure, state) + ) /** * Evaluates `taskKey` and returns the new State and the result of the task wrapped in Some. * If the task is not defined, None is returned. The provided task key is resolved against the current project `ref`. * `config` configures concurrency and canceling of task execution. */ - def apply[T](structure: BuildStructure, - taskKey: ScopedKey[Task[T]], - state: State, - ref: ProjectRef, - config: EvaluateTaskConfig): Option[(State, Result[T])] = { + def apply[T]( + structure: BuildStructure, + taskKey: ScopedKey[Task[T]], + state: State, + ref: ProjectRef, + config: EvaluateTaskConfig + ): Option[(State, Result[T])] = { withStreams(structure, state) { str => for ((task, toNode) <- getTask(structure, taskKey, state, str, ref)) yield runTask(task, state, str, structure.index.triggers, config)(toNode) @@ -335,34 +355,41 @@ object EvaluateTask { try { f(str) } finally { str.close() } } - def getTask[T](structure: BuildStructure, - taskKey: ScopedKey[Task[T]], - state: State, - streams: Streams, - ref: ProjectRef): Option[(Task[T], NodeView[Task])] = { + def getTask[T]( + structure: BuildStructure, + taskKey: ScopedKey[Task[T]], + state: State, + streams: Streams, + ref: ProjectRef + ): Option[(Task[T], NodeView[Task])] = { val thisScope = Load.projectScope(ref) val resolvedScope = Scope.replaceThis(thisScope)(taskKey.scope) for (t <- structure.data.get(resolvedScope, taskKey.key)) yield (t, nodeView(state, streams, taskKey :: Nil)) } - def nodeView[HL <: HList](state: State, - streams: Streams, - roots: Seq[ScopedKey[_]], - dummies: DummyTaskMap = DummyTaskMap(Nil)): NodeView[Task] = + def nodeView[HL <: HList]( + state: State, + streams: Streams, + roots: Seq[ScopedKey[_]], + dummies: DummyTaskMap = DummyTaskMap(Nil) + ): NodeView[Task] = Transform( - (dummyRoots, roots) :: (Def.dummyStreamsManager, streams) :: (dummyState, state) :: dummies) + (dummyRoots, roots) :: (Def.dummyStreamsManager, streams) :: (dummyState, state) :: dummies + ) def runTask[T]( root: Task[T], state: State, streams: Streams, triggers: Triggers[Task], - config: EvaluateTaskConfig)(implicit taskToNode: NodeView[Task]): (State, Result[T]) = { + config: EvaluateTaskConfig + )(implicit taskToNode: NodeView[Task]): (State, Result[T]) = { import ConcurrentRestrictions.{ completionService, tagged, tagsKey } val log = state.log log.debug( - s"Running task... Cancel: ${config.cancelStrategy}, check cycles: ${config.checkCycles}, forcegc: ${config.forceGarbageCollection}") + s"Running task... Cancel: ${config.cancelStrategy}, check cycles: ${config.checkCycles}, forcegc: ${config.forceGarbageCollection}" + ) val tags = tagged[Task[_]](_.info get tagsKey getOrElse Map.empty, Tags.predicate(config.restrictions)) val (service, shutdownThreads) = @@ -383,9 +410,11 @@ object EvaluateTask { case _ => true } def run() = { - val x = new Execute[Task](Execute.config(config.checkCycles, overwriteNode), - triggers, - config.progressReporter)(taskToNode) + val x = new Execute[Task]( + Execute.config(config.checkCycles, overwriteNode), + triggers, + config.progressReporter + )(taskToNode) val (newState, result) = try { val results = x.runKeep(root)(service) @@ -410,15 +439,19 @@ object EvaluateTask { finally strat.onTaskEngineFinish(cancelState) } - private[this] def storeValuesForPrevious(results: RMap[Task, Result], - state: State, - streams: Streams): Unit = + private[this] def storeValuesForPrevious( + results: RMap[Task, Result], + state: State, + streams: Streams + ): Unit = for (referenced <- Previous.references in Global get Project.structure(state).data) Previous.complete(referenced, results, streams) - def applyResults[T](results: RMap[Task, Result], - state: State, - root: Task[T]): (State, Result[T]) = + def applyResults[T]( + results: RMap[Task, Result], + state: State, + root: Task[T] + ): (State, Result[T]) = (stateTransform(results)(state), results(root)) def stateTransform(results: RMap[Task, Result]): State => State = Function.chain( diff --git a/main/src/main/scala/sbt/Extracted.scala b/main/src/main/scala/sbt/Extracted.scala index c35b6021d..20c2115a1 100644 --- a/main/src/main/scala/sbt/Extracted.scala +++ b/main/src/main/scala/sbt/Extracted.scala @@ -16,9 +16,11 @@ import sbt.util.Show import std.Transform.DummyTaskMap import sbt.EvaluateTask.extractedTaskConfig -final case class Extracted(structure: BuildStructure, - session: SessionSettings, - currentRef: ProjectRef)(implicit val showKey: Show[ScopedKey[_]]) { +final case class Extracted( + structure: BuildStructure, + session: SessionSettings, + currentRef: ProjectRef +)(implicit val showKey: Show[ScopedKey[_]]) { def rootProject = structure.rootProject lazy val currentUnit = structure units currentRef.build lazy val currentProject = currentUnit defined currentRef.project @@ -123,7 +125,8 @@ final case class Extracted(structure: BuildStructure, @deprecated( "This discards session settings. Migrate to appendWithSession or appendWithoutSession.", - "1.2.0") + "1.2.0" + ) def append(settings: Seq[Setting[_]], state: State): State = appendWithoutSession(settings, state) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index c19d44c40..ac1ad03be 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -69,7 +69,8 @@ final class xMain extends xsbti.AppMain { val state = StandardMain.initialState( configuration, Seq(defaults, early), - runEarly(DefaultsCommand) :: runEarly(InitCommand) :: BootCommand :: Nil) + runEarly(DefaultsCommand) :: runEarly(InitCommand) :: BootCommand :: Nil + ) StandardMain.runManaged(state) } } @@ -119,13 +120,17 @@ object StandardMain { ConsoleOut.systemOutOverwrite(ConsoleOut.overwriteContaining("Resolving ")) def initialGlobalLogging: GlobalLogging = - GlobalLogging.initial(MainAppender.globalDefault(console), - File.createTempFile("sbt", ".log"), - console) + GlobalLogging.initial( + MainAppender.globalDefault(console), + File.createTempFile("sbt", ".log"), + console + ) - def initialState(configuration: xsbti.AppConfiguration, - initialDefinitions: Seq[Command], - preCommands: Seq[String]): State = { + def initialState( + configuration: xsbti.AppConfiguration, + initialDefinitions: Seq[Command], + preCommands: Seq[String] + ): State = { // This is to workaround https://github.com/sbt/io/issues/110 sys.props.put("jna.nosys", "true") @@ -277,21 +282,27 @@ object BuiltinCommands { catch { case _: Exception => None } def settingsCommand: Command = - showSettingLike(SettingsCommand, - settingsPreamble, - KeyRanks.MainSettingCutoff, - key => !isTask(key.manifest)) + showSettingLike( + SettingsCommand, + settingsPreamble, + KeyRanks.MainSettingCutoff, + key => !isTask(key.manifest) + ) def tasks: Command = - showSettingLike(TasksCommand, - tasksPreamble, - KeyRanks.MainTaskCutoff, - key => isTask(key.manifest)) + showSettingLike( + TasksCommand, + tasksPreamble, + KeyRanks.MainTaskCutoff, + key => isTask(key.manifest) + ) - def showSettingLike(command: String, - preamble: String, - cutoff: Int, - keep: AttributeKey[_] => Boolean): Command = + def showSettingLike( + command: String, + preamble: String, + cutoff: Int, + keep: AttributeKey[_] => Boolean + ): Command = Command(command, settingsBrief(command), settingsDetailed(command))(showSettingParser(keep)) { case (s: State, (verbosity: Int, selected: Option[String])) => if (selected.isEmpty) System.out.println(preamble) @@ -302,8 +313,9 @@ object BuiltinCommands { if (prominentOnly) System.out.println(moreAvailableMessage(command, selected.isDefined)) s } - def showSettingParser(keepKeys: AttributeKey[_] => Boolean)( - s: State): Parser[(Int, Option[String])] = + def showSettingParser( + keepKeys: AttributeKey[_] => Boolean + )(s: State): Parser[(Int, Option[String])] = verbosityParser ~ selectedParser(s, keepKeys).? def selectedParser(s: State, keepKeys: AttributeKey[_] => Boolean): Parser[String] = singleArgument(allTaskAndSettingKeys(s).filter(keepKeys).map(_.label).toSet) @@ -338,16 +350,19 @@ object BuiltinCommands { def sortByRank(keys: Seq[AttributeKey[_]]): Seq[AttributeKey[_]] = keys.sortBy(_.rank) def withDescription(keys: Seq[AttributeKey[_]]): Seq[AttributeKey[_]] = keys.filter(_.description.isDefined) - def isTask(mf: Manifest[_])(implicit taskMF: Manifest[Task[_]], - inputMF: Manifest[InputTask[_]]): Boolean = + def isTask( + mf: Manifest[_] + )(implicit taskMF: Manifest[Task[_]], inputMF: Manifest[InputTask[_]]): Boolean = mf.runtimeClass == taskMF.runtimeClass || mf.runtimeClass == inputMF.runtimeClass def topNRanked(n: Int) = (keys: Seq[AttributeKey[_]]) => sortByRank(keys).take(n) def highPass(rankCutoff: Int) = (keys: Seq[AttributeKey[_]]) => sortByRank(keys).takeWhile(_.rank <= rankCutoff) - def tasksHelp(s: State, - filter: Seq[AttributeKey[_]] => Seq[AttributeKey[_]], - arg: Option[String]): String = { + def tasksHelp( + s: State, + filter: Seq[AttributeKey[_]] => Seq[AttributeKey[_]], + arg: Option[String] + ): String = { val commandAndDescription = taskDetail(filter(allTaskAndSettingKeys(s))) arg match { case Some(selected) => detail(selected, commandAndDescription.toMap) @@ -618,8 +633,9 @@ object BuiltinCommands { } def projects: Command = - Command(ProjectsCommand, (ProjectsCommand, projectsBrief), projectsDetailed)(s => - projectsParser(s).?) { + Command(ProjectsCommand, (ProjectsCommand, projectsBrief), projectsDetailed)( + s => projectsParser(s).? + ) { case (s, Some(modifyBuilds)) => transformExtraBuilds(s, modifyBuilds) case (s, None) => showProjects(s); s } @@ -658,7 +674,8 @@ object BuiltinCommands { @tailrec private[this] def doLoadFailed(s: State, loadArg: String): State = { val result = (SimpleReader.readLine( - "Project loading failed: (r)etry, (q)uit, (l)ast, or (i)gnore? ") getOrElse Quit) + "Project loading failed: (r)etry, (q)uit, (l)ast, or (i)gnore? " + ) getOrElse Quit) .toLowerCase(Locale.ENGLISH) def matches(s: String) = !result.isEmpty && (s startsWith result) def retry = loadProjectCommand(LoadProject, loadArg) :: s.clearGlobalLog @@ -684,8 +701,9 @@ object BuiltinCommands { Nil def loadProject: Command = - Command(LoadProject, LoadProjectBrief, LoadProjectDetailed)(loadProjectParser)((s, arg) => - loadProjectCommands(arg) ::: s) + Command(LoadProject, LoadProjectBrief, LoadProjectDetailed)(loadProjectParser)( + (s, arg) => loadProjectCommands(arg) ::: s + ) private[this] def loadProjectParser: State => Parser[String] = _ => matched(Project.loadActionParser) @@ -707,11 +725,13 @@ object BuiltinCommands { Option(buildProperties.getProperty("sbt.version")) } else None - sbtVersionOpt.foreach(version => - if (version != app.id.version()) { - state.log.warn(s"""sbt version mismatch, current: ${app.id - .version()}, in build.properties: "$version", use 'reboot' to use the new value.""") - }) + sbtVersionOpt.foreach( + version => + if (version != app.id.version()) { + state.log.warn(s"""sbt version mismatch, current: ${app.id + .version()}, in build.properties: "$version", use 'reboot' to use the new value.""") + } + ) } def doLoadProject(s0: State, action: LoadAction.Value): State = { @@ -758,8 +778,10 @@ object BuiltinCommands { exchange publishEventMessage ConsolePromptEvent(s0) val exec: Exec = exchange.blockUntilNextExec val newState = s1 - .copy(onFailure = Some(Exec(Shell, None)), - remainingCommands = exec +: Exec(Shell, None) +: s1.remainingCommands) + .copy( + onFailure = Some(Exec(Shell, None)), + remainingCommands = exec +: Exec(Shell, None) +: s1.remainingCommands + ) .setInteractive(true) exchange publishEventMessage ConsoleUnpromptEvent(exec.source) if (exec.commandLine.trim.isEmpty) newState diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index e2c1fe0f2..3b007ced4 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -67,7 +67,8 @@ object MainLoop { throw new xsbti.FullReload(e.arguments.toArray, false) case NonFatal(e) => 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 + ) deleteLastLog(logBacking) throw e } diff --git a/main/src/main/scala/sbt/Opts.scala b/main/src/main/scala/sbt/Opts.scala index 2d6ac2c6c..e788b027e 100644 --- a/main/src/main/scala/sbt/Opts.scala +++ b/main/src/main/scala/sbt/Opts.scala @@ -45,9 +45,11 @@ object Opts { val sonatypeSnapshots = Resolver.sonatypeRepo("snapshots") val sonatypeStaging = MavenRepository( "sonatype-staging", - "https://oss.sonatype.org/service/local/staging/deploy/maven2") + "https://oss.sonatype.org/service/local/staging/deploy/maven2" + ) val mavenLocalFile = Resolver.file("Local Repository", userHome / ".m2" / "repository" asFile)( - Resolver.defaultPatterns) + Resolver.defaultPatterns + ) val sbtSnapshots = Resolver.bintrayRepo("sbt", "maven-snapshots") val sbtIvySnapshots = Resolver.bintrayIvyRepo("sbt", "ivy-snapshots") } diff --git a/main/src/main/scala/sbt/PluginCross.scala b/main/src/main/scala/sbt/PluginCross.scala index 0da74dc7b..3e3fd4bba 100644 --- a/main/src/main/scala/sbt/PluginCross.scala +++ b/main/src/main/scala/sbt/PluginCross.scala @@ -25,7 +25,8 @@ private[sbt] object PluginCross { lazy val pluginSwitch: Command = { def switchParser(state: State): Parser[(String, String)] = { lazy val switchArgs = token(NotSpace.examples()) ~ (token( - Space ~> matched(state.combinedParser)) ?? "") + Space ~> matched(state.combinedParser) + ) ?? "") lazy val nextSpaced = spacedFirst(PluginSwitchCommand) token(PluginSwitchCommand ~ OptSpace) flatMap { _ => switchArgs & nextSpaced @@ -58,8 +59,11 @@ private[sbt] object PluginCross { def crossParser(state: State): Parser[String] = token(PluginCrossCommand <~ OptSpace) flatMap { _ => token( - matched(state.combinedParser & - spacedFirst(PluginCrossCommand))) + matched( + state.combinedParser & + spacedFirst(PluginCrossCommand) + ) + ) } def crossVersions(state: State): List[String] = { val x = Project.extract(state) diff --git a/main/src/main/scala/sbt/Plugins.scala b/main/src/main/scala/sbt/Plugins.scala index a0aa1ee2a..7df9f0561 100644 --- a/main/src/main/scala/sbt/Plugins.scala +++ b/main/src/main/scala/sbt/Plugins.scala @@ -202,10 +202,12 @@ object Plugins extends PluginsFunctions { _.head subsetOf knowledge0 }) log.debug( - s"deducing auto plugins based on known facts ${knowledge0.toString} and clauses ${clauses.toString}") + s"deducing auto plugins based on known facts ${knowledge0.toString} and clauses ${clauses.toString}" + ) Logic.reduce( clauses, - (flattenConvert(requestedPlugins) ++ convertAll(alwaysEnabled)).toSet) match { + (flattenConvert(requestedPlugins) ++ convertAll(alwaysEnabled)).toSet + ) match { case Left(problem) => throw AutoPluginException(problem) case Right(results) => log.debug(s" :: deduced result: ${results}") @@ -234,9 +236,11 @@ object Plugins extends PluginsFunctions { private[sbt] def topologicalSort(ns: List[AutoPlugin]): List[AutoPlugin] = { @tailrec - def doSort(found0: List[AutoPlugin], - notFound0: List[AutoPlugin], - limit0: Int): List[AutoPlugin] = { + def doSort( + found0: List[AutoPlugin], + notFound0: List[AutoPlugin], + limit0: Int + ): List[AutoPlugin] = { if (limit0 < 0) throw AutoPluginException(s"Failed to sort ${ns} topologically") else if (notFound0.isEmpty) found0 else { @@ -252,11 +256,9 @@ object Plugins extends PluginsFunctions { private[sbt] def translateMessage(e: LogicException) = e match { case ic: InitialContradictions => - s"Contradiction in selected plugins. These plugins were both included and excluded: ${literalsString( - ic.literals.toSeq)}" + s"Contradiction in selected plugins. These plugins were both included and excluded: ${literalsString(ic.literals.toSeq)}" case io: InitialOverlap => - s"Cannot directly enable plugins. Plugins are enabled when their required plugins are satisfied. The directly selected plugins were: ${literalsString( - io.literals.toSeq)}" + s"Cannot directly enable plugins. Plugins are enabled when their required plugins are satisfied. The directly selected plugins were: ${literalsString(io.literals.toSeq)}" case cn: CyclicNegation => s"Cycles in plugin requirements cannot involve excludes. The problematic cycle is: ${literalsString(cn.cycle)}" } @@ -273,9 +275,11 @@ object Plugins extends PluginsFunctions { throw AutoPluginException(message) } - private[this] def exclusionConflictError(requested: Plugins, - selected: Seq[AutoPlugin], - conflicting: Seq[AutoPlugin]): Unit = { + private[this] def exclusionConflictError( + requested: Plugins, + selected: Seq[AutoPlugin], + conflicting: Seq[AutoPlugin] + ): Unit = { def listConflicts(ns: Seq[AutoPlugin]) = (ns map { c => val reasons = (if (flatten(requested) contains c) List("requested") @@ -427,8 +431,9 @@ ${listConflicts(conflicting)}""") val pluginClazz = ap.getClass existsAutoImportVal(pluginClazz) .orElse( - catching(classOf[ClassNotFoundException]).opt( - Class.forName(s"${pluginClazz.getName}$autoImport$$", false, loader))) + catching(classOf[ClassNotFoundException]) + .opt(Class.forName(s"${pluginClazz.getName}$autoImport$$", false, loader)) + ) .isDefined } diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 8657f119f..e78a47f14 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -482,7 +482,8 @@ object Project extends ProjectExtra { val newState = unloaded.copy(attributes = newAttrs) // TODO: Fix this onLoad( - updateCurrent(newState) /*LogManager.setGlobalLogLevels(updateCurrent(newState), structure.data)*/ ) + updateCurrent(newState) /*LogManager.setGlobalLogLevels(updateCurrent(newState), structure.data)*/ + ) } def orIdentity[T](opt: Option[T => T]): T => T = opt getOrElse idFun @@ -517,8 +518,10 @@ object Project extends ProjectExtra { val srvLogLevel: Option[Level.Value] = (logLevel in (ref, serverLog)).get(structure.data) val hs: Option[Seq[ServerHandler]] = get(fullServerHandlers) val commandDefs = allCommands.distinct.flatten[Command].map(_ tag (projectCommand, true)) - val newDefinedCommands = commandDefs ++ BasicCommands.removeTagged(s.definedCommands, - projectCommand) + val newDefinedCommands = commandDefs ++ BasicCommands.removeTagged( + s.definedCommands, + projectCommand + ) val newAttrs = s.attributes .setCond(Watched.Configuration, watched) @@ -555,7 +558,8 @@ object Project extends ProjectExtra { } } private[this] def overlappingTargets( - targets: Seq[(ProjectRef, File)]): Map[File, Seq[ProjectRef]] = + targets: Seq[(ProjectRef, File)] + ): Map[File, Seq[ProjectRef]] = targets.groupBy(_._2).filter(_._2.size > 1).mapValues(_.map(_._1)) private[this] def allTargets(data: Settings[Scope]): Seq[(ProjectRef, File)] = { @@ -588,15 +592,18 @@ object Project extends ProjectExtra { def delegates(structure: BuildStructure, scope: Scope, key: AttributeKey[_]): Seq[ScopedKey[_]] = structure.delegates(scope).map(d => ScopedKey(d, key)) - def scopedKeyData(structure: BuildStructure, - scope: Scope, - key: AttributeKey[_]): Option[ScopedKeyData[_]] = + def scopedKeyData( + structure: BuildStructure, + scope: Scope, + key: AttributeKey[_] + ): Option[ScopedKeyData[_]] = structure.data.get(scope, key) map { v => ScopedKeyData(ScopedKey(scope, key), v) } def details(structure: BuildStructure, actual: Boolean, scope: Scope, key: AttributeKey[_])( - implicit display: Show[ScopedKey[_]]): String = { + implicit display: Show[ScopedKey[_]] + ): String = { val scoped = ScopedKey(scope, key) val data = scopedKeyData(structure, scope, key) map { _.description } getOrElse { @@ -637,20 +644,24 @@ object Project extends ProjectExtra { val reverse = reverseDependencies(cMap, scoped) val derivedReverse = reverse.filter(r => derivedDependencies(r).contains(definingScoped)).toSet - def printDepScopes(baseLabel: String, - derivedLabel: String, - scopes: Iterable[ScopedKey[_]], - derived: Set[ScopedKey[_]]): String = { + def printDepScopes( + baseLabel: String, + derivedLabel: String, + scopes: Iterable[ScopedKey[_]], + derived: Set[ScopedKey[_]] + ): String = { val label = s"$baseLabel${if (derived.isEmpty) "" else s" (D=$derivedLabel)"}" val prefix: ScopedKey[_] => String = if (derived.isEmpty) const("") else sk => if (derived(sk)) "D " else " " printScopes(label, scopes, prefix = prefix) } - def printScopes(label: String, - scopes: Iterable[ScopedKey[_]], - max: Int = Int.MaxValue, - prefix: ScopedKey[_] => String = const("")) = + def printScopes( + label: String, + scopes: Iterable[ScopedKey[_]], + max: Int = Int.MaxValue, + prefix: ScopedKey[_] => String = const("") + ) = if (scopes.isEmpty) "" else { val (limited, more) = @@ -668,23 +679,27 @@ object Project extends ProjectExtra { printScopes("Related", related, 10) } def settingGraph(structure: BuildStructure, basedir: File, scoped: ScopedKey[_])( - implicit display: Show[ScopedKey[_]]): SettingGraph = + implicit display: Show[ScopedKey[_]] + ): SettingGraph = SettingGraph(structure, basedir, scoped, 0) def graphSettings(structure: BuildStructure, basedir: File)( - implicit display: Show[ScopedKey[_]]): Unit = { + implicit display: Show[ScopedKey[_]] + ): Unit = { def graph(actual: Boolean, name: String) = graphSettings(structure, actual, name, new File(basedir, name + ".dot")) graph(true, "actual_dependencies") graph(false, "declared_dependencies") } def graphSettings(structure: BuildStructure, actual: Boolean, graphName: String, file: File)( - implicit display: Show[ScopedKey[_]]): Unit = { + implicit display: Show[ScopedKey[_]] + ): Unit = { val rel = relation(structure, actual) val keyToString = display.show _ DotGraph.generateGraph(file, graphName, rel, keyToString, keyToString) } def relation(structure: BuildStructure, actual: Boolean)( - implicit display: Show[ScopedKey[_]]): Relation[ScopedKey[_], ScopedKey[_]] = + implicit display: Show[ScopedKey[_]] + ): Relation[ScopedKey[_], ScopedKey[_]] = relation(structure.settings, actual)(structure.delegates, structure.scopeLocal, display) private[sbt] def relation(settings: Seq[Def.Setting[_]], actual: Boolean)( @@ -698,7 +713,8 @@ object Project extends ProjectExtra { } def showDefinitions(key: AttributeKey[_], defs: Seq[Scope])( - implicit display: Show[ScopedKey[_]]): String = + implicit display: Show[ScopedKey[_]] + ): String = showKeys(defs.map(scope => ScopedKey(scope, key))) def showUses(defs: Seq[ScopedKey[_]])(implicit display: Show[ScopedKey[_]]): String = @@ -708,17 +724,21 @@ object Project extends ProjectExtra { s.map(display.show).sorted.mkString("\n\t", "\n\t", "\n\n") def definitions(structure: BuildStructure, actual: Boolean, key: AttributeKey[_])( - implicit display: Show[ScopedKey[_]]): Seq[Scope] = + implicit display: Show[ScopedKey[_]] + ): Seq[Scope] = relation(structure, actual)(display)._1s.toSeq flatMap { sk => if (sk.key == key) sk.scope :: Nil else Nil } def usedBy(structure: BuildStructure, actual: Boolean, key: AttributeKey[_])( - implicit display: Show[ScopedKey[_]]): Seq[ScopedKey[_]] = + implicit display: Show[ScopedKey[_]] + ): Seq[ScopedKey[_]] = relation(structure, actual)(display).all.toSeq flatMap { case (a, b) => if (b.key == key) List[ScopedKey[_]](a) else Nil } - def reverseDependencies(cMap: Map[ScopedKey[_], Flattened], - scoped: ScopedKey[_]): Iterable[ScopedKey[_]] = + def reverseDependencies( + cMap: Map[ScopedKey[_], Flattened], + scoped: ScopedKey[_] + ): Iterable[ScopedKey[_]] = for ((key, compiled) <- cMap; dep <- compiled.dependencies if dep == scoped) yield key def setAll(extracted: Extracted, settings: Seq[Def.Setting[_]]): SessionSettings = @@ -726,7 +746,8 @@ object Project extends ProjectExtra { val ExtraBuilds = AttributeKey[List[URI]]( "extra-builds", - "Extra build URIs to load in addition to the ones defined by the project.") + "Extra build URIs to load in addition to the ones defined by the project." + ) def extraBuilds(s: State): List[URI] = getOrNil(s, ExtraBuilds) def getOrNil[T](s: State, key: AttributeKey[List[T]]): List[T] = s get key getOrElse Nil def setExtraBuilds(s: State, extra: List[URI]): State = s.put(ExtraBuilds, extra) @@ -812,15 +833,20 @@ object Project extends ProjectExtra { import TupleSyntax._ (Keys.resolvedScoped, i)( (scoped, task) => - tx(task, - (state, value) => - persistAndSet(resolveContext(key, scoped.scope, state), state, value)(f))) + tx( + task, + (state, value) => + persistAndSet(resolveContext(key, scoped.scope, state), state, value)(f) + ) + ) } def keepAs(key: TaskKey[S]): Def.Initialize[Task[S]] = { import TupleSyntax._ - (i, Keys.resolvedScoped)((t, scoped) => - tx(t, (state, value) => set(resolveContext(key, scoped.scope, state), state, value))) + (i, Keys.resolvedScoped)( + (t, scoped) => + tx(t, (state, value) => set(resolveContext(key, scoped.scope, state), state, value)) + ) } } @@ -831,7 +857,8 @@ object Project extends ProjectExtra { val enclosingValName = std.KeyMacro.definingValName( c, methodName => - s"""$methodName must be directly assigned to a val, such as `val x = $methodName`. Alternatively, you can use `sbt.Project.apply`""") + s"""$methodName must be directly assigned to a val, such as `val x = $methodName`. Alternatively, you can use `sbt.Project.apply`""" + ) val name = c.Expr[String](Literal(Constant(enclosingValName))) reify { Project(name.splice, new File(name.splice)) } } @@ -840,8 +867,9 @@ object Project extends ProjectExtra { private[sbt] trait GeneratedRootProject trait ProjectExtra { - implicit def configDependencyConstructor[T](p: T)( - implicit ev: T => ProjectReference): Constructor = + implicit def configDependencyConstructor[T]( + p: T + )(implicit ev: T => ProjectReference): Constructor = new Constructor(p) implicit def classpathDependency[T]( @@ -854,7 +882,8 @@ trait ProjectExtra { new Scoped.RichInitializeTask(init) implicit def richInitializeInputTask[T]( - init: Initialize[InputTask[T]]): Scoped.RichInitializeInputTask[T] = + init: Initialize[InputTask[T]] + ): Scoped.RichInitializeInputTask[T] = new Scoped.RichInitializeInputTask(init) implicit def richInitialize[T](i: Initialize[T]): Scoped.RichInitialize[T] = diff --git a/main/src/main/scala/sbt/RichURI.scala b/main/src/main/scala/sbt/RichURI.scala index 78a06372d..2e54ca65d 100644 --- a/main/src/main/scala/sbt/RichURI.scala +++ b/main/src/main/scala/sbt/RichURI.scala @@ -17,13 +17,15 @@ class RichURI(uri: URI) { * Note that this method simply passes the individual components of this URI to the URI constructor * that accepts each component individually. It is thus limited by the implementation restrictions of the relevant methods. */ - def copy(scheme: String = uri.getScheme, - userInfo: String = uri.getUserInfo, - host: String = uri.getHost, - port: Int = uri.getPort, - path: String = uri.getPath, - query: String = uri.getQuery, - fragment: String = uri.getFragment) = + def copy( + scheme: String = uri.getScheme, + userInfo: String = uri.getUserInfo, + host: String = uri.getHost, + port: Int = uri.getPort, + path: String = uri.getPath, + query: String = uri.getQuery, + fragment: String = uri.getFragment + ) = new URI(scheme, userInfo, host, port, path, query, fragment) /** Returns `true` if the fragment of the URI is defined. */ diff --git a/main/src/main/scala/sbt/ScopeFilter.scala b/main/src/main/scala/sbt/ScopeFilter.scala index 4ce8aca12..fea70d23e 100644 --- a/main/src/main/scala/sbt/ScopeFilter.scala +++ b/main/src/main/scala/sbt/ScopeFilter.scala @@ -30,9 +30,11 @@ object ScopeFilter { * If a task filter is not supplied, global is selected. * Generally, always specify the project axis. */ - def apply(projects: ProjectFilter = inProjects(ThisProject), - configurations: ConfigurationFilter = zeroAxis, - tasks: TaskFilter = zeroAxis): ScopeFilter = + def apply( + projects: ProjectFilter = inProjects(ThisProject), + configurations: ConfigurationFilter = zeroAxis, + tasks: TaskFilter = zeroAxis + ): ScopeFilter = new ScopeFilter { private[sbt] def apply(data: Data): Scope => Boolean = { val pf = projects(data) @@ -116,27 +118,35 @@ object ScopeFilter { * Selects Scopes that have a project axis that is aggregated by `ref`, transitively if `transitive` is true. * If `includeRoot` is true, Scopes with `ref` itself as the project axis value are also selected. */ - def inAggregates(ref: ProjectReference, - transitive: Boolean = true, - includeRoot: Boolean = true): ProjectFilter = - byDeps(ref, - transitive = transitive, - includeRoot = includeRoot, - aggregate = true, - classpath = false) + def inAggregates( + ref: ProjectReference, + transitive: Boolean = true, + includeRoot: Boolean = true + ): ProjectFilter = + byDeps( + ref, + transitive = transitive, + includeRoot = includeRoot, + aggregate = true, + classpath = false + ) /** * Selects Scopes that have a project axis that is a dependency of `ref`, transitively if `transitive` is true. * If `includeRoot` is true, Scopes with `ref` itself as the project axis value are also selected. */ - def inDependencies(ref: ProjectReference, - transitive: Boolean = true, - includeRoot: Boolean = true): ProjectFilter = - byDeps(ref, - transitive = transitive, - includeRoot = includeRoot, - aggregate = false, - classpath = true) + def inDependencies( + ref: ProjectReference, + transitive: Boolean = true, + includeRoot: Boolean = true + ): ProjectFilter = + byDeps( + ref, + transitive = transitive, + includeRoot = includeRoot, + aggregate = false, + classpath = true + ) /** Selects Scopes that have a project axis with one of the provided values.*/ def inProjects(projects: ProjectReference*): ProjectFilter = @@ -172,9 +182,11 @@ object ScopeFilter { * Information provided to Scope filters. These provide project relationships, * project reference resolution, and the list of all static Scopes. */ - private final class Data(val units: Map[URI, LoadedBuildUnit], - val resolve: ProjectReference => ProjectRef, - val allScopes: Set[Scope]) + private final class Data( + val units: Map[URI, LoadedBuildUnit], + val resolve: ProjectReference => ProjectRef, + val allScopes: Set[Scope] + ) /** Constructs a Data instance from the list of static scopes and the project relationships.*/ private[this] val getData: Initialize[Data] = @@ -195,20 +207,24 @@ object ScopeFilter { new Data(build.units, resolve, scopes) } - private[this] def getDependencies(structure: Map[URI, LoadedBuildUnit], - classpath: Boolean, - aggregate: Boolean): ProjectRef => Seq[ProjectRef] = + private[this] def getDependencies( + structure: Map[URI, LoadedBuildUnit], + classpath: Boolean, + aggregate: Boolean + ): ProjectRef => Seq[ProjectRef] = ref => Project.getProject(ref, structure).toList flatMap { p => (if (classpath) p.dependencies.map(_.project) else Nil) ++ (if (aggregate) p.aggregate else Nil) } - private[this] def byDeps(ref: ProjectReference, - transitive: Boolean, - includeRoot: Boolean, - aggregate: Boolean, - classpath: Boolean): ProjectFilter = + private[this] def byDeps( + ref: ProjectReference, + transitive: Boolean, + includeRoot: Boolean, + aggregate: Boolean, + classpath: Boolean + ): ProjectFilter = inResolvedProjects { data => val resolvedRef = data.resolve(ref) val direct = getDependencies(data.units, classpath = classpath, aggregate = aggregate) diff --git a/main/src/main/scala/sbt/ScopedKeyData.scala b/main/src/main/scala/sbt/ScopedKeyData.scala index 3574f455d..abfabb2f4 100644 --- a/main/src/main/scala/sbt/ScopedKeyData.scala +++ b/main/src/main/scala/sbt/ScopedKeyData.scala @@ -16,9 +16,11 @@ final case class ScopedKeyData[A](scoped: ScopedKey[A], value: Any) { def typeName: String = fold(fmtMf("Task[%s]"), fmtMf("InputTask[%s]"), key.manifest.toString) def settingValue: Option[Any] = fold(const(None), const(None), Some(value)) def description: String = - fold(fmtMf("Task: %s"), - fmtMf("Input task: %s"), - "Setting: %s = %s" format (key.manifest.toString, value.toString)) + fold( + fmtMf("Task: %s"), + fmtMf("Input task: %s"), + "Setting: %s = %s" format (key.manifest.toString, value.toString) + ) def fold[T](targ: OptManifest[_] => T, itarg: OptManifest[_] => T, s: => T): T = key.manifest.runtimeClass match { case TaskClass => targ(key.manifest.typeArguments.head) diff --git a/main/src/main/scala/sbt/ScriptedPlugin.scala b/main/src/main/scala/sbt/ScriptedPlugin.scala index 6e2544426..f1dfd3434 100644 --- a/main/src/main/scala/sbt/ScriptedPlugin.scala +++ b/main/src/main/scala/sbt/ScriptedPlugin.scala @@ -39,7 +39,8 @@ object ScriptedPlugin extends AutoPlugin { val scriptedBatchExecution = settingKey[Boolean]("Enables or disables batch execution for scripted.") val scriptedParallelInstances = settingKey[Int]( - "Configures the number of scripted instances for parallel testing, only used in batch mode.") + "Configures the number of scripted instances for parallel testing, only used in batch mode." + ) val scriptedRun = taskKey[Method]("") val scriptedLaunchOpts = settingKey[Seq[String]]("options to pass to jvm launching scripted tasks") diff --git a/main/src/main/scala/sbt/SessionVar.scala b/main/src/main/scala/sbt/SessionVar.scala index 58ec38b15..da5e41213 100644 --- a/main/src/main/scala/sbt/SessionVar.scala +++ b/main/src/main/scala/sbt/SessionVar.scala @@ -28,7 +28,8 @@ object SessionVar { def emptyMap = Map(IMap.empty) def persistAndSet[T](key: ScopedKey[Task[T]], state: State, value: T)( - implicit f: JsonFormat[T]): State = { + implicit f: JsonFormat[T] + ): State = { persist(key, state, value)(f) set(key, state, value) } @@ -70,7 +71,8 @@ object SessionVar { get(key, state) orElse read(key, state)(f) def loadAndSet[T](key: ScopedKey[Task[T]], state: State, setIfUnset: Boolean = true)( - implicit f: JsonFormat[T]): (State, Option[T]) = + implicit f: JsonFormat[T] + ): (State, Option[T]) = get(key, state) match { case s: Some[T] => (state, s) case None => diff --git a/main/src/main/scala/sbt/TemplateCommand.scala b/main/src/main/scala/sbt/TemplateCommand.scala index ae1cafa33..a7759ed91 100644 --- a/main/src/main/scala/sbt/TemplateCommand.scala +++ b/main/src/main/scala/sbt/TemplateCommand.scala @@ -22,7 +22,8 @@ import BasicCommandStrings._, BasicKeys._ private[sbt] object TemplateCommandUtil { def templateCommand: Command = Command(TemplateCommand, templateBrief, templateDetailed)(_ => templateCommandParser)( - runTemplate) + runTemplate + ) private def templateCommandParser: Parser[Seq[String]] = (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map (_ => Nil)) @@ -35,10 +36,12 @@ private[sbt] object TemplateCommandUtil { val templateStage = stagingDirectory / "new" // This moves the target directory to a staging directory // https://github.com/sbt/sbt/issues/2835 - val state = extracted0.appendWithSession(Seq( - Keys.target := templateStage - ), - s0) + val state = extracted0.appendWithSession( + Seq( + Keys.target := templateStage + ), + s0 + ) val infos = (state get templateResolverInfos getOrElse Nil).toList val log = state.globalLogging.full val extracted = (Project extract state) @@ -74,18 +77,22 @@ private[sbt] object TemplateCommandUtil { case None => System.err.println("Template not found for: " + arguments.mkString(" ")) } - private def tryTemplate(info: TemplateResolverInfo, - arguments: List[String], - loader: ClassLoader): Boolean = { + private def tryTemplate( + info: TemplateResolverInfo, + arguments: List[String], + loader: ClassLoader + ): Boolean = { val resultObj = call(info.implementationClass, "isDefined", loader)( classOf[Array[String]] )(arguments.toArray) resultObj.asInstanceOf[Boolean] } - private def runTemplate(info: TemplateResolverInfo, - arguments: List[String], - loader: ClassLoader): Unit = { + private def runTemplate( + info: TemplateResolverInfo, + arguments: List[String], + loader: ClassLoader + ): Unit = { call(info.implementationClass, "run", loader)(classOf[Array[String]])(arguments.toArray) () } diff --git a/main/src/main/scala/sbt/internal/Act.scala b/main/src/main/scala/sbt/internal/Act.scala index 9349f2b64..953598933 100644 --- a/main/src/main/scala/sbt/internal/Act.scala +++ b/main/src/main/scala/sbt/internal/Act.scala @@ -32,44 +32,56 @@ object Act { token(OptSpace ~> '/' <~ OptSpace).examples("/").map(_ => ()) // this does not take aggregation into account - def scopedKey(index: KeyIndex, - current: ProjectRef, - defaultConfigs: Option[ResolvedReference] => Seq[String], - keyMap: Map[String, AttributeKey[_]], - data: Settings[Scope]): Parser[ScopedKey[_]] = + def scopedKey( + index: KeyIndex, + current: ProjectRef, + defaultConfigs: Option[ResolvedReference] => Seq[String], + keyMap: Map[String, AttributeKey[_]], + data: Settings[Scope] + ): Parser[ScopedKey[_]] = scopedKeySelected(index, current, defaultConfigs, keyMap, data).map(_.key) // the index should be an aggregated index for proper tab completion - def scopedKeyAggregated(current: ProjectRef, - defaultConfigs: Option[ResolvedReference] => Seq[String], - structure: BuildStructure): KeysParser = - for (selected <- scopedKeySelected(structure.index.aggregateKeyIndex, - current, - defaultConfigs, - structure.index.keyMap, - structure.data)) + def scopedKeyAggregated( + current: ProjectRef, + defaultConfigs: Option[ResolvedReference] => Seq[String], + structure: BuildStructure + ): KeysParser = + for (selected <- scopedKeySelected( + structure.index.aggregateKeyIndex, + current, + defaultConfigs, + structure.index.keyMap, + structure.data + )) yield Aggregation.aggregate(selected.key, selected.mask, structure.extra) - def scopedKeySelected(index: KeyIndex, - current: ProjectRef, - defaultConfigs: Option[ResolvedReference] => Seq[String], - keyMap: Map[String, AttributeKey[_]], - data: Settings[Scope]): Parser[ParsedKey] = + def scopedKeySelected( + index: KeyIndex, + current: ProjectRef, + defaultConfigs: Option[ResolvedReference] => Seq[String], + keyMap: Map[String, AttributeKey[_]], + data: Settings[Scope] + ): Parser[ParsedKey] = scopedKeyFull(index, current, defaultConfigs, keyMap) flatMap { choices => select(choices, data)(showRelativeKey2(current)) } - def scopedKeyFull(index: KeyIndex, - current: ProjectRef, - defaultConfigs: Option[ResolvedReference] => Seq[String], - keyMap: Map[String, AttributeKey[_]]): Parser[Seq[Parser[ParsedKey]]] = { + def scopedKeyFull( + index: KeyIndex, + current: ProjectRef, + defaultConfigs: Option[ResolvedReference] => Seq[String], + keyMap: Map[String, AttributeKey[_]] + ): Parser[Seq[Parser[ParsedKey]]] = { def fullKey = for { rawProject <- optProjectRef(index, current) proj = resolveProject(rawProject, current) - confAmb <- configIdent(index configs proj, - index configIdents proj, - index.fromConfigIdent(proj)) + confAmb <- configIdent( + index configs proj, + index configIdents proj, + index.fromConfigIdent(proj) + ) partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false) } yield taskKeyExtra(index, defaultConfigs, keyMap, proj, confAmb, partialMask) @@ -78,12 +90,14 @@ object Act { for { g <- globalIdent } yield - taskKeyExtra(index, - defaultConfigs, - keyMap, - None, - ParsedZero, - ScopeMask(true, true, false, false)) + taskKeyExtra( + index, + defaultConfigs, + keyMap, + None, + ParsedZero, + ScopeMask(true, true, false, false) + ) globalKey | fullKey } @@ -109,17 +123,21 @@ object Act { new ParsedKey(makeScopedKey(proj, conf, task, extra, key), mask) } - def makeScopedKey(proj: Option[ResolvedReference], - conf: Option[String], - task: Option[AttributeKey[_]], - extra: ScopeAxis[AttributeMap], - key: AttributeKey[_]): ScopedKey[_] = + def makeScopedKey( + proj: Option[ResolvedReference], + conf: Option[String], + task: Option[AttributeKey[_]], + extra: ScopeAxis[AttributeMap], + key: AttributeKey[_] + ): ScopedKey[_] = ScopedKey( Scope(toAxis(proj, Zero), toAxis(conf map ConfigKey.apply, Zero), toAxis(task, Zero), extra), - key) + key + ) def select(allKeys: Seq[Parser[ParsedKey]], data: Settings[Scope])( - implicit show: Show[ScopedKey[_]]): Parser[ParsedKey] = + implicit show: Show[ScopedKey[_]] + ): Parser[ParsedKey] = seq(allKeys) flatMap { ss => val default = ss.headOption match { case None => noValidKeys @@ -128,7 +146,8 @@ object Act { selectFromValid(ss filter isValid(data), default) } def selectFromValid(ss: Seq[ParsedKey], default: Parser[ParsedKey])( - implicit show: Show[ScopedKey[_]]): Parser[ParsedKey] = + implicit show: Show[ScopedKey[_]] + ): Parser[ParsedKey] = selectByTask(selectByConfig(ss)) match { case Seq() => default case Seq(single) => success(single) @@ -179,9 +198,11 @@ object Act { } // New configuration parser that's able to parse configuration ident trailed by slash. - private[sbt] def configIdent(confs: Set[String], - idents: Set[String], - fromIdent: String => String): Parser[ParsedAxis[String]] = { + private[sbt] def configIdent( + confs: Set[String], + idents: Set[String], + fromIdent: String => String + ): Parser[ParsedAxis[String]] = { val oldSep: Parser[Char] = ':' val sep: Parser[Unit] = spacedSlash !!! "Expected '/'" token( @@ -195,14 +216,17 @@ object Act { ) ?? Omitted } - def configs(explicit: ParsedAxis[String], - defaultConfigs: Option[ResolvedReference] => Seq[String], - proj: Option[ResolvedReference], - index: KeyIndex): Seq[Option[String]] = + def configs( + explicit: ParsedAxis[String], + defaultConfigs: Option[ResolvedReference] => Seq[String], + proj: Option[ResolvedReference], + index: KeyIndex + ): Seq[Option[String]] = explicit match { case Omitted => None +: defaultConfigurations(proj, index, defaultConfigs).flatMap( - nonEmptyConfig(index, proj)) + nonEmptyConfig(index, proj) + ) case ParsedZero | ParsedGlobal => None :: Nil case pv: ParsedValue[x] => Some(pv.value) :: Nil } @@ -214,15 +238,19 @@ object Act { ): Seq[String] = if (index exists proj) defaultConfigs(proj) else Nil - def nonEmptyConfig(index: KeyIndex, - proj: Option[ResolvedReference]): String => Seq[Option[String]] = + def nonEmptyConfig( + index: KeyIndex, + proj: Option[ResolvedReference] + ): String => Seq[Option[String]] = config => if (index.isEmpty(proj, Some(config))) Nil else Some(config) :: Nil - def key(index: KeyIndex, - proj: Option[ResolvedReference], - conf: Option[String], - task: Option[AttributeKey[_]], - keyMap: Map[String, AttributeKey[_]]): Parser[AttributeKey[_]] = { + def key( + index: KeyIndex, + proj: Option[ResolvedReference], + conf: Option[String], + task: Option[AttributeKey[_]], + keyMap: Map[String, AttributeKey[_]] + ): Parser[AttributeKey[_]] = { def dropHyphenated(keys: Set[String]): Set[String] = keys.filterNot(Util.hasHyphen) def keyParser(keys: Set[String]): Parser[AttributeKey[_]] = token(ID !!! "Expected key" examples dropHyphenated(keys)) flatMap { keyString => @@ -240,9 +268,11 @@ object Act { keyParser(keys) } - def getKey[T](keyMap: Map[String, AttributeKey[_]], - keyString: String, - f: AttributeKey[_] => T): Parser[T] = + def getKey[T]( + keyMap: Map[String, AttributeKey[_]], + keyString: String, + f: AttributeKey[_] => T + ): Parser[T] = keyMap.get(keyString) match { case Some(k) => success(f(k)) case None => failure(Command.invalidValue("key", keyMap.keys)(keyString)) @@ -250,8 +280,10 @@ object Act { val spacedComma = token(OptSpace ~ ',' ~ OptSpace) - def extraAxis(knownKeys: Map[String, AttributeKey[_]], - knownValues: IMap[AttributeKey, Set]): Parser[ScopeAxis[AttributeMap]] = { + def extraAxis( + knownKeys: Map[String, AttributeKey[_]], + knownValues: IMap[AttributeKey, Set] + ): Parser[ScopeAxis[AttributeMap]] = { val extrasP = extrasParser(knownKeys, knownValues) val extras = token('(', hide = _ == 1 && knownValues.isEmpty) ~> extrasP <~ token(')') optionalAxis(extras, Zero) @@ -271,7 +303,8 @@ object Act { (token( value(keyP) | ZeroString ^^^ ParsedZero - | ZeroIdent ^^^ ParsedZero) <~ (token("::".id) | spacedSlash)) ?? Omitted + | ZeroIdent ^^^ ParsedZero + ) <~ (token("::".id) | spacedSlash)) ?? Omitted } def resolveTask(task: ParsedAxis[AttributeKey[_]]): Option[AttributeKey[_]] = @@ -283,8 +316,10 @@ object Act { def filterStrings(base: Parser[String], valid: Set[String], label: String): Parser[String] = base.filter(valid, Command.invalidValue(label, valid)) - def extrasParser(knownKeys: Map[String, AttributeKey[_]], - knownValues: IMap[AttributeKey, Set]): Parser[AttributeMap] = { + def extrasParser( + knownKeys: Map[String, AttributeKey[_]], + knownValues: IMap[AttributeKey, Set] + ): Parser[AttributeMap] = { val validKeys = knownKeys.filter { case (_, key) => knownValues get key exists (_.nonEmpty) } if (validKeys.isEmpty) failure("No valid extra keys.") @@ -292,8 +327,10 @@ object Act { rep1sep(extraParser(validKeys, knownValues), spacedComma) map AttributeMap.apply } - def extraParser(knownKeys: Map[String, AttributeKey[_]], - knownValues: IMap[AttributeKey, Set]): Parser[AttributeEntry[_]] = { + def extraParser( + knownKeys: Map[String, AttributeKey[_]], + knownValues: IMap[AttributeKey, Set] + ): Parser[AttributeEntry[_]] = { val keyp = knownIDParser(knownKeys, "Not a valid extra key") <~ token(':' ~ OptSpace) keyp flatMap { case key: AttributeKey[t] => @@ -321,12 +358,15 @@ object Act { value(resolvedReference(index, currentBuild, trailing)) } - private[sbt] def resolvedReferenceIdent(index: KeyIndex, - currentBuild: URI, - trailing: Parser[_]): Parser[ResolvedReference] = { + private[sbt] def resolvedReferenceIdent( + index: KeyIndex, + currentBuild: URI, + trailing: Parser[_] + ): Parser[ResolvedReference] = { def projectID(uri: URI) = token( - DQuoteChar ~> examplesStrict(ID, index projects uri, "project ID") <~ DQuoteChar <~ OptSpace <~ ")" <~ trailing) + DQuoteChar ~> examplesStrict(ID, index projects uri, "project ID") <~ DQuoteChar <~ OptSpace <~ ")" <~ trailing + ) def projectRef(uri: URI) = projectID(uri) map { id => ProjectRef(uri, id) } @@ -336,15 +376,18 @@ object Act { val buildRef = token( "ProjectRef(" ~> OptSpace ~> "uri(" ~> OptSpace ~> DQuoteChar ~> - resolvedURI <~ DQuoteChar <~ OptSpace <~ ")" <~ spacedComma) + resolvedURI <~ DQuoteChar <~ OptSpace <~ ")" <~ spacedComma + ) buildRef flatMap { uri => projectRef(uri) } } - def resolvedReference(index: KeyIndex, - currentBuild: URI, - trailing: Parser[_]): Parser[ResolvedReference] = { + def resolvedReference( + index: KeyIndex, + currentBuild: URI, + trailing: Parser[_] + ): Parser[ResolvedReference] = { def projectID(uri: URI) = token(examplesStrict(ID, index projects uri, "project ID") <~ trailing) def projectRef(uri: URI) = projectID(uri) map { id => @@ -363,8 +406,10 @@ object Act { def optProjectRef(index: KeyIndex, current: ProjectRef): Parser[ParsedAxis[ResolvedReference]] = projectRef(index, current.build) ?? Omitted - def resolveProject(parsed: ParsedAxis[ResolvedReference], - current: ProjectRef): Option[ResolvedReference] = + def resolveProject( + parsed: ParsedAxis[ResolvedReference], + current: ProjectRef + ): Option[ResolvedReference] = parsed match { case Omitted => Some(current) case ParsedZero => None @@ -412,11 +457,13 @@ object Act { def scopedKeyParser(extracted: Extracted): Parser[ScopedKey[_]] = scopedKeyParser(extracted.structure, extracted.currentRef) def scopedKeyParser(structure: BuildStructure, currentRef: ProjectRef): Parser[ScopedKey[_]] = - scopedKey(structure.index.keyIndex, - currentRef, - structure.extra.configurationsForAxis, - structure.index.keyMap, - structure.data) + scopedKey( + structure.index.keyIndex, + currentRef, + structure.extra.configurationsForAxis, + structure.index.keyMap, + structure.data + ) type KeysParser = Parser[Seq[ScopedKey[T]] forSome { type T }] def aggregatedKeyParser(state: State): KeysParser = aggregatedKeyParser(Project extract state) @@ -435,17 +482,21 @@ object Act { KeyValue(key, value) } } - private[this] def anyKeyValues(structure: BuildStructure, - keys: Seq[ScopedKey[_]]): Seq[KeyValue[_]] = + private[this] def anyKeyValues( + structure: BuildStructure, + keys: Seq[ScopedKey[_]] + ): Seq[KeyValue[_]] = keys.flatMap { key => getValue(structure.data, key.scope, key.key) map { value => KeyValue(key, value) } } - private[this] def getValue[T](data: Settings[Scope], - scope: Scope, - key: AttributeKey[T]): Option[T] = + private[this] def getValue[T]( + data: Settings[Scope], + scope: Scope, + key: AttributeKey[T] + ): Option[T] = if (java.lang.Boolean.getBoolean("sbt.cli.nodelegation")) data.getDirect(scope, key) else data.get(scope, key) diff --git a/main/src/main/scala/sbt/internal/Aggregation.scala b/main/src/main/scala/sbt/internal/Aggregation.scala index ea523df53..c6a2c9828 100644 --- a/main/src/main/scala/sbt/internal/Aggregation.scala +++ b/main/src/main/scala/sbt/internal/Aggregation.scala @@ -102,10 +102,12 @@ object Aggregation { Complete(start, stop, result, newS) } - def runTasks[HL <: HList, T](s: State, - ts: Values[Task[T]], - extra: DummyTaskMap, - show: ShowConfig)(implicit display: Show[ScopedKey[_]]): State = { + def runTasks[HL <: HList, T]( + s: State, + ts: Values[Task[T]], + extra: DummyTaskMap, + show: ShowConfig + )(implicit display: Show[ScopedKey[_]]): State = { val complete = timedRun[T](s, ts, extra) showRun(complete, show) complete.results match { @@ -226,8 +228,9 @@ object Aggregation { reverse: Boolean ): Seq[ProjectRef] = { val resRef = proj.map(p => extra.projectRefFor(extra.resolveRef(p))) - resRef.toList.flatMap(ref => - if (reverse) extra.aggregates.reverse(ref) else extra.aggregates.forward(ref)) + resRef.toList.flatMap( + ref => if (reverse) extra.aggregates.reverse(ref) else extra.aggregates.forward(ref) + ) } def aggregate[T, Proj]( diff --git a/main/src/main/scala/sbt/internal/BuildDependencies.scala b/main/src/main/scala/sbt/internal/BuildDependencies.scala index 6b6709610..437018cf7 100644 --- a/main/src/main/scala/sbt/internal/BuildDependencies.scala +++ b/main/src/main/scala/sbt/internal/BuildDependencies.scala @@ -12,8 +12,10 @@ import sbt.internal.util.Types.idFun import sbt.internal.util.Dag import BuildDependencies._ -final class BuildDependencies private (val classpath: DependencyMap[ClasspathDep[ProjectRef]], - val aggregate: DependencyMap[ProjectRef]) { +final class BuildDependencies private ( + val classpath: DependencyMap[ClasspathDep[ProjectRef]], + val aggregate: DependencyMap[ProjectRef] +) { def classpathRefs(ref: ProjectRef): Seq[ProjectRef] = classpath(ref) map getID def classpathTransitiveRefs(ref: ProjectRef): Seq[ProjectRef] = classpathTransitive(ref) @@ -27,8 +29,10 @@ final class BuildDependencies private (val classpath: DependencyMap[ClasspathDep new BuildDependencies(classpath, aggregate.updated(ref, deps ++ aggregate.getOrElse(ref, Nil))) } object BuildDependencies { - def apply(classpath: DependencyMap[ClasspathDep[ProjectRef]], - aggregate: DependencyMap[ProjectRef]): BuildDependencies = + def apply( + classpath: DependencyMap[ClasspathDep[ProjectRef]], + aggregate: DependencyMap[ProjectRef] + ): BuildDependencies = new BuildDependencies(classpath, aggregate) type DependencyMap[D] = Map[ProjectRef, Seq[D]] diff --git a/main/src/main/scala/sbt/internal/BuildLoader.scala b/main/src/main/scala/sbt/internal/BuildLoader.scala index 11d024337..bdc697b68 100644 --- a/main/src/main/scala/sbt/internal/BuildLoader.scala +++ b/main/src/main/scala/sbt/internal/BuildLoader.scala @@ -16,20 +16,24 @@ import sbt.internal.util.Types.{ const, idFun } import sbt.util.Logger import sbt.librarymanagement.ModuleID -final class MultiHandler[S, T](builtIn: S => Option[T], - root: Option[S => Option[T]], - nonRoots: List[(URI, S => Option[T])], - getURI: S => URI, - log: S => Logger) { +final class MultiHandler[S, T]( + builtIn: S => Option[T], + root: Option[S => Option[T]], + nonRoots: List[(URI, S => Option[T])], + getURI: S => URI, + log: S => Logger +) { def applyFun: S => Option[T] = apply def apply(info: S): Option[T] = (baseLoader(info), applyNonRoots(info)) match { case (None, Nil) => None case (None, xs @ (_, nr) :: ignored) => if (ignored.nonEmpty) - warn("Using first of multiple matching non-root build resolvers for " + getURI(info), - log(info), - xs) + warn( + "Using first of multiple matching non-root build resolvers for " + getURI(info), + log(info), + xs + ) Some(nr) case (Some(b), xs) => if (xs.nonEmpty) @@ -72,28 +76,34 @@ object BuildLoader { type Loader = LoadInfo => Option[() => BuildUnit] type TransformAll = PartBuild => PartBuild - final class Components(val resolver: Resolver, - val builder: Builder, - val transformer: Transformer, - val full: Loader, - val transformAll: TransformAll) { + final class Components( + val resolver: Resolver, + val builder: Builder, + val transformer: Transformer, + val full: Loader, + val transformAll: TransformAll + ) { def |(cs: Components): Components = - new Components(resolver | cs.resolver, - builder | cs.builder, - seq(transformer, cs.transformer), - full | cs.full, - transformAll andThen cs.transformAll) + new Components( + resolver | cs.resolver, + builder | cs.builder, + seq(transformer, cs.transformer), + full | cs.full, + transformAll andThen cs.transformAll + ) } def transform(t: Transformer): Components = components(transformer = t) def resolve(r: Resolver): Components = components(resolver = r) def build(b: Builder): Components = components(builder = b) def full(f: Loader): Components = components(full = f) def transformAll(t: TransformAll) = components(transformAll = t) - def components(resolver: Resolver = const(None), - builder: Builder = const(None), - transformer: Transformer = _.unit, - full: Loader = const(None), - transformAll: TransformAll = idFun) = + def components( + resolver: Resolver = const(None), + builder: Builder = const(None), + transformer: Transformer = _.unit, + full: Loader = const(None), + transformAll: TransformAll = idFun + ) = new Components(resolver, builder, transformer, full, transformAll) def seq(a: Transformer, b: Transformer): Transformer = info => b(info.setUnit(a(info))) @@ -103,47 +113,55 @@ object BuildLoader { def config: LoadBuildConfiguration def state: State } - final class ResolveInfo(val uri: URI, - val staging: File, - val config: LoadBuildConfiguration, - val state: State) - extends Info - final class BuildInfo(val uri: URI, - val base: File, - val config: LoadBuildConfiguration, - val state: State) - extends Info - final class TransformInfo(val uri: URI, - val base: File, - val unit: BuildUnit, - val config: LoadBuildConfiguration, - val state: State) - extends Info { + final class ResolveInfo( + val uri: URI, + val staging: File, + val config: LoadBuildConfiguration, + val state: State + ) extends Info + final class BuildInfo( + val uri: URI, + val base: File, + val config: LoadBuildConfiguration, + val state: State + ) extends Info + final class TransformInfo( + val uri: URI, + val base: File, + val unit: BuildUnit, + val config: LoadBuildConfiguration, + val state: State + ) extends Info { def setUnit(newUnit: BuildUnit): TransformInfo = new TransformInfo(uri, base, newUnit, config, state) } - final class LoadInfo(val uri: URI, - val staging: File, - val config: LoadBuildConfiguration, - val state: State, - val components: Components) - extends Info + final class LoadInfo( + val uri: URI, + val staging: File, + val config: LoadBuildConfiguration, + val state: State, + val components: Components + ) extends Info - def apply(base: Components, - fail: URI => Nothing, - s: State, - config: LoadBuildConfiguration): BuildLoader = { + def apply( + base: Components, + fail: URI => Nothing, + s: State, + config: LoadBuildConfiguration + ): BuildLoader = { def makeMulti[S <: Info, T](base: S => Option[T]) = new MultiHandler[S, T](base, None, Nil, _.uri, _.config.log) - new BuildLoader(fail, - s, - config, - makeMulti(base.resolver), - makeMulti(base.builder), - base.transformer, - makeMulti(base.full), - base.transformAll) + new BuildLoader( + fail, + s, + config, + makeMulti(base.resolver), + makeMulti(base.builder), + base.transformer, + makeMulti(base.full), + base.transformAll + ) } def componentLoader: Loader = (info: LoadInfo) => { diff --git a/main/src/main/scala/sbt/internal/BuildStructure.scala b/main/src/main/scala/sbt/internal/BuildStructure.scala index 65b5eea6a..04db31933 100644 --- a/main/src/main/scala/sbt/internal/BuildStructure.scala +++ b/main/src/main/scala/sbt/internal/BuildStructure.scala @@ -20,14 +20,16 @@ import sbt.internal.util.Attributed.data import sbt.util.Logger import sjsonnew.shaded.scalajson.ast.unsafe.JValue -final class BuildStructure(val units: Map[URI, LoadedBuildUnit], - val root: URI, - val settings: Seq[Setting[_]], - val data: Settings[Scope], - val index: StructureIndex, - val streams: State => Streams, - val delegates: Scope => Seq[Scope], - val scopeLocal: ScopeLocal) { +final class BuildStructure( + val units: Map[URI, LoadedBuildUnit], + val root: URI, + val settings: Seq[Setting[_]], + val data: Settings[Scope], + val index: StructureIndex, + val streams: State => Streams, + val delegates: Scope => Seq[Scope], + val scopeLocal: ScopeLocal +) { val rootProject: URI => String = Load getRootProject units def allProjects: Seq[ResolvedProject] = units.values.flatMap(_.defined.values).toSeq def allProjects(build: URI): Seq[ResolvedProject] = @@ -59,11 +61,12 @@ final class StructureIndex( * @param rootProjects The list of project IDs for the projects considered roots of this build. * The first root project is used as the default in several situations where a project is not otherwise selected. */ -final class LoadedBuildUnit(val unit: BuildUnit, - val defined: Map[String, ResolvedProject], - val rootProjects: Seq[String], - val buildSettings: Seq[Setting[_]]) - extends BuildUnitBase { +final class LoadedBuildUnit( + val unit: BuildUnit, + val defined: Map[String, ResolvedProject], + val rootProjects: Seq[String], + val buildSettings: Seq[Setting[_]] +) extends BuildUnitBase { /** * The project to use as the default when one is not otherwise selected. @@ -72,7 +75,8 @@ final class LoadedBuildUnit(val unit: BuildUnit, val root = rootProjects match { case Nil => throw new java.lang.AssertionError( - "assertion failed: No root projects defined for build unit " + unit) + "assertion failed: No root projects defined for build unit " + unit + ) case Seq(root, _*) => root } @@ -157,8 +161,10 @@ case class DetectedAutoPlugin(name: String, value: AutoPlugin, hasAutoImport: Bo * @param builds The [[BuildDef]]s detected in the build definition. * This does not include the default [[BuildDef]] that sbt creates if none is defined. */ -final class DetectedPlugins(val autoPlugins: Seq[DetectedAutoPlugin], - val builds: DetectedModules[BuildDef]) { +final class DetectedPlugins( + val autoPlugins: Seq[DetectedAutoPlugin], + val builds: DetectedModules[BuildDef] +) { /** * Sequence of import expressions for the build definition. @@ -201,10 +207,12 @@ final class DetectedPlugins(val autoPlugins: Seq[DetectedAutoPlugin], * @param loader The class loader for the build definition project, notably excluding classes used for .sbt files. * @param detected Auto-detected modules in the build definition. */ -final class LoadedPlugins(val base: File, - val pluginData: PluginData, - val loader: ClassLoader, - val detected: DetectedPlugins) { +final class LoadedPlugins( + val base: File, + val pluginData: PluginData, + val loader: ClassLoader, + val detected: DetectedPlugins +) { def fullClasspath: Seq[Attributed[File]] = pluginData.classpath def classpath = data(fullClasspath) } @@ -215,10 +223,12 @@ final class LoadedPlugins(val base: File, * @param localBase The working location of the build on the filesystem. * For local URIs, this is the same as `uri`, but for remote URIs, this is the local copy or workspace allocated for the build. */ -final class BuildUnit(val uri: URI, - val localBase: File, - val definitions: LoadedDefinitions, - val plugins: LoadedPlugins) { +final class BuildUnit( + val uri: URI, + val localBase: File, + val definitions: LoadedDefinitions, + val plugins: LoadedPlugins +) { override def toString = if (uri.getScheme == "file") localBase.toString else (uri + " (locally: " + localBase + ")") } @@ -234,11 +244,12 @@ final class LoadedBuild(val root: URI, val units: Map[URI, LoadedBuildUnit]) { } final class PartBuild(val root: URI, val units: Map[URI, PartBuildUnit]) sealed trait BuildUnitBase { def rootProjects: Seq[String]; def buildSettings: Seq[Setting[_]] } -final class PartBuildUnit(val unit: BuildUnit, - val defined: Map[String, Project], - val rootProjects: Seq[String], - val buildSettings: Seq[Setting[_]]) - extends BuildUnitBase { +final class PartBuildUnit( + val unit: BuildUnit, + val defined: Map[String, Project], + val rootProjects: Seq[String], + val buildSettings: Seq[Setting[_]] +) extends BuildUnitBase { def resolve(f: Project => ResolvedProject): LoadedBuildUnit = new LoadedBuildUnit(unit, defined mapValues f toMap, rootProjects, buildSettings) def resolveRefs(f: ProjectReference => ProjectRef): LoadedBuildUnit = resolve(_ resolve f) @@ -251,29 +262,37 @@ object BuildStreams { final val BuildUnitPath = "$build" final val StreamsDirectory = "streams" - def mkStreams(units: Map[URI, LoadedBuildUnit], - root: URI, - data: Settings[Scope]): State => Streams = s => { + def mkStreams( + units: Map[URI, LoadedBuildUnit], + root: URI, + data: Settings[Scope] + ): State => Streams = s => { implicit val isoString: sjsonnew.IsoString[JValue] = - sjsonnew.IsoString.iso(sjsonnew.support.scalajson.unsafe.CompactPrinter.apply, - sjsonnew.support.scalajson.unsafe.Parser.parseUnsafe) + sjsonnew.IsoString.iso( + sjsonnew.support.scalajson.unsafe.CompactPrinter.apply, + sjsonnew.support.scalajson.unsafe.Parser.parseUnsafe + ) (s get Keys.stateStreams) getOrElse { - std.Streams(path(units, root, data), - displayFull, - LogManager.construct(data, s), - sjsonnew.support.scalajson.unsafe.Converter) + std.Streams( + path(units, root, data), + displayFull, + LogManager.construct(data, s), + sjsonnew.support.scalajson.unsafe.Converter + ) } } def path(units: Map[URI, LoadedBuildUnit], root: URI, data: Settings[Scope])( - scoped: ScopedKey[_]): File = + scoped: ScopedKey[_] + ): File = resolvePath(projectPath(units, root, scoped, data), nonProjectPath(scoped)) def resolvePath(base: File, components: Seq[String]): File = (base /: components)((b, p) => new File(b, p)) def pathComponent[T](axis: ScopeAxis[T], scoped: ScopedKey[_], label: String)( - show: T => String): String = + show: T => String + ): String = axis match { case Zero => GlobalPath case This => @@ -292,10 +311,12 @@ object BuildStreams { a.entries.toSeq.sortBy(_.key.label).map { case AttributeEntry(key, value) => key.label + "=" + value.toString } mkString (" ") - def projectPath(units: Map[URI, LoadedBuildUnit], - root: URI, - scoped: ScopedKey[_], - data: Settings[Scope]): File = + def projectPath( + units: Map[URI, LoadedBuildUnit], + root: URI, + scoped: ScopedKey[_], + data: Settings[Scope] + ): File = scoped.scope.project match { case Zero => refTarget(GlobalScope, units(root).localBase, data) / GlobalPath case Select(br @ BuildRef(uri)) => refTarget(br, units(uri).localBase, data) / BuildUnitPath diff --git a/main/src/main/scala/sbt/internal/BuildUtil.scala b/main/src/main/scala/sbt/internal/BuildUtil.scala index 0d1ac38e5..a62511a30 100644 --- a/main/src/main/scala/sbt/internal/BuildUtil.scala +++ b/main/src/main/scala/sbt/internal/BuildUtil.scala @@ -48,10 +48,12 @@ final class BuildUtil[Proj]( refOpt => configurations(projectForAxis(refOpt)).map(_.name) } object BuildUtil { - def apply(root: URI, - units: Map[URI, LoadedBuildUnit], - keyIndex: KeyIndex, - data: Settings[Scope]): BuildUtil[ResolvedProject] = { + def apply( + root: URI, + units: Map[URI, LoadedBuildUnit], + keyIndex: KeyIndex, + data: Settings[Scope] + ): BuildUtil[ResolvedProject] = { val getp = (build: URI, project: String) => Load.getProject(units, build, project) val configs = (_: ResolvedProject).configurations.map(c => ConfigKey(c.name)) val aggregates = aggregationRelation(units) @@ -72,8 +74,9 @@ object BuildUtil { def checkCycles(units: Map[URI, LoadedBuildUnit]): Unit = { def getRef(pref: ProjectRef) = units(pref.build).defined(pref.project) - def deps(proj: ResolvedProject)( - base: ResolvedProject => Seq[ProjectRef]): Seq[ResolvedProject] = + def deps( + proj: ResolvedProject + )(base: ResolvedProject => Seq[ProjectRef]): Seq[ResolvedProject] = Dag.topologicalSort(proj)(p => base(p) map getRef) // check for cycles for ((_, lbu) <- units; proj <- lbu.defined.values) { diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 2a4e1c105..9474a83b6 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -147,7 +147,8 @@ private[sbt] final class CommandExchange { server = Some(serverInstance) case Some(Failure(_: AlreadyRunningException)) => s.log.warn( - "sbt server could not start because there's another instance of sbt running on this build.") + "sbt server could not start because there's another instance of sbt running on this build." + ) s.log.warn("Running multiple instances is unsupported") server = None firstInstance.set(false) diff --git a/main/src/main/scala/sbt/internal/CommandStrings.scala b/main/src/main/scala/sbt/internal/CommandStrings.scala index 278985b64..1239cc034 100644 --- a/main/src/main/scala/sbt/internal/CommandStrings.scala +++ b/main/src/main/scala/sbt/internal/CommandStrings.scala @@ -115,8 +115,10 @@ $LastCommand val InspectCommand = "inspect" val inspectBrief = - (s"$InspectCommand [tree|uses|definitions|actual] ", - "Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.") + ( + s"$InspectCommand [tree|uses|definitions|actual] ", + "Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies." + ) val inspectDetailed = s""" |$InspectCommand | diff --git a/main/src/main/scala/sbt/internal/ConsoleProject.scala b/main/src/main/scala/sbt/internal/ConsoleProject.scala index f4b554c51..bb1279d8f 100644 --- a/main/src/main/scala/sbt/internal/ConsoleProject.scala +++ b/main/src/main/scala/sbt/internal/ConsoleProject.scala @@ -15,7 +15,8 @@ import xsbti.compile.ClasspathOptionsUtil object ConsoleProject { def apply(state: State, extra: String, cleanupCommands: String = "", options: Seq[String] = Nil)( - implicit log: Logger): Unit = { + implicit log: Logger + ): Unit = { val extracted = Project extract state val cpImports = new Imports(extracted, state) val bindings = ("currentState" -> state) :: ("extracted" -> extracted) :: ("cpHelpers" -> cpImports) :: Nil diff --git a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala index a4dc0641a..873ac2fdb 100644 --- a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala +++ b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala @@ -113,9 +113,11 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe protected def makeContext(id: Long, spawningTask: ScopedKey[_], state: State): ManagedLogger - def doRunInBackground(spawningTask: ScopedKey[_], - state: State, - start: (Logger, File) => BackgroundJob): JobHandle = { + def doRunInBackground( + spawningTask: ScopedKey[_], + state: State, + start: (Logger, File) => BackgroundJob + ): JobHandle = { val id = nextId.getAndIncrement() val logger = makeContext(id, spawningTask, state) val workingDir = serviceTempDir / s"job-$id" @@ -132,7 +134,8 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe } override def runInBackground(spawningTask: ScopedKey[_], state: State)( - start: (Logger, File) => Unit): JobHandle = { + start: (Logger, File) => Unit + ): JobHandle = { pool.run(this, spawningTask, state)(start) } @@ -155,7 +158,8 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe case _: DeadHandle @unchecked => () // nothing to stop or wait for case other => sys.error( - s"BackgroundJobHandle does not originate with the current BackgroundJobService: $other") + s"BackgroundJobHandle does not originate with the current BackgroundJobService: $other" + ) } private def withHandleTry(job: JobHandle)(f: ThreadJobHandle => Try[Unit]): Try[Unit] = @@ -163,8 +167,11 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe case handle: ThreadJobHandle @unchecked => f(handle) case _: DeadHandle @unchecked => Try(()) // nothing to stop or wait for case other => - Try(sys.error( - s"BackgroundJobHandle does not originate with the current BackgroundJobService: $other")) + Try( + sys.error( + s"BackgroundJobHandle does not originate with the current BackgroundJobService: $other" + ) + ) } override def stop(job: JobHandle): Unit = @@ -363,7 +370,8 @@ private[sbt] class BackgroundThreadPool extends java.io.Closeable { } def run(manager: AbstractBackgroundJobService, spawningTask: ScopedKey[_], state: State)( - work: (Logger, File) => Unit): JobHandle = { + work: (Logger, File) => Unit + ): JobHandle = { def start(logger: Logger, workingDir: File): BackgroundJob = { val runnable = new BackgroundRunnable(spawningTask.key.label, { () => work(logger, workingDir) diff --git a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala index 26e5348ff..347e23b86 100644 --- a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -44,9 +44,11 @@ private[sbt] object EvaluateConfigurations { /** * This represents the parsed expressions in a build sbt, as well as where they were defined. */ - private[this] final class ParsedFile(val imports: Seq[(String, Int)], - val definitions: Seq[(String, LineRange)], - val settings: Seq[(String, LineRange)]) + private[this] final class ParsedFile( + val imports: Seq[(String, Int)], + val definitions: Seq[(String, LineRange)], + val settings: Seq[(String, LineRange)] + ) /** The keywords we look for when classifying a string as a definition. */ private[this] val DefinitionKeywords = Seq("lazy val ", "def ", "val ") @@ -72,9 +74,11 @@ private[sbt] object EvaluateConfigurations { * * Note: This ignores any non-Setting[_] values in the file. */ - def evaluateConfiguration(eval: Eval, - src: File, - imports: Seq[String]): LazyClassLoaded[Seq[Setting[_]]] = + def evaluateConfiguration( + eval: Eval, + src: File, + imports: Seq[String] + ): LazyClassLoaded[Seq[Setting[_]]] = evaluateConfiguration(eval, src, IO.readLines(src), imports, 0) /** @@ -83,14 +87,17 @@ private[sbt] object EvaluateConfigurations { * * @param builtinImports The set of import statements to add to those parsed in the .sbt file. */ - private[this] def parseConfiguration(file: File, - lines: Seq[String], - builtinImports: Seq[String], - offset: Int): ParsedFile = { + private[this] def parseConfiguration( + file: File, + lines: Seq[String], + builtinImports: Seq[String], + offset: Int + ): ParsedFile = { val (importStatements, settingsAndDefinitions) = splitExpressions(file, lines) val allImports = builtinImports.map(s => (s, -1)) ++ addOffset(offset, importStatements) val (definitions, settings) = splitSettingsDefinitions( - addOffsetToRange(offset, settingsAndDefinitions)) + addOffsetToRange(offset, settingsAndDefinitions) + ) new ParsedFile(allImports, definitions, settings) } @@ -104,11 +111,13 @@ private[sbt] object EvaluateConfigurations { * * @return Just the Setting[_] instances defined in the .sbt file. */ - def evaluateConfiguration(eval: Eval, - file: File, - lines: Seq[String], - imports: Seq[String], - offset: Int): LazyClassLoaded[Seq[Setting[_]]] = { + def evaluateConfiguration( + eval: Eval, + file: File, + lines: Seq[String], + imports: Seq[String], + offset: Int + ): LazyClassLoaded[Seq[Setting[_]]] = { val l = evaluateSbtFile(eval, file, lines, imports, offset) loader => l(loader).settings @@ -124,11 +133,13 @@ private[sbt] object EvaluateConfigurations { * @return A function which can take an sbt classloader and return the raw types/configuration * which was compiled/parsed for the given file. */ - private[sbt] def evaluateSbtFile(eval: Eval, - file: File, - lines: Seq[String], - imports: Seq[String], - offset: Int): LazyClassLoaded[LoadedSbtFile] = { + private[sbt] def evaluateSbtFile( + eval: Eval, + file: File, + lines: Seq[String], + imports: Seq[String], + offset: Int + ): LazyClassLoaded[LoadedSbtFile] = { // TODO - Store the file on the LoadedSbtFile (or the parent dir) so we can accurately do // detection for which project project manipulations should be applied. val name = file.getPath @@ -170,12 +181,14 @@ private[sbt] object EvaluateConfigurations { case DslEntry.ProjectManipulation(f) => f } // TODO -get project manipulations. - new LoadedSbtFile(settings, - projects, - importDefs, - manipulations, - definitions, - allGeneratedFiles) + new LoadedSbtFile( + settings, + projects, + importDefs, + manipulations, + definitions, + allGeneratedFiles + ) } } @@ -208,19 +221,23 @@ private[sbt] object EvaluateConfigurations { * @return A method that given an sbt classloader, can return the actual [[sbt.internal.DslEntry]] defined by * the expression, and the sequence of .class files generated. */ - private[sbt] def evaluateDslEntry(eval: Eval, - name: String, - imports: Seq[(String, Int)], - expression: String, - range: LineRange): TrackedEvalResult[DslEntry] = { + private[sbt] def evaluateDslEntry( + eval: Eval, + name: String, + imports: Seq[(String, Int)], + expression: String, + range: LineRange + ): TrackedEvalResult[DslEntry] = { // TODO - Should we try to namespace these between.sbt files? IF they hash to the same value, they may actually be // exactly the same setting, so perhaps we don't care? val result = try { - eval.eval(expression, - imports = new EvalImports(imports, name), - srcName = name, - tpeName = Some(SettingsDefinitionName), - line = range.start) + eval.eval( + expression, + imports = new EvalImports(imports, name), + srcName = name, + tpeName = Some(SettingsDefinitionName), + line = range.start + ) } catch { case e: sbt.compiler.EvalException => throw new MessageOnlyException(e.getMessage) } @@ -249,11 +266,13 @@ private[sbt] object EvaluateConfigurations { */ // Build DSL now includes non-Setting[_] type settings. // Note: This method is used by the SET command, so we may want to evaluate that sucker a bit. - def evaluateSetting(eval: Eval, - name: String, - imports: Seq[(String, Int)], - expression: String, - range: LineRange): LazyClassLoaded[Seq[Setting[_]]] = + def evaluateSetting( + eval: Eval, + name: String, + imports: Seq[(String, Int)], + expression: String, + range: LineRange + ): LazyClassLoaded[Seq[Setting[_]]] = evaluateDslEntry(eval, name, imports, expression, range).result andThen { case DslEntry.ProjectSettings(values) => values case _ => Nil @@ -265,7 +284,8 @@ private[sbt] object EvaluateConfigurations { */ private[sbt] def splitExpressions( file: File, - lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = { + lines: Seq[String] + ): (Seq[(String, Int)], Seq[(String, LineRange)]) = { val split = SbtParser(file, lines) // TODO - Look at pulling the parsed expression trees from the SbtParser and stitch them back into a different // scala compiler rather than re-parsing. @@ -273,7 +293,8 @@ private[sbt] object EvaluateConfigurations { } private[this] def splitSettingsDefinitions( - lines: Seq[(String, LineRange)]): (Seq[(String, LineRange)], Seq[(String, LineRange)]) = + lines: Seq[(String, LineRange)] + ): (Seq[(String, LineRange)], Seq[(String, LineRange)]) = lines partition { case (line, _) => isDefinition(line) } private[this] def isDefinition(line: String): Boolean = { @@ -282,34 +303,41 @@ private[sbt] object EvaluateConfigurations { } private[this] def extractedValTypes: Seq[String] = - Seq(classOf[CompositeProject], - classOf[InputKey[_]], - classOf[TaskKey[_]], - classOf[SettingKey[_]]) - .map(_.getName) + Seq( + classOf[CompositeProject], + classOf[InputKey[_]], + classOf[TaskKey[_]], + classOf[SettingKey[_]] + ).map(_.getName) - private[this] def evaluateDefinitions(eval: Eval, - name: String, - imports: Seq[(String, Int)], - definitions: Seq[(String, LineRange)], - file: Option[File]): compiler.EvalDefinitions = { + private[this] def evaluateDefinitions( + eval: Eval, + name: String, + imports: Seq[(String, Int)], + definitions: Seq[(String, LineRange)], + file: Option[File] + ): compiler.EvalDefinitions = { val convertedRanges = definitions.map { case (s, r) => (s, r.start to r.end) } - eval.evalDefinitions(convertedRanges, - new EvalImports(imports, name), - name, - file, - extractedValTypes) + eval.evalDefinitions( + convertedRanges, + new EvalImports(imports, name), + name, + file, + extractedValTypes + ) } } object Index { def taskToKeyMap(data: Settings[Scope]): Map[Task[_], ScopedKey[Task[_]]] = { - val pairs = data.scopes flatMap (scope => - data.data(scope).entries collect { - case AttributeEntry(key, value: Task[_]) => - (value, ScopedKey(scope, key.asInstanceOf[AttributeKey[Task[_]]])) - }) + val pairs = data.scopes flatMap ( + scope => + data.data(scope).entries collect { + case AttributeEntry(key, value: Task[_]) => + (value, ScopedKey(scope, key.asInstanceOf[AttributeKey[Task[_]]])) + } + ) pairs.toMap[Task[_], ScopedKey[Task[_]]] } @@ -326,8 +354,9 @@ object Index { def stringToKeyMap(settings: Set[AttributeKey[_]]): Map[String, AttributeKey[_]] = stringToKeyMap0(settings)(_.label) - private[this] def stringToKeyMap0(settings: Set[AttributeKey[_]])( - label: AttributeKey[_] => String): Map[String, AttributeKey[_]] = { + private[this] def stringToKeyMap0( + settings: Set[AttributeKey[_]] + )(label: AttributeKey[_] => String): Map[String, AttributeKey[_]] = { val multiMap = settings.groupBy(label) val duplicates = multiMap collect { case (k, xs) if xs.size > 1 => (k, xs.map(_.manifest)) } collect { case (k, xs) if xs.size > 1 => (k, xs) @@ -336,7 +365,8 @@ object Index { multiMap.collect { case (k, v) if validID(k) => (k, v.head) } toMap else sys.error( - duplicates map { case (k, tps) => "'" + k + "' (" + tps.mkString(", ") + ")" } mkString ("Some keys were defined with the same name but different types: ", ", ", "")) + duplicates map { case (k, tps) => "'" + k + "' (" + tps.mkString(", ") + ")" } mkString ("Some keys were defined with the same name but different types: ", ", ", "") + ) } private[this] type TriggerMap = collection.mutable.HashMap[Task[_], Seq[Task[_]]] diff --git a/main/src/main/scala/sbt/internal/GlobalPlugin.scala b/main/src/main/scala/sbt/internal/GlobalPlugin.scala index 317e6fd0d..92f237b7d 100644 --- a/main/src/main/scala/sbt/internal/GlobalPlugin.scala +++ b/main/src/main/scala/sbt/internal/GlobalPlugin.scala @@ -41,8 +41,10 @@ object GlobalPlugin { injectInternalClasspath(Runtime, gp.internalClasspath), injectInternalClasspath(Compile, gp.internalClasspath) ) - private[this] def injectInternalClasspath(config: Configuration, - cp: Seq[Attributed[File]]): Setting[_] = + private[this] def injectInternalClasspath( + config: Configuration, + cp: Seq[Attributed[File]] + ): Setting[_] = internalDependencyClasspath in config ~= { prev => (prev ++ cp).distinct } @@ -50,8 +52,10 @@ object GlobalPlugin { def build(base: File, s: State, config: LoadBuildConfiguration): (BuildStructure, State) = { val newInject = config.injectSettings.copy(global = config.injectSettings.global ++ globalPluginSettings) - val globalConfig = config.copy(injectSettings = newInject, - pluginManagement = config.pluginManagement.forGlobalPlugin) + val globalConfig = config.copy( + injectSettings = newInject, + pluginManagement = config.pluginManagement.forGlobalPlugin + ) val (eval, structure) = Load(base, s, globalConfig) val session = Load.initialSession(structure, eval) (structure, Project.setProject(session, structure, s)) @@ -73,22 +77,26 @@ object GlobalPlugin { // If we reference it directly (if it's an executionRoot) then it forces an update, which is not what we want. val updateReport = Def.taskDyn { Def.task { update.value } }.value - GlobalPluginData(projectID.value, - projectDependencies.value, - depMap, - resolvers.value.toVector, - (fullClasspath in Runtime).value, - (prods ++ intcp).distinct)(updateReport) + GlobalPluginData( + projectID.value, + projectDependencies.value, + depMap, + resolvers.value.toVector, + (fullClasspath in Runtime).value, + (prods ++ intcp).distinct + )(updateReport) } val resolvedTaskInit = taskInit mapReferenced Project.mapScope(Scope replaceThis p) val task = resolvedTaskInit evaluate data val roots = resolvedTaskInit.dependencies evaluate(state, structure, task, roots) } - def evaluate[T](state: State, - structure: BuildStructure, - t: Task[T], - roots: Seq[ScopedKey[_]]): (State, T) = { + def evaluate[T]( + state: State, + structure: BuildStructure, + t: Task[T], + roots: Seq[ScopedKey[_]] + ): (State, T) = { import EvaluateTask._ withStreams(structure, state) { str => val nv = nodeView(state, str, roots) @@ -105,13 +113,17 @@ object GlobalPlugin { version := "0.0" ) } -final case class GlobalPluginData(projectID: ModuleID, - dependencies: Seq[ModuleID], - descriptors: Map[ModuleRevisionId, ModuleDescriptor], - resolvers: Vector[Resolver], - fullClasspath: Classpath, - internalClasspath: Classpath)(val updateReport: UpdateReport) -final case class GlobalPlugin(data: GlobalPluginData, - structure: BuildStructure, - inject: Seq[Setting[_]], - base: File) +final case class GlobalPluginData( + projectID: ModuleID, + dependencies: Seq[ModuleID], + descriptors: Map[ModuleRevisionId, ModuleDescriptor], + resolvers: Vector[Resolver], + fullClasspath: Classpath, + internalClasspath: Classpath +)(val updateReport: UpdateReport) +final case class GlobalPlugin( + data: GlobalPluginData, + structure: BuildStructure, + inject: Seq[Setting[_]], + base: File +) diff --git a/main/src/main/scala/sbt/internal/GroupedAutoPlugins.scala b/main/src/main/scala/sbt/internal/GroupedAutoPlugins.scala index c43b5a50b..06564204f 100644 --- a/main/src/main/scala/sbt/internal/GroupedAutoPlugins.scala +++ b/main/src/main/scala/sbt/internal/GroupedAutoPlugins.scala @@ -11,8 +11,10 @@ package internal import Def.Setting import java.net.URI -private[sbt] final class GroupedAutoPlugins(val all: Seq[AutoPlugin], - val byBuild: Map[URI, Seq[AutoPlugin]]) { +private[sbt] final class GroupedAutoPlugins( + val all: Seq[AutoPlugin], + val byBuild: Map[URI, Seq[AutoPlugin]] +) { def globalSettings: Seq[Setting[_]] = all.flatMap(_.globalSettings) def buildSettings(uri: URI): Seq[Setting[_]] = byBuild.getOrElse(uri, Nil).flatMap(_.buildSettings) diff --git a/main/src/main/scala/sbt/internal/IvyConsole.scala b/main/src/main/scala/sbt/internal/IvyConsole.scala index 9cce3a74f..a2cd16b1d 100644 --- a/main/src/main/scala/sbt/internal/IvyConsole.scala +++ b/main/src/main/scala/sbt/internal/IvyConsole.scala @@ -50,19 +50,23 @@ object IvyConsole { logLevel in Global := Level.Warn, showSuccess in Global := false ) - val append = Load.transformSettings(Load.projectScope(currentRef), - currentRef.build, - rootProject, - depSettings) + val append = Load.transformSettings( + Load.projectScope(currentRef), + currentRef.build, + rootProject, + depSettings + ) val newStructure = Load.reapply(session.original ++ append, structure) val newState = state.copy(remainingCommands = Exec(Keys.consoleQuick.key.label, None) :: Nil) Project.setProject(session, newStructure, newState) } - final case class Dependencies(managed: Seq[ModuleID], - resolvers: Seq[Resolver], - unmanaged: Seq[File]) + final case class Dependencies( + managed: Seq[ModuleID], + resolvers: Seq[Resolver], + unmanaged: Seq[File] + ) def parseDependencies(args: Seq[String], log: Logger): Dependencies = (Dependencies(Nil, Nil, Nil) /: args)(parseArgument(log)) def parseArgument(log: Logger)(acc: Dependencies, arg: String): Dependencies = diff --git a/main/src/main/scala/sbt/internal/KeyIndex.scala b/main/src/main/scala/sbt/internal/KeyIndex.scala index 78183984e..fc6ef184c 100644 --- a/main/src/main/scala/sbt/internal/KeyIndex.scala +++ b/main/src/main/scala/sbt/internal/KeyIndex.scala @@ -17,19 +17,25 @@ import sbt.librarymanagement.Configuration object KeyIndex { def empty: ExtendableKeyIndex = new KeyIndex0(emptyBuildIndex) - def apply(known: Iterable[ScopedKey[_]], - projects: Map[URI, Set[String]], - configurations: Map[String, Seq[Configuration]]): ExtendableKeyIndex = + def apply( + known: Iterable[ScopedKey[_]], + projects: Map[URI, Set[String]], + configurations: Map[String, Seq[Configuration]] + ): ExtendableKeyIndex = (base(projects, configurations) /: known) { _ add _ } - def aggregate(known: Iterable[ScopedKey[_]], - extra: BuildUtil[_], - projects: Map[URI, Set[String]], - configurations: Map[String, Seq[Configuration]]): ExtendableKeyIndex = + def aggregate( + known: Iterable[ScopedKey[_]], + extra: BuildUtil[_], + projects: Map[URI, Set[String]], + configurations: Map[String, Seq[Configuration]] + ): ExtendableKeyIndex = (base(projects, configurations) /: known) { (index, key) => index.addAggregated(key, extra) } - private[this] def base(projects: Map[URI, Set[String]], - configurations: Map[String, Seq[Configuration]]): ExtendableKeyIndex = { + private[this] def base( + projects: Map[URI, Set[String]], + configurations: Map[String, Seq[Configuration]] + ): ExtendableKeyIndex = { val data = for { (uri, ids) <- projects } yield { @@ -78,23 +84,29 @@ trait KeyIndex { // TODO, optimize def isEmpty(proj: Option[ResolvedReference], conf: Option[String]): Boolean = keys(proj, conf).isEmpty - def isEmpty(proj: Option[ResolvedReference], - conf: Option[String], - task: Option[AttributeKey[_]]): Boolean = keys(proj, conf, task).isEmpty + def isEmpty( + proj: Option[ResolvedReference], + conf: Option[String], + task: Option[AttributeKey[_]] + ): Boolean = keys(proj, conf, task).isEmpty def buildURIs: Set[URI] def projects(uri: URI): Set[String] def exists(project: Option[ResolvedReference]): Boolean def configs(proj: Option[ResolvedReference]): Set[String] def tasks(proj: Option[ResolvedReference], conf: Option[String]): Set[AttributeKey[_]] - def tasks(proj: Option[ResolvedReference], - conf: Option[String], - key: String): Set[AttributeKey[_]] + def tasks( + proj: Option[ResolvedReference], + conf: Option[String], + key: String + ): Set[AttributeKey[_]] def keys(proj: Option[ResolvedReference]): Set[String] def keys(proj: Option[ResolvedReference], conf: Option[String]): Set[String] - def keys(proj: Option[ResolvedReference], - conf: Option[String], - task: Option[AttributeKey[_]]): Set[String] + def keys( + proj: Option[ResolvedReference], + conf: Option[String], + task: Option[AttributeKey[_]] + ): Set[String] private[sbt] def configIdents(project: Option[ResolvedReference]): Set[String] private[sbt] def fromConfigIdent(proj: Option[ResolvedReference])(configIdent: String): String } @@ -116,11 +128,15 @@ private[sbt] final class AKeyIndex(val data: Relation[Option[AttributeKey[_]], S * data contains the mapping between a configuration and keys. * identData contains the mapping between a configuration and its identifier. */ -private[sbt] final class ConfigIndex(val data: Map[Option[String], AKeyIndex], - val identData: Map[String, String]) { - def add(config: Option[String], - task: Option[AttributeKey[_]], - key: AttributeKey[_]): ConfigIndex = { +private[sbt] final class ConfigIndex( + val data: Map[Option[String], AKeyIndex], + val identData: Map[String, String] +) { + def add( + config: Option[String], + task: Option[AttributeKey[_]], + key: AttributeKey[_] + ): ConfigIndex = { new ConfigIndex(data updated (config, keyIndex(config).add(task, key)), this.identData) } @@ -141,20 +157,24 @@ private[sbt] final class ConfigIndex(val data: Map[Option[String], AKeyIndex], configIdentsInverse.getOrElse(ident, Scope.unguessConfigIdent(ident)) } private[sbt] final class ProjectIndex(val data: Map[Option[String], ConfigIndex]) { - def add(id: Option[String], - config: Option[String], - task: Option[AttributeKey[_]], - key: AttributeKey[_]): ProjectIndex = + def add( + id: Option[String], + config: Option[String], + task: Option[AttributeKey[_]], + key: AttributeKey[_] + ): ProjectIndex = new ProjectIndex(data updated (id, confIndex(id).add(config, task, key))) def confIndex(id: Option[String]): ConfigIndex = getOr(data, id, emptyConfigIndex) def projects: Set[String] = keySet(data) } private[sbt] final class BuildIndex(val data: Map[Option[URI], ProjectIndex]) { - def add(build: Option[URI], - project: Option[String], - config: Option[String], - task: Option[AttributeKey[_]], - key: AttributeKey[_]): BuildIndex = + def add( + build: Option[URI], + project: Option[String], + config: Option[String], + task: Option[AttributeKey[_]], + key: AttributeKey[_] + ): BuildIndex = new BuildIndex(data updated (build, projectIndex(build).add(project, config, task, key))) def projectIndex(build: Option[URI]): ProjectIndex = getOr(data, build, emptyProjectIndex) def builds: Set[URI] = keySet(data) @@ -176,18 +196,22 @@ private[sbt] final class KeyIndex0(val data: BuildIndex) extends ExtendableKeyIn def tasks(proj: Option[ResolvedReference], conf: Option[String]): Set[AttributeKey[_]] = keyIndex(proj, conf).tasks - def tasks(proj: Option[ResolvedReference], - conf: Option[String], - key: String): Set[AttributeKey[_]] = keyIndex(proj, conf).tasks(key) + def tasks( + proj: Option[ResolvedReference], + conf: Option[String], + key: String + ): Set[AttributeKey[_]] = keyIndex(proj, conf).tasks(key) def keys(proj: Option[ResolvedReference]): Set[String] = (Set.empty[String] /: optConfigs(proj)) { (s, c) => s ++ keys(proj, c) } def keys(proj: Option[ResolvedReference], conf: Option[String]): Set[String] = keyIndex(proj, conf).allKeys - def keys(proj: Option[ResolvedReference], - conf: Option[String], - task: Option[AttributeKey[_]]): Set[String] = keyIndex(proj, conf).keys(task) + def keys( + proj: Option[ResolvedReference], + conf: Option[String], + task: Option[AttributeKey[_]] + ): Set[String] = keyIndex(proj, conf).keys(task) def keyIndex(proj: Option[ResolvedReference], conf: Option[String]): AKeyIndex = confIndex(proj).keyIndex(conf) @@ -217,10 +241,12 @@ private[sbt] final class KeyIndex0(val data: BuildIndex) extends ExtendableKeyIn val (build, project) = parts(scoped.scope.project.toOption) add1(build, project, scoped.scope.config, scoped.scope.task, scoped.key) } - private[this] def add1(uri: Option[URI], - id: Option[String], - config: ScopeAxis[ConfigKey], - task: ScopeAxis[AttributeKey[_]], - key: AttributeKey[_]): ExtendableKeyIndex = + private[this] def add1( + uri: Option[URI], + id: Option[String], + config: ScopeAxis[ConfigKey], + task: ScopeAxis[AttributeKey[_]], + key: AttributeKey[_] + ): ExtendableKeyIndex = new KeyIndex0(data.add(uri, id, config.toOption.map(_.name), task.toOption, key)) } diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 64819b906..6d620ec7c 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -309,8 +309,9 @@ private[sbt] object Load { case _ => None } ) - ss.map(s => - s mapConstant setResolved(s.key) mapReferenced mapSpecial(s.key) mapInit setDefining) + ss.map( + s => s mapConstant setResolved(s.key) mapReferenced mapSpecial(s.key) mapInit setDefining + ) } def setDefinitionKey[T](tk: Task[T], key: ScopedKey[_]): Task[T] = @@ -559,8 +560,10 @@ private[sbt] object Load { def checkProjectBase(buildBase: File, projectBase: File): Unit = { checkDirectory(projectBase) - assert(buildBase == projectBase || IO.relativize(buildBase, projectBase).isDefined, - s"Directory $projectBase is not contained in build root $buildBase") + assert( + buildBase == projectBase || IO.relativize(buildBase, projectBase).isDefined, + s"Directory $projectBase is not contained in build root $buildBase" + ) } def checkBuildBase(base: File) = checkDirectory(base) @@ -581,8 +584,10 @@ private[sbt] object Load { } } - def checkAll(referenced: Map[URI, List[ProjectReference]], - builds: Map[URI, PartBuildUnit]): Unit = { + def checkAll( + referenced: Map[URI, List[ProjectReference]], + builds: Map[URI, PartBuildUnit] + ): Unit = { val rootProject = getRootProject(builds) for ((uri, refs) <- referenced; ref <- refs) { val ProjectRef(refURI, refID) = Scope.resolveProjectRef(uri, rootProject, ref) @@ -718,12 +723,15 @@ private[sbt] object Load { // here on, so the autogenerated build aggregated can be removed from this code. ( I think) // We may actually want to move it back here and have different flags in loadTransitive... val hasRoot = loadedProjectsRaw.projects.exists(_.base == normBase) || defsScala.exists( - _.rootProject.isDefined) + _.rootProject.isDefined + ) val (loadedProjects, defaultBuildIfNone, keepClassFiles) = if (hasRoot) - (loadedProjectsRaw.projects, - BuildDef.defaultEmpty, - loadedProjectsRaw.generatedConfigClassFiles) + ( + loadedProjectsRaw.projects, + BuildDef.defaultEmpty, + loadedProjectsRaw.generatedConfigClassFiles + ) else { val existingIDs = loadedProjectsRaw.projects.map(_.id) val refs = existingIDs.map(id => ProjectRef(uri, id)) @@ -732,9 +740,11 @@ private[sbt] object Load { val defaultProjects = timed("Load.loadUnit: defaultProjects", log) { loadProjects(projectsFromBuild(b, normBase), false) } - (defaultProjects.projects ++ loadedProjectsRaw.projects, - b, - defaultProjects.generatedConfigClassFiles ++ loadedProjectsRaw.generatedConfigClassFiles) + ( + defaultProjects.projects ++ loadedProjectsRaw.projects, + b, + defaultProjects.generatedConfigClassFiles ++ loadedProjectsRaw.generatedConfigClassFiles + ) } // Now we clean stale class files. // TODO - this may cause issues with multiple sbt clients, but that should be deprecated pending sbt-server anyway @@ -907,7 +917,8 @@ private[sbt] object Load { discover(AddSettings.defaultSbtFiles, buildBase) match { case DiscoveredProjects(Some(root), discovered, files, generated) => log.debug( - s"[Loading] Found root project ${root.id} w/ remaining ${discovered.map(_.id).mkString(",")}") + s"[Loading] Found root project ${root.id} w/ remaining ${discovered.map(_.id).mkString(",")}" + ) val (finalRoot, projectLevelExtra) = timed(s"Load.loadTransitive: finalizeProject($root)", log) { finalizeProject(root, files, true) @@ -957,18 +968,22 @@ private[sbt] object Load { } val result = root +: (acc ++ otherProjects.projects) log.debug( - s"[Loading] Done in ${buildBase}, returning: ${result.map(_.id).mkString("(", ", ", ")")}") + s"[Loading] Done in ${buildBase}, returning: ${result.map(_.id).mkString("(", ", ", ")")}" + ) LoadedProjects(result, generated ++ otherGenerated ++ generatedConfigClassFiles) } case Nil => log.debug( - s"[Loading] Done in ${buildBase}, returning: ${acc.map(_.id).mkString("(", ", ", ")")}") + s"[Loading] Done in ${buildBase}, returning: ${acc.map(_.id).mkString("(", ", ", ")")}" + ) LoadedProjects(acc, generatedConfigClassFiles) } } - private[this] def translateAutoPluginException(e: AutoPluginException, - project: Project): AutoPluginException = + private[this] def translateAutoPluginException( + e: AutoPluginException, + project: Project + ): AutoPluginException = e.withPrefix(s"Error determining plugins for project '${project.id}' in ${project.base}:\n") /** @@ -1157,7 +1172,8 @@ private[sbt] object Load { injectSettings = config.injectSettings.copy( global = autoPluginSettings ++ config.injectSettings.global, project = config.pluginManagement.inject ++ config.injectSettings.project - )) + ) + ) def activateGlobalPlugin(config: LoadBuildConfiguration): LoadBuildConfiguration = config.globalPlugin match { @@ -1266,8 +1282,9 @@ private[sbt] object Load { def initialSession(structure: BuildStructure, rootEval: () => Eval, s: State): SessionSettings = { val session = s get Keys.sessionSettings val currentProject = session map (_.currentProject) getOrElse Map.empty - val currentBuild = session map (_.currentBuild) filter (uri => - structure.units.keys exists (uri ==)) getOrElse structure.root + val currentBuild = session map (_.currentBuild) filter ( + uri => structure.units.keys exists (uri ==) + ) getOrElse structure.root new SessionSettings( currentBuild, projectMap(structure, currentProject), diff --git a/main/src/main/scala/sbt/internal/LogManager.scala b/main/src/main/scala/sbt/internal/LogManager.scala index 85e293dab..982b6a45c 100644 --- a/main/src/main/scala/sbt/internal/LogManager.scala +++ b/main/src/main/scala/sbt/internal/LogManager.scala @@ -111,11 +111,13 @@ object LogManager { } // to change from global being the default to overriding, switch the order of state.get and data.get - def getOr[T](key: AttributeKey[T], - data: Settings[Scope], - scope: Scope, - state: State, - default: T): T = + def getOr[T]( + key: AttributeKey[T], + data: Settings[Scope], + scope: Scope, + state: State, + default: T + ): T = data.get(scope, key) orElse state.get(key) getOrElse default // This is the main function that is used to generate the logger for tasks. @@ -205,7 +207,8 @@ object LogManager { val consoleOpt = consoleLocally(state, console) LogExchange.bindLoggerAppenders( loggerName, - (consoleOpt.toList map { _ -> screenLevel }) ::: (relay -> backingLevel) :: Nil) + (consoleOpt.toList map { _ -> screenLevel }) ::: (relay -> backingLevel) :: Nil + ) log } diff --git a/main/src/main/scala/sbt/internal/PluginDiscovery.scala b/main/src/main/scala/sbt/internal/PluginDiscovery.scala index cc99453e3..5fe657639 100644 --- a/main/src/main/scala/sbt/internal/PluginDiscovery.scala +++ b/main/src/main/scala/sbt/internal/PluginDiscovery.scala @@ -94,10 +94,12 @@ object PluginDiscovery { * Discovers the names of top-level modules listed in resources named `resourceName` as per [[binaryModuleNames]] or * available as analyzed source and extending from any of `subclasses` as per [[sourceModuleNames]]. */ - def binarySourceModuleNames(classpath: Seq[Attributed[File]], - loader: ClassLoader, - resourceName: String, - subclasses: String*): Seq[String] = + def binarySourceModuleNames( + classpath: Seq[Attributed[File]], + loader: ClassLoader, + resourceName: String, + subclasses: String* + ): Seq[String] = ( binaryModuleNames(data(classpath), loader, resourceName) ++ (analyzed(classpath) flatMap (a => sourceModuleNames(a, subclasses: _*))) @@ -120,9 +122,11 @@ object PluginDiscovery { * `classpath` and `loader` are both required to ensure that `loader` * doesn't bring in any resources outside of the intended `classpath`, such as from parent loaders. */ - def binaryModuleNames(classpath: Seq[File], - loader: ClassLoader, - resourceName: String): Seq[String] = { + def binaryModuleNames( + classpath: Seq[File], + loader: ClassLoader, + resourceName: String + ): Seq[String] = { import collection.JavaConverters._ loader.getResources(resourceName).asScala.toSeq.filter(onClasspath(classpath)) flatMap { u => IO.readLinesURL(u).map(_.trim).filter(!_.isEmpty) @@ -136,7 +140,8 @@ object PluginDiscovery { private[sbt] def binarySourceModules[T]( data: PluginData, loader: ClassLoader, - resourceName: String)(implicit classTag: reflect.ClassTag[T]): DetectedModules[T] = { + resourceName: String + )(implicit classTag: reflect.ClassTag[T]): DetectedModules[T] = { val classpath = data.classpath val namesAndValues = if (classpath.isEmpty) Nil @@ -148,9 +153,11 @@ object PluginDiscovery { new DetectedModules(namesAndValues) } - private[this] def loadModules[T: reflect.ClassTag](data: PluginData, - names: Seq[String], - loader: ClassLoader): Seq[(String, T)] = + private[this] def loadModules[T: reflect.ClassTag]( + data: PluginData, + names: Seq[String], + loader: ClassLoader + ): Seq[(String, T)] = try ModuleUtilities.getCheckedObjects[T](names, loader) catch { case e: ExceptionInInitializerError => @@ -170,7 +177,8 @@ object PluginDiscovery { if (evictedStrings.isEmpty) "" else "\nNote that conflicts were resolved for some dependencies:\n\t" + evictedStrings.mkString( - "\n\t") + "\n\t" + ) throw new IncompatiblePluginsException(msgBase + msgExtra, t) } } diff --git a/main/src/main/scala/sbt/internal/PluginManagement.scala b/main/src/main/scala/sbt/internal/PluginManagement.scala index e35964040..386648c35 100644 --- a/main/src/main/scala/sbt/internal/PluginManagement.scala +++ b/main/src/main/scala/sbt/internal/PluginManagement.scala @@ -15,17 +15,21 @@ import sbt.librarymanagement.ModuleID import java.net.{ URI, URL, URLClassLoader } -final case class PluginManagement(overrides: Set[ModuleID], - applyOverrides: Set[ModuleID], - loader: PluginClassLoader, - initialLoader: ClassLoader, - context: Context) { +final case class PluginManagement( + overrides: Set[ModuleID], + applyOverrides: Set[ModuleID], + loader: PluginClassLoader, + initialLoader: ClassLoader, + context: Context +) { def shift: PluginManagement = - PluginManagement(Set.empty, - overrides, - new PluginClassLoader(initialLoader), - initialLoader, - context) + PluginManagement( + Set.empty, + overrides, + new PluginClassLoader(initialLoader), + initialLoader, + context + ) def addOverrides(os: Set[ModuleID]): PluginManagement = copy(overrides = overrides ++ os) @@ -49,11 +53,13 @@ object PluginManagement { val emptyContext: Context = Context(false, 0) def apply(initialLoader: ClassLoader): PluginManagement = - PluginManagement(Set.empty, - Set.empty, - new PluginClassLoader(initialLoader), - initialLoader, - emptyContext) + PluginManagement( + Set.empty, + Set.empty, + new PluginClassLoader(initialLoader), + initialLoader, + emptyContext + ) def extractOverrides(classpath: Classpath): Set[ModuleID] = classpath flatMap { _.metadata get Keys.moduleID.key map keepOverrideInfo } toSet; diff --git a/main/src/main/scala/sbt/internal/PluginsDebug.scala b/main/src/main/scala/sbt/internal/PluginsDebug.scala index f908a29fe..6462475f4 100644 --- a/main/src/main/scala/sbt/internal/PluginsDebug.scala +++ b/main/src/main/scala/sbt/internal/PluginsDebug.scala @@ -48,8 +48,10 @@ private[sbt] class PluginsDebug( activePrefix + debugDeactivated(notFoundKey, deactivated) } - private[this] def debugDeactivated(notFoundKey: String, - deactivated: Seq[EnableDeactivated]): String = { + private[this] def debugDeactivated( + notFoundKey: String, + deactivated: Seq[EnableDeactivated] + ): String = { val (impossible, possible) = Util.separate(deactivated) { case pi: PluginImpossible => Left(pi) case pr: PluginRequirements => Right(pr) @@ -154,11 +156,13 @@ private[sbt] object PluginsDebug { val perBuild: Map[URI, Set[AutoPlugin]] = structure.units.mapValues(unit => availableAutoPlugins(unit).toSet) val pluginsThisBuild = perBuild.getOrElse(currentRef.build, Set.empty).toList - lazy val context = Context(currentProject.plugins, - currentProject.autoPlugins, - Plugins.deducer(pluginsThisBuild), - pluginsThisBuild, - s.log) + lazy val context = Context( + currentProject.plugins, + currentProject.autoPlugins, + Plugins.deducer(pluginsThisBuild), + pluginsThisBuild, + s.log + ) lazy val debug = PluginsDebug(context.available) if (!pluginsThisBuild.contains(plugin)) { val availableInBuilds: List[URI] = perBuild.toList.filter(_._2(plugin)).map(_._1) @@ -222,10 +226,11 @@ private[sbt] object PluginsDebug { sealed abstract class EnableDeactivated extends PluginEnable /** Describes a [[plugin]] that cannot be activated in a [[context]] due to [[contradictions]] in requirements. */ - final case class PluginImpossible(plugin: AutoPlugin, - context: Context, - contradictions: Set[AutoPlugin]) - extends EnableDeactivated + final case class PluginImpossible( + plugin: AutoPlugin, + context: Context, + contradictions: Set[AutoPlugin] + ) extends EnableDeactivated /** * Describes the requirements for activating [[plugin]] in [[context]]. @@ -256,9 +261,11 @@ private[sbt] object PluginsDebug { * affecting the other plugin. If empty, a direct exclusion is required. * @param newlySelected If false, this plugin was selected in the original context. */ - final case class DeactivatePlugin(plugin: AutoPlugin, - removeOneOf: Set[AutoPlugin], - newlySelected: Boolean) + final case class DeactivatePlugin( + plugin: AutoPlugin, + removeOneOf: Set[AutoPlugin], + newlySelected: Boolean + ) /** Determines how to enable [[AutoPlugin]] in [[Context]]. */ def pluginEnable(context: Context, plugin: AutoPlugin): PluginEnable = @@ -344,13 +351,15 @@ private[sbt] object PluginsDebug { DeactivatePlugin(d, removeToDeactivate, newlySelected) } - PluginRequirements(plugin, - context, - blockingExcludes, - addToExistingPlugins, - extraPlugins, - willRemove, - deactivate) + PluginRequirements( + plugin, + context, + blockingExcludes, + addToExistingPlugins, + extraPlugins, + willRemove, + deactivate + ) } } @@ -376,13 +385,15 @@ private[sbt] object PluginsDebug { /** String representation of [[PluginEnable]], intended for end users. */ def explainPluginEnable(ps: PluginEnable): String = ps match { - case PluginRequirements(plugin, - _, - blockingExcludes, - enablingPlugins, - extraEnabledPlugins, - toBeRemoved, - deactivate) => + case PluginRequirements( + plugin, + _, + blockingExcludes, + enablingPlugins, + extraEnabledPlugins, + toBeRemoved, + deactivate + ) => def indent(str: String) = if (str.isEmpty) "" else s"\t$str" def note(str: String) = if (str.isEmpty) "" else s"Note: $str" val parts = @@ -490,8 +501,9 @@ private[sbt] object PluginsDebug { s"$s1 $s2 $s3" } - private[this] def pluginImpossibleN(plugin: AutoPlugin)( - contradictions: List[AutoPlugin]): String = { + private[this] def pluginImpossibleN( + plugin: AutoPlugin + )(contradictions: List[AutoPlugin]): String = { val s1 = s"There is no way to enable plugin ${plugin.label}." val s2 = s"It (or its dependencies) requires these plugins to be both present and absent:" val s3 = s"Please report the problem to the plugin's author." diff --git a/main/src/main/scala/sbt/internal/ProjectNavigation.scala b/main/src/main/scala/sbt/internal/ProjectNavigation.scala index 305967d06..b7f04ffd1 100644 --- a/main/src/main/scala/sbt/internal/ProjectNavigation.scala +++ b/main/src/main/scala/sbt/internal/ProjectNavigation.scala @@ -49,7 +49,8 @@ final class ProjectNavigation(s: State) { setProject(uri, to) else fail( - s"Invalid project name '$to' in build $uri (type 'projects' to list available projects).") + s"Invalid project name '$to' in build $uri (type 'projects' to list available projects)." + ) def changeBuild(newBuild: URI): State = if (structure.units contains newBuild) diff --git a/main/src/main/scala/sbt/internal/Resolve.scala b/main/src/main/scala/sbt/internal/Resolve.scala index 9797bbab8..7e3fe0676 100644 --- a/main/src/main/scala/sbt/internal/Resolve.scala +++ b/main/src/main/scala/sbt/internal/Resolve.scala @@ -11,10 +11,12 @@ package internal import sbt.internal.util.AttributeKey object Resolve { - def apply(index: BuildUtil[_], - current: ScopeAxis[Reference], - key: AttributeKey[_], - mask: ScopeMask): Scope => Scope = { + def apply( + index: BuildUtil[_], + current: ScopeAxis[Reference], + key: AttributeKey[_], + mask: ScopeMask + ): Scope => Scope = { val rs = resolveProject(current, mask) _ :: resolveExtra(mask) _ :: @@ -39,7 +41,8 @@ object Resolve { else scope.copy(extra = Zero) def resolveConfig[P](index: BuildUtil[P], key: AttributeKey[_], mask: ScopeMask)( - scope: Scope): Scope = + scope: Scope + ): Scope = if (mask.config) scope else { diff --git a/main/src/main/scala/sbt/internal/Script.scala b/main/src/main/scala/sbt/internal/Script.scala index addf7d4ed..2192785a6 100644 --- a/main/src/main/scala/sbt/internal/Script.scala +++ b/main/src/main/scala/sbt/internal/Script.scala @@ -25,7 +25,8 @@ object Script { lazy val command = Command.command(Name) { state => val scriptArg = state.remainingCommands.headOption map { _.commandLine } getOrElse sys.error( - "No script file specified") + "No script file specified" + ) val scriptFile = new File(scriptArg).getAbsoluteFile val hash = Hash.halve(Hash.toHex(Hash(scriptFile.getAbsolutePath))) val base = new File(CommandUtil.bootDirectory(state), hash) @@ -51,14 +52,18 @@ object Script { } val scriptAsSource = sources in Compile := script :: Nil val asScript = scalacOptions ++= Seq("-Xscript", script.getName.stripSuffix(".scala")) - val scriptSettings = Seq(asScript, - scriptAsSource, - logLevel in Global := Level.Warn, - showSuccess in Global := false) - val append = Load.transformSettings(Load.projectScope(currentRef), - currentRef.build, - rootProject, - scriptSettings ++ embeddedSettings) + val scriptSettings = Seq( + asScript, + scriptAsSource, + logLevel in Global := Level.Warn, + showSuccess in Global := false + ) + val append = Load.transformSettings( + Load.projectScope(currentRef), + currentRef.build, + rootProject, + scriptSettings ++ embeddedSettings + ) val newStructure = Load.reapply(session.original ++ append, structure) val arguments = state.remainingCommands.drop(1).map(e => s""""${e.commandLine}"""") diff --git a/main/src/main/scala/sbt/internal/SessionSettings.scala b/main/src/main/scala/sbt/internal/SessionSettings.scala index 30911a384..ddca7d66c 100755 --- a/main/src/main/scala/sbt/internal/SessionSettings.scala +++ b/main/src/main/scala/sbt/internal/SessionSettings.scala @@ -40,8 +40,10 @@ final case class SessionSettings( currentEval: () => Eval ) { - assert(currentProject contains currentBuild, - s"Current build ($currentBuild) not associated with a current project.") + assert( + currentProject contains currentBuild, + s"Current build ($currentBuild) not associated with a current project." + ) /** * Modifiy the current state. @@ -52,9 +54,11 @@ final case class SessionSettings( * @return A new SessionSettings object */ def setCurrent(build: URI, project: String, eval: () => Eval): SessionSettings = - copy(currentBuild = build, - currentProject = currentProject.updated(build, project), - currentEval = eval) + copy( + currentBuild = build, + currentProject = currentProject.updated(build, project), + currentEval = eval + ) /** * @return The current ProjectRef with which we scope settings. @@ -147,7 +151,8 @@ object SessionSettings { val oldSettings = (oldState get Keys.sessionSettings).toList.flatMap(_.append).flatMap(_._2) if (newSession.append.isEmpty && oldSettings.nonEmpty) oldState.log.warn( - "Discarding " + pluralize(oldSettings.size, " session setting") + ". Use 'session save' to persist session settings.") + "Discarding " + pluralize(oldSettings.size, " session setting") + ". Use 'session save' to persist session settings." + ) } def removeRanges[T](in: Seq[T], ranges: Seq[(Int, Int)]): Seq[T] = { @@ -197,10 +202,12 @@ object SessionSettings { reapply(newSession.copy(original = newSession.mergeSettings, append = Map.empty), s) } - def writeSettings(pref: ProjectRef, - settings: List[SessionSetting], - original: Seq[Setting[_]], - structure: BuildStructure): (Seq[SessionSetting], Seq[Setting[_]]) = { + def writeSettings( + pref: ProjectRef, + settings: List[SessionSetting], + original: Seq[Setting[_]], + structure: BuildStructure + ): (Seq[SessionSetting], Seq[Setting[_]]) = { val project = Project.getProject(pref, structure).getOrElse(sys.error("Invalid project reference " + pref)) val writeTo: File = BuildPaths @@ -224,9 +231,10 @@ object SessionSettings { val RangePosition(_, r @ LineRange(start, end)) = s.pos settings find (_._1.key == s.key) match { case Some(ss @ (ns, newLines)) if !ns.init.dependencies.contains(ns.key) => - val shifted = ns withPos RangePosition(path, - LineRange(start - offs, - start - offs + newLines.size)) + val shifted = ns withPos RangePosition( + path, + LineRange(start - offs, start - offs + newLines.size) + ) (offs + end - start - newLines.size, shifted :: olds, ss +: repl) case _ => val shifted = s withPos RangePosition(path, r shift -offs) @@ -324,9 +332,11 @@ save, save-all lazy val parser = token(Space) ~> (token("list-all" ^^^ new Print(true)) | token("list" ^^^ new Print(false)) | token( - "clear" ^^^ new Clear(false)) | + "clear" ^^^ new Clear(false) + ) | token("save-all" ^^^ new Save(true)) | token("save" ^^^ new Save(false)) | token( - "clear-all" ^^^ new Clear(true)) | + "clear-all" ^^^ new Clear(true) + ) | remove) lazy val remove = token("remove") ~> token(Space) ~> natSelect.map(ranges => new Remove(ranges)) diff --git a/main/src/main/scala/sbt/internal/SettingCompletions.scala b/main/src/main/scala/sbt/internal/SettingCompletions.scala index 3a3f8388e..1952c5598 100644 --- a/main/src/main/scala/sbt/internal/SettingCompletions.scala +++ b/main/src/main/scala/sbt/internal/SettingCompletions.scala @@ -24,9 +24,11 @@ import DefaultParsers._ * The verbose summary will typically use more vertical space and show full details, * while the quiet summary will be a couple of lines and truncate information. */ -private[sbt] class SetResult(val session: SessionSettings, - val verboseSummary: String, - val quietSummary: String) +private[sbt] class SetResult( + val session: SessionSettings, + val verboseSummary: String, + val quietSummary: String +) /** Defines methods for implementing the `set` command.*/ private[sbt] object SettingCompletions { @@ -41,9 +43,12 @@ private[sbt] object SettingCompletions { val r = relation(extracted.structure, true) val allDefs = Def .flattenLocals( - Def.compiled(extracted.structure.settings, true)(structure.delegates, - structure.scopeLocal, - implicitly[Show[ScopedKey[_]]])) + Def.compiled(extracted.structure.settings, true)( + structure.delegates, + structure.scopeLocal, + implicitly[Show[ScopedKey[_]]] + ) + ) .keys val projectScope = Load.projectScope(currentRef) def resolve(s: Setting[_]): Seq[Setting[_]] = @@ -72,9 +77,11 @@ private[sbt] object SettingCompletions { val append = Load.transformSettings(Load.projectScope(currentRef), currentRef.build, rootProject, settings) val newSession = session.appendSettings(append map (a => (a, arg.split('\n').toList))) - val r = relation(newSession.mergeSettings, true)(structure.delegates, - structure.scopeLocal, - implicitly) + val r = relation(newSession.mergeSettings, true)( + structure.delegates, + structure.scopeLocal, + implicitly + ) setResult(newSession, r, append) } @@ -149,9 +156,11 @@ private[sbt] object SettingCompletions { } /** Parser for a Scope+AttributeKey (ScopedKey). */ - def scopedKeyParser(keyMap: Map[String, AttributeKey[_]], - settings: Settings[Scope], - context: ResolvedProject): Parser[ScopedKey[_]] = { + def scopedKeyParser( + keyMap: Map[String, AttributeKey[_]], + settings: Settings[Scope], + context: ResolvedProject + ): Parser[ScopedKey[_]] = { val cutoff = KeyRanks.MainCutoff val keyCompletions = fixedCompletions { (seen, level) => completeKey(seen, keyMap, level, cutoff, 10).toSet @@ -186,9 +195,11 @@ private[sbt] object SettingCompletions { * The completions are restricted to be more useful. Currently, this parser will suggest * only known axis values for configurations and tasks and only in that order. */ - def scopeParser(key: AttributeKey[_], - settings: Settings[Scope], - context: ResolvedProject): Parser[Scope] = { + def scopeParser( + key: AttributeKey[_], + settings: Settings[Scope], + context: ResolvedProject + ): Parser[Scope] = { val data = settings.data val allScopes = data.keys.toSeq val definedScopes = data.toSeq flatMap { @@ -277,11 +288,13 @@ private[sbt] object SettingCompletions { completeDescribed(seen, true, applicable)(assignDescription) } - def completeKey(seen: String, - keys: Map[String, AttributeKey[_]], - level: Int, - prominentCutoff: Int, - detailLimit: Int): Seq[Completion] = + def completeKey( + seen: String, + keys: Map[String, AttributeKey[_]], + level: Int, + prominentCutoff: Int, + detailLimit: Int + ): Seq[Completion] = completeSelectDescribed(seen, level, keys, detailLimit)(_.description) { case (_, v) => v.rank <= prominentCutoff } @@ -290,13 +303,15 @@ private[sbt] object SettingCompletions { seen: String, level: Int, definedChoices: Set[String], - allChoices: Map[String, T])(description: T => Option[String]): Seq[Completion] = + allChoices: Map[String, T] + )(description: T => Option[String]): Seq[Completion] = completeSelectDescribed(seen, level, allChoices, 10)(description) { case (k, _) => definedChoices(k) } def completeSelectDescribed[T](seen: String, level: Int, all: Map[String, T], detailLimit: Int)( - description: T => Option[String])(prominent: (String, T) => Boolean): Seq[Completion] = { + description: T => Option[String] + )(prominent: (String, T) => Boolean): Seq[Completion] = { val applicable = all.toSeq.filter { case (k, _) => k startsWith seen } val prominentOnly = applicable filter { case (k, v) => prominent(k, v) } @@ -306,7 +321,8 @@ private[sbt] object SettingCompletions { completeDescribed(seen, showDescriptions, showKeys)(s => description(s).toList.mkString) } def completeDescribed[T](seen: String, showDescriptions: Boolean, in: Seq[(String, T)])( - description: T => String): Seq[Completion] = { + description: T => String + ): Seq[Completion] = { def appendString(id: String): String = id.stripPrefix(seen) + " " if (in.isEmpty) Nil @@ -337,7 +353,8 @@ private[sbt] object SettingCompletions { def keyType[S](key: AttributeKey[_])( onSetting: Manifest[_] => S, onTask: Manifest[_] => S, - onInput: Manifest[_] => S)(implicit tm: Manifest[Task[_]], im: Manifest[InputTask[_]]): S = { + onInput: Manifest[_] => S + )(implicit tm: Manifest[Task[_]], im: Manifest[InputTask[_]]): S = { def argTpe = key.manifest.typeArguments.head val TaskClass = tm.runtimeClass val InputTaskClass = im.runtimeClass diff --git a/main/src/main/scala/sbt/internal/SettingGraph.scala b/main/src/main/scala/sbt/internal/SettingGraph.scala index 8b74b51a4..68fab0353 100644 --- a/main/src/main/scala/sbt/internal/SettingGraph.scala +++ b/main/src/main/scala/sbt/internal/SettingGraph.scala @@ -19,9 +19,11 @@ import sbt.io.IO object SettingGraph { def apply(structure: BuildStructure, basedir: File, scoped: ScopedKey[_], generation: Int)( - implicit display: Show[ScopedKey[_]]): SettingGraph = { + implicit display: Show[ScopedKey[_]] + ): SettingGraph = { val cMap = flattenLocals( - compiled(structure.settings, false)(structure.delegates, structure.scopeLocal, display)) + compiled(structure.settings, false)(structure.delegates, structure.scopeLocal, display) + ) def loop(scoped: ScopedKey[_], generation: Int): SettingGraph = { val key = scoped.key val scope = scoped.scope @@ -34,14 +36,16 @@ object SettingGraph { // val related = cMap.keys.filter(k => k.key == key && k.scope != scope) // val reverse = reverseDependencies(cMap, scoped) - SettingGraph(display.show(scoped), - definedIn, - Project.scopedKeyData(structure, scope, key), - key.description, - basedir, - depends map { (x: ScopedKey[_]) => - loop(x, generation + 1) - }) + SettingGraph( + display.show(scoped), + definedIn, + Project.scopedKeyData(structure, scope, key), + key.description, + basedir, + depends map { (x: ScopedKey[_]) => + loop(x, generation + 1) + } + ) } loop(scoped, generation) } diff --git a/main/src/main/scala/sbt/internal/TaskSequential.scala b/main/src/main/scala/sbt/internal/TaskSequential.scala index 8fbab839e..d21fa1b8f 100644 --- a/main/src/main/scala/sbt/internal/TaskSequential.scala +++ b/main/src/main/scala/sbt/internal/TaskSequential.scala @@ -21,60 +21,81 @@ trait TaskSequential { last: Initialize[Task[B]] ): Initialize[Task[B]] = sequential(List(unitTask(task0)), last) - def sequential[A0, A1, B](task0: Initialize[Task[A0]], - task1: Initialize[Task[A1]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + def sequential[A0, A1, B]( + task0: Initialize[Task[A0]], + task1: Initialize[Task[A1]], + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential(List(unitTask(task0), unitTask(task1)), last) - def sequential[A0, A1, A2, B](task0: Initialize[Task[A0]], - task1: Initialize[Task[A1]], - task2: Initialize[Task[A2]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + def sequential[A0, A1, A2, B]( + task0: Initialize[Task[A0]], + task1: Initialize[Task[A1]], + task2: Initialize[Task[A2]], + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential(List(unitTask(task0), unitTask(task1), unitTask(task2)), last) - def sequential[A0, A1, A2, A3, B](task0: Initialize[Task[A0]], - task1: Initialize[Task[A1]], - task2: Initialize[Task[A2]], - task3: Initialize[Task[A3]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + def sequential[A0, A1, A2, A3, B]( + task0: Initialize[Task[A0]], + task1: Initialize[Task[A1]], + task2: Initialize[Task[A2]], + task3: Initialize[Task[A3]], + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential(List(unitTask(task0), unitTask(task1), unitTask(task2), unitTask(task3)), last) - def sequential[A0, A1, A2, A3, A4, B](task0: Initialize[Task[A0]], - task1: Initialize[Task[A1]], - task2: Initialize[Task[A2]], - task3: Initialize[Task[A3]], - task4: Initialize[Task[A4]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + def sequential[A0, A1, A2, A3, A4, B]( + task0: Initialize[Task[A0]], + task1: Initialize[Task[A1]], + task2: Initialize[Task[A2]], + task3: Initialize[Task[A3]], + task4: Initialize[Task[A4]], + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( List(unitTask(task0), unitTask(task1), unitTask(task2), unitTask(task3), unitTask(task4)), - last) - def sequential[A0, A1, A2, A3, A4, A5, B](task0: Initialize[Task[A0]], - task1: Initialize[Task[A1]], - task2: Initialize[Task[A2]], - task3: Initialize[Task[A3]], - task4: Initialize[Task[A4]], - task5: Initialize[Task[A5]], - last: Initialize[Task[B]]): Initialize[Task[B]] = - sequential(List(unitTask(task0), - unitTask(task1), - unitTask(task2), - unitTask(task3), - unitTask(task4), - unitTask(task5)), - last) - def sequential[A0, A1, A2, A3, A4, A5, A6, B](task0: Initialize[Task[A0]], - task1: Initialize[Task[A1]], - task2: Initialize[Task[A2]], - task3: Initialize[Task[A3]], - task4: Initialize[Task[A4]], - task5: Initialize[Task[A5]], - task6: Initialize[Task[A6]], - last: Initialize[Task[B]]): Initialize[Task[B]] = - sequential(List(unitTask(task0), - unitTask(task1), - unitTask(task2), - unitTask(task3), - unitTask(task4), - unitTask(task5), - unitTask(task6)), - last) + last + ) + def sequential[A0, A1, A2, A3, A4, A5, B]( + task0: Initialize[Task[A0]], + task1: Initialize[Task[A1]], + task2: Initialize[Task[A2]], + task3: Initialize[Task[A3]], + task4: Initialize[Task[A4]], + task5: Initialize[Task[A5]], + last: Initialize[Task[B]] + ): Initialize[Task[B]] = + sequential( + List( + unitTask(task0), + unitTask(task1), + unitTask(task2), + unitTask(task3), + unitTask(task4), + unitTask(task5) + ), + last + ) + def sequential[A0, A1, A2, A3, A4, A5, A6, B]( + task0: Initialize[Task[A0]], + task1: Initialize[Task[A1]], + task2: Initialize[Task[A2]], + task3: Initialize[Task[A3]], + task4: Initialize[Task[A4]], + task5: Initialize[Task[A5]], + task6: Initialize[Task[A6]], + last: Initialize[Task[B]] + ): Initialize[Task[B]] = + sequential( + List( + unitTask(task0), + unitTask(task1), + unitTask(task2), + unitTask(task3), + unitTask(task4), + unitTask(task5), + unitTask(task6) + ), + last + ) def sequential[A0, A1, A2, A3, A4, A5, A6, A7, B]( task0: Initialize[Task[A0]], task1: Initialize[Task[A1]], @@ -84,16 +105,21 @@ trait TaskSequential { task5: Initialize[Task[A5]], task6: Initialize[Task[A6]], task7: Initialize[Task[A7]], - last: Initialize[Task[B]]): Initialize[Task[B]] = - sequential(List(unitTask(task0), - unitTask(task1), - unitTask(task2), - unitTask(task3), - unitTask(task4), - unitTask(task5), - unitTask(task6), - unitTask(task7)), - last) + last: Initialize[Task[B]] + ): Initialize[Task[B]] = + sequential( + List( + unitTask(task0), + unitTask(task1), + unitTask(task2), + unitTask(task3), + unitTask(task4), + unitTask(task5), + unitTask(task6), + unitTask(task7) + ), + last + ) def sequential[A0, A1, A2, A3, A4, A5, A6, A7, A8, B]( task0: Initialize[Task[A0]], task1: Initialize[Task[A1]], @@ -104,17 +130,20 @@ trait TaskSequential { task6: Initialize[Task[A6]], task7: Initialize[Task[A7]], task8: Initialize[Task[A8]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( - List(unitTask(task0), - unitTask(task1), - unitTask(task2), - unitTask(task3), - unitTask(task4), - unitTask(task5), - unitTask(task6), - unitTask(task7), - unitTask(task8)), + List( + unitTask(task0), + unitTask(task1), + unitTask(task2), + unitTask(task3), + unitTask(task4), + unitTask(task5), + unitTask(task6), + unitTask(task7), + unitTask(task8) + ), last ) def sequential[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, B]( @@ -128,7 +157,8 @@ trait TaskSequential { task7: Initialize[Task[A7]], task8: Initialize[Task[A8]], task9: Initialize[Task[A9]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( List( unitTask(task0), @@ -156,7 +186,8 @@ trait TaskSequential { task8: Initialize[Task[A8]], task9: Initialize[Task[A9]], task10: Initialize[Task[A10]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( List( unitTask(task0), @@ -186,7 +217,8 @@ trait TaskSequential { task9: Initialize[Task[A9]], task10: Initialize[Task[A10]], task11: Initialize[Task[A11]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( List( unitTask(task0), @@ -218,7 +250,8 @@ trait TaskSequential { task10: Initialize[Task[A10]], task11: Initialize[Task[A11]], task12: Initialize[Task[A12]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( List( unitTask(task0), @@ -252,7 +285,8 @@ trait TaskSequential { task11: Initialize[Task[A11]], task12: Initialize[Task[A12]], task13: Initialize[Task[A13]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( List( unitTask(task0), @@ -288,7 +322,8 @@ trait TaskSequential { task12: Initialize[Task[A12]], task13: Initialize[Task[A13]], task14: Initialize[Task[A14]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( List( unitTask(task0), @@ -326,7 +361,8 @@ trait TaskSequential { task13: Initialize[Task[A13]], task14: Initialize[Task[A14]], task15: Initialize[Task[A15]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( List( unitTask(task0), @@ -366,7 +402,8 @@ trait TaskSequential { task14: Initialize[Task[A14]], task15: Initialize[Task[A15]], task16: Initialize[Task[A16]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( List( unitTask(task0), @@ -433,45 +470,49 @@ trait TaskSequential { ), last ) - def sequential[A0, - A1, - A2, - A3, - A4, - A5, - A6, - A7, - A8, - A9, - A10, - A11, - A12, - A13, - A14, - A15, - A16, - A17, - A18, - B](task0: Initialize[Task[A0]], - task1: Initialize[Task[A1]], - task2: Initialize[Task[A2]], - task3: Initialize[Task[A3]], - task4: Initialize[Task[A4]], - task5: Initialize[Task[A5]], - task6: Initialize[Task[A6]], - task7: Initialize[Task[A7]], - task8: Initialize[Task[A8]], - task9: Initialize[Task[A9]], - task10: Initialize[Task[A10]], - task11: Initialize[Task[A11]], - task12: Initialize[Task[A12]], - task13: Initialize[Task[A13]], - task14: Initialize[Task[A14]], - task15: Initialize[Task[A15]], - task16: Initialize[Task[A16]], - task17: Initialize[Task[A17]], - task18: Initialize[Task[A18]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + def sequential[ + A0, + A1, + A2, + A3, + A4, + A5, + A6, + A7, + A8, + A9, + A10, + A11, + A12, + A13, + A14, + A15, + A16, + A17, + A18, + B + ]( + task0: Initialize[Task[A0]], + task1: Initialize[Task[A1]], + task2: Initialize[Task[A2]], + task3: Initialize[Task[A3]], + task4: Initialize[Task[A4]], + task5: Initialize[Task[A5]], + task6: Initialize[Task[A6]], + task7: Initialize[Task[A7]], + task8: Initialize[Task[A8]], + task9: Initialize[Task[A9]], + task10: Initialize[Task[A10]], + task11: Initialize[Task[A11]], + task12: Initialize[Task[A12]], + task13: Initialize[Task[A13]], + task14: Initialize[Task[A14]], + task15: Initialize[Task[A15]], + task16: Initialize[Task[A16]], + task17: Initialize[Task[A17]], + task18: Initialize[Task[A18]], + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( List( unitTask(task0), @@ -496,47 +537,51 @@ trait TaskSequential { ), last ) - def sequential[A0, - A1, - A2, - A3, - A4, - A5, - A6, - A7, - A8, - A9, - A10, - A11, - A12, - A13, - A14, - A15, - A16, - A17, - A18, - A19, - B](task0: Initialize[Task[A0]], - task1: Initialize[Task[A1]], - task2: Initialize[Task[A2]], - task3: Initialize[Task[A3]], - task4: Initialize[Task[A4]], - task5: Initialize[Task[A5]], - task6: Initialize[Task[A6]], - task7: Initialize[Task[A7]], - task8: Initialize[Task[A8]], - task9: Initialize[Task[A9]], - task10: Initialize[Task[A10]], - task11: Initialize[Task[A11]], - task12: Initialize[Task[A12]], - task13: Initialize[Task[A13]], - task14: Initialize[Task[A14]], - task15: Initialize[Task[A15]], - task16: Initialize[Task[A16]], - task17: Initialize[Task[A17]], - task18: Initialize[Task[A18]], - task19: Initialize[Task[A19]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + def sequential[ + A0, + A1, + A2, + A3, + A4, + A5, + A6, + A7, + A8, + A9, + A10, + A11, + A12, + A13, + A14, + A15, + A16, + A17, + A18, + A19, + B + ]( + task0: Initialize[Task[A0]], + task1: Initialize[Task[A1]], + task2: Initialize[Task[A2]], + task3: Initialize[Task[A3]], + task4: Initialize[Task[A4]], + task5: Initialize[Task[A5]], + task6: Initialize[Task[A6]], + task7: Initialize[Task[A7]], + task8: Initialize[Task[A8]], + task9: Initialize[Task[A9]], + task10: Initialize[Task[A10]], + task11: Initialize[Task[A11]], + task12: Initialize[Task[A12]], + task13: Initialize[Task[A13]], + task14: Initialize[Task[A14]], + task15: Initialize[Task[A15]], + task16: Initialize[Task[A16]], + task17: Initialize[Task[A17]], + task18: Initialize[Task[A18]], + task19: Initialize[Task[A19]], + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( List( unitTask(task0), @@ -562,49 +607,53 @@ trait TaskSequential { ), last ) - def sequential[A0, - A1, - A2, - A3, - A4, - A5, - A6, - A7, - A8, - A9, - A10, - A11, - A12, - A13, - A14, - A15, - A16, - A17, - A18, - A19, - A20, - B](task0: Initialize[Task[A0]], - task1: Initialize[Task[A1]], - task2: Initialize[Task[A2]], - task3: Initialize[Task[A3]], - task4: Initialize[Task[A4]], - task5: Initialize[Task[A5]], - task6: Initialize[Task[A6]], - task7: Initialize[Task[A7]], - task8: Initialize[Task[A8]], - task9: Initialize[Task[A9]], - task10: Initialize[Task[A10]], - task11: Initialize[Task[A11]], - task12: Initialize[Task[A12]], - task13: Initialize[Task[A13]], - task14: Initialize[Task[A14]], - task15: Initialize[Task[A15]], - task16: Initialize[Task[A16]], - task17: Initialize[Task[A17]], - task18: Initialize[Task[A18]], - task19: Initialize[Task[A19]], - task20: Initialize[Task[A20]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + def sequential[ + A0, + A1, + A2, + A3, + A4, + A5, + A6, + A7, + A8, + A9, + A10, + A11, + A12, + A13, + A14, + A15, + A16, + A17, + A18, + A19, + A20, + B + ]( + task0: Initialize[Task[A0]], + task1: Initialize[Task[A1]], + task2: Initialize[Task[A2]], + task3: Initialize[Task[A3]], + task4: Initialize[Task[A4]], + task5: Initialize[Task[A5]], + task6: Initialize[Task[A6]], + task7: Initialize[Task[A7]], + task8: Initialize[Task[A8]], + task9: Initialize[Task[A9]], + task10: Initialize[Task[A10]], + task11: Initialize[Task[A11]], + task12: Initialize[Task[A12]], + task13: Initialize[Task[A13]], + task14: Initialize[Task[A14]], + task15: Initialize[Task[A15]], + task16: Initialize[Task[A16]], + task17: Initialize[Task[A17]], + task18: Initialize[Task[A18]], + task19: Initialize[Task[A19]], + task20: Initialize[Task[A20]], + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( List( unitTask(task0), @@ -631,51 +680,55 @@ trait TaskSequential { ), last ) - def sequential[A0, - A1, - A2, - A3, - A4, - A5, - A6, - A7, - A8, - A9, - A10, - A11, - A12, - A13, - A14, - A15, - A16, - A17, - A18, - A19, - A20, - A21, - B](task0: Initialize[Task[A0]], - task1: Initialize[Task[A1]], - task2: Initialize[Task[A2]], - task3: Initialize[Task[A3]], - task4: Initialize[Task[A4]], - task5: Initialize[Task[A5]], - task6: Initialize[Task[A6]], - task7: Initialize[Task[A7]], - task8: Initialize[Task[A8]], - task9: Initialize[Task[A9]], - task10: Initialize[Task[A10]], - task11: Initialize[Task[A11]], - task12: Initialize[Task[A12]], - task13: Initialize[Task[A13]], - task14: Initialize[Task[A14]], - task15: Initialize[Task[A15]], - task16: Initialize[Task[A16]], - task17: Initialize[Task[A17]], - task18: Initialize[Task[A18]], - task19: Initialize[Task[A19]], - task20: Initialize[Task[A20]], - task21: Initialize[Task[A21]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + def sequential[ + A0, + A1, + A2, + A3, + A4, + A5, + A6, + A7, + A8, + A9, + A10, + A11, + A12, + A13, + A14, + A15, + A16, + A17, + A18, + A19, + A20, + A21, + B + ]( + task0: Initialize[Task[A0]], + task1: Initialize[Task[A1]], + task2: Initialize[Task[A2]], + task3: Initialize[Task[A3]], + task4: Initialize[Task[A4]], + task5: Initialize[Task[A5]], + task6: Initialize[Task[A6]], + task7: Initialize[Task[A7]], + task8: Initialize[Task[A8]], + task9: Initialize[Task[A9]], + task10: Initialize[Task[A10]], + task11: Initialize[Task[A11]], + task12: Initialize[Task[A12]], + task13: Initialize[Task[A13]], + task14: Initialize[Task[A14]], + task15: Initialize[Task[A15]], + task16: Initialize[Task[A16]], + task17: Initialize[Task[A17]], + task18: Initialize[Task[A18]], + task19: Initialize[Task[A19]], + task20: Initialize[Task[A20]], + task21: Initialize[Task[A21]], + last: Initialize[Task[B]] + ): Initialize[Task[B]] = sequential( List( unitTask(task0), @@ -704,8 +757,10 @@ trait TaskSequential { last ) - def sequential[B](tasks: Seq[Initialize[Task[Unit]]], - last: Initialize[Task[B]]): Initialize[Task[B]] = + def sequential[B]( + tasks: Seq[Initialize[Task[Unit]]], + last: Initialize[Task[B]] + ): Initialize[Task[B]] = tasks.toList match { case Nil => Def.task { last.value } case x :: xs => diff --git a/main/src/main/scala/sbt/internal/TaskTimings.scala b/main/src/main/scala/sbt/internal/TaskTimings.scala index 3586eb4b1..9e095fdc3 100644 --- a/main/src/main/scala/sbt/internal/TaskTimings.scala +++ b/main/src/main/scala/sbt/internal/TaskTimings.scala @@ -52,10 +52,12 @@ private[sbt] final class TaskTimings(shutdown: Boolean) extends ExecuteProgress[ if (!shutdown) start = System.nanoTime } - def registered(state: Unit, - task: Task[_], - allDeps: Iterable[Task[_]], - pendingDeps: Iterable[Task[_]]) = { + def registered( + state: Unit, + task: Task[_], + allDeps: Iterable[Task[_]], + pendingDeps: Iterable[Task[_]] + ) = { pendingDeps foreach { t => if (transformNode(t).isEmpty) anonOwners.put(t, task) } diff --git a/main/src/main/scala/sbt/internal/parser/SbtParser.scala b/main/src/main/scala/sbt/internal/parser/SbtParser.scala index c8f0e9797..fa427ec92 100644 --- a/main/src/main/scala/sbt/internal/parser/SbtParser.scala +++ b/main/src/main/scala/sbt/internal/parser/SbtParser.scala @@ -85,7 +85,8 @@ private[sbt] object SbtParser { val reporter = reporters.get(fileName) if (reporter == null) { scalacGlobalInitReporter.getOrElse( - sys.error(s"Sbt forgot to initialize `scalacGlobalInitReporter`.")) + sys.error(s"Sbt forgot to initialize `scalacGlobalInitReporter`.") + ) } else reporter } @@ -139,9 +140,11 @@ private[sbt] object SbtParser { * The reporter id must be unique per parsing session. * @return */ - private[sbt] def parse(code: String, - filePath: String, - reporterId0: Option[String]): (Seq[Tree], String) = { + private[sbt] def parse( + code: String, + filePath: String, + reporterId0: Option[String] + ): (Seq[Tree], String) = { import defaultGlobalForParser._ val reporterId = reporterId0.getOrElse(s"$filePath-${Random.nextInt}") val reporter = globalReporter.getOrCreateReporter(reporterId) @@ -204,7 +207,8 @@ private[sbt] case class SbtParser(file: File, lines: Seq[String]) extends Parsed private def splitExpressions( file: File, - lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)], Seq[(String, Tree)]) = { + lines: Seq[String] + ): (Seq[(String, Int)], Seq[(String, LineRange)], Seq[(String, Tree)]) = { import sbt.internal.parser.MissingBracketHandler.findMissingText val indexedLines = lines.toIndexedSeq @@ -224,7 +228,8 @@ private[sbt] case class SbtParser(file: File, lines: Seq[String]) extends Parsed // Issue errors val positionLine = badTree.pos.line throw new MessageOnlyException( - s"""[$fileName]:$positionLine: Pattern matching in val statements is not supported""".stripMargin) + s"""[$fileName]:$positionLine: Pattern matching in val statements is not supported""".stripMargin + ) } val (imports: Seq[Tree], statements: Seq[Tree]) = parsedTrees partition { @@ -262,9 +267,9 @@ private[sbt] case class SbtParser(file: File, lines: Seq[String]) extends Parsed } val stmtTreeLineRange = statements flatMap convertStatement val importsLineRange = importsToLineRanges(content, imports) - (importsLineRange, - stmtTreeLineRange.map { case (stmt, _, lr) => (stmt, lr) }, - stmtTreeLineRange.map { case (stmt, tree, _) => (stmt, tree) }) + (importsLineRange, stmtTreeLineRange.map { case (stmt, _, lr) => (stmt, lr) }, stmtTreeLineRange.map { + case (stmt, tree, _) => (stmt, tree) + }) } /** @@ -300,8 +305,10 @@ private[sbt] case class SbtParser(file: File, lines: Seq[String]) extends Parsed * @param importsInOneLine - imports in line * @return - text */ - private def extractLine(modifiedContent: String, - importsInOneLine: Seq[((Int, Int), Int)]): String = { + private def extractLine( + modifiedContent: String, + importsInOneLine: Seq[((Int, Int), Int)] + ): String = { val (begin, end) = importsInOneLine.foldLeft((Int.MaxValue, Int.MinValue)) { case ((min, max), ((start, end), _)) => (min.min(start), max.max(end)) @@ -333,7 +340,8 @@ private[sbt] object MissingBracketHandler { positionLine: Int, fileName: String, originalException: Throwable, - reporterId: Option[String] = Some(Random.nextInt.toString)): String = { + reporterId: Option[String] = Some(Random.nextInt.toString) + ): String = { findClosingBracketIndex(content, positionEnd) match { case Some(index) => val text = content.substring(positionEnd, index + 1) @@ -342,16 +350,19 @@ private[sbt] object MissingBracketHandler { case Success(_) => text case Failure(_) => - findMissingText(content, - index + 1, - positionLine, - fileName, - originalException, - reporterId) + findMissingText( + content, + index + 1, + positionLine, + fileName, + originalException, + reporterId + ) } case _ => throw new MessageOnlyException( - s"""[$fileName]:$positionLine: ${originalException.getMessage}""".stripMargin) + s"""[$fileName]:$positionLine: ${originalException.getMessage}""".stripMargin + ) } } diff --git a/main/src/main/scala/sbt/internal/parser/SbtRefactorings.scala b/main/src/main/scala/sbt/internal/parser/SbtRefactorings.scala index 1e3893672..a82eb971e 100644 --- a/main/src/main/scala/sbt/internal/parser/SbtRefactorings.scala +++ b/main/src/main/scala/sbt/internal/parser/SbtRefactorings.scala @@ -26,8 +26,10 @@ private[sbt] object SbtRefactorings { * the first will be replaced and the other will be removed. * @return a SbtConfigFile with new lines which represent the contents of the refactored .sbt file. */ - def applySessionSettings(configFile: SbtConfigFile, - commands: Seq[SessionSetting]): SbtConfigFile = { + def applySessionSettings( + configFile: SbtConfigFile, + commands: Seq[SessionSetting] + ): SbtConfigFile = { val (file, lines) = configFile val split = SbtParser(FAKE_FILE, lines) val recordedCommands = recordCommands(commands, split) @@ -37,8 +39,10 @@ private[sbt] object SbtRefactorings { (file, newContent.lines.toList) } - private def replaceFromBottomToTop(modifiedContent: String, - sortedRecordedCommands: Seq[(Int, String, String)]) = { + private def replaceFromBottomToTop( + modifiedContent: String, + sortedRecordedCommands: Seq[(Int, String, String)] + ) = { sortedRecordedCommands.foldLeft(modifiedContent) { case (acc, (from, old, replacement)) => val before = acc.substring(0, from) diff --git a/main/src/main/scala/sbt/internal/server/Definition.scala b/main/src/main/scala/sbt/internal/server/Definition.scala index bc6a0a220..44e6e9efa 100644 --- a/main/src/main/scala/sbt/internal/server/Definition.scala +++ b/main/src/main/scala/sbt/internal/server/Definition.scala @@ -210,7 +210,8 @@ private[sbt] object Definition { private[sbt] def updateCache[F[_]](cache: Cache[Any])(cacheFile: String, useBinary: Boolean)( implicit mode: Mode[F], - flags: Flags): F[Any] = { + flags: Flags + ): F[Any] = { mode.M.flatMap(AnalysesAccess.getFrom(cache)) { case None => AnalysesAccess.putIn(cache, Set(cacheFile -> useBinary -> None), Option(Duration.Inf)) diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index df43cf534..9245e2ab8 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -27,10 +27,11 @@ private[sbt] object LanguageServerProtocol { lazy val internalJsonProtocol = new InitializeOptionFormats with sjsonnew.BasicJsonProtocol {} lazy val serverCapabilities: ServerCapabilities = { - ServerCapabilities(textDocumentSync = - TextDocumentSyncOptions(true, 0, false, false, SaveOptions(false)), - hoverProvider = false, - definitionProvider = true) + ServerCapabilities( + textDocumentSync = TextDocumentSyncOptions(true, 0, false, false, SaveOptions(false)), + hoverProvider = false, + definitionProvider = true + ) } lazy val handler: ServerHandler = ServerHandler({ @@ -42,16 +43,22 @@ private[sbt] object LanguageServerProtocol { import internalJsonProtocol._ def json(r: JsonRpcRequestMessage) = r.params.getOrElse( - throw LangServerError(ErrorCodes.InvalidParams, - s"param is expected on '${r.method}' method.")) + throw LangServerError( + ErrorCodes.InvalidParams, + s"param is expected on '${r.method}' method." + ) + ) { case r: JsonRpcRequestMessage if r.method == "initialize" => if (authOptions(ServerAuthentication.Token)) { val param = Converter.fromJson[InitializeParams](json(r)).get val optionJson = param.initializationOptions.getOrElse( - throw LangServerError(ErrorCodes.InvalidParams, - "initializationOptions is expected on 'initialize' param.")) + throw LangServerError( + ErrorCodes.InvalidParams, + "initializationOptions is expected on 'initialize' param." + ) + ) val opt = Converter.fromJson[InitializeOption](optionJson).get val token = opt.token.getOrElse(sys.error("'token' is missing.")) if (authenticate(token)) () diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index b07e38775..80149dfc5 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -21,14 +21,15 @@ import sbt.internal.util.codec.JValueFormats import sbt.internal.protocol.{ JsonRpcRequestMessage, JsonRpcNotificationMessage } import sbt.util.Logger -final class NetworkChannel(val name: String, - connection: Socket, - structure: BuildStructure, - auth: Set[ServerAuthentication], - instance: ServerInstance, - handlers: Seq[ServerHandler], - val log: Logger) - extends CommandChannel +final class NetworkChannel( + val name: String, + connection: Socket, + structure: BuildStructure, + auth: Set[ServerAuthentication], + instance: ServerInstance, + handlers: Seq[ServerHandler], + val log: Logger +) extends CommandChannel with LanguageServerProtocol { import NetworkChannel._ @@ -206,7 +207,8 @@ final class NetworkChannel(val name: String, .fold( errorDesc => log.error( - s"Got invalid chunk from client (${new String(chunk.toArray, "UTF-8")}): " + errorDesc), + s"Got invalid chunk from client (${new String(chunk.toArray, "UTF-8")}): " + errorDesc + ), onCommand ) case _ => @@ -342,7 +344,8 @@ final class NetworkChannel(val name: String, private def onExecCommand(cmd: ExecCommand) = { if (initialized) { append( - Exec(cmd.commandLine, cmd.execId orElse Some(Exec.newExecId), Some(CommandSource(name)))) + Exec(cmd.commandLine, cmd.execId orElse Some(Exec.newExecId), Some(CommandSource(name))) + ) () } else { log.warn(s"ignoring command $cmd before initialization") diff --git a/main/src/main/scala/sbt/internal/server/SettingQuery.scala b/main/src/main/scala/sbt/internal/server/SettingQuery.scala index fa106d23e..6ab872f5b 100644 --- a/main/src/main/scala/sbt/internal/server/SettingQuery.scala +++ b/main/src/main/scala/sbt/internal/server/SettingQuery.scala @@ -32,8 +32,10 @@ object SettingQuery { new ParsedExplicitValue(v) } - def projectRef(index: KeyIndex, - currentBuild: URI): Parser[ParsedExplicitAxis[ResolvedReference]] = { + def projectRef( + index: KeyIndex, + currentBuild: URI + ): Parser[ParsedExplicitAxis[ResolvedReference]] = { val global = token(Act.ZeroString ~ '/') ^^^ ParsedExplicitGlobal val trailing = '/' !!! "Expected '/' (if selecting a project)" global | explicitValue(Act.resolvedReference(index, currentBuild, trailing)) @@ -107,15 +109,21 @@ object SettingQuery { def toJson[A: JsonWriter](x: A): JValue = Converter toJsonUnsafe x - def getSettingJsonValue[A](structure: BuildStructure, - key: Def.ScopedKey[A]): Either[String, JValue] = - getSettingValue(structure, key) flatMap (value => - getJsonWriter(key.key) map { implicit jw: JsonWriter[A] => - toJson(value) - }) + def getSettingJsonValue[A]( + structure: BuildStructure, + key: Def.ScopedKey[A] + ): Either[String, JValue] = + getSettingValue(structure, key) flatMap ( + value => + getJsonWriter(key.key) map { implicit jw: JsonWriter[A] => + toJson(value) + } + ) - def handleSettingQueryEither(req: SettingQuery, - structure: BuildStructure): Either[String, SettingQuerySuccess] = { + def handleSettingQueryEither( + req: SettingQuery, + structure: BuildStructure + ): Either[String, SettingQuerySuccess] = { val key = Parser.parse(req.setting, scopedKeyParser(structure)) for { diff --git a/main/src/test/scala/DefaultsTest.scala b/main/src/test/scala/DefaultsTest.scala index b029123fc..b9399ece4 100644 --- a/main/src/test/scala/DefaultsTest.scala +++ b/main/src/test/scala/DefaultsTest.scala @@ -30,8 +30,10 @@ object DefaultsTest extends Specification { } "work correctly with excludes" in { - assertFiltered(List("Test*", "-Test2"), - Map("Test1" -> true, "Test2" -> false, "Foo" -> false)) + assertFiltered( + List("Test*", "-Test2"), + Map("Test1" -> true, "Test2" -> false, "Foo" -> false) + ) } "work correctly without includes" in { @@ -43,18 +45,24 @@ object DefaultsTest extends Specification { } "cope with multiple filters" in { - assertFiltered(List("T*1", "T*2", "-F*"), - Map("Test1" -> true, "Test2" -> true, "Foo" -> false)) + assertFiltered( + List("T*1", "T*2", "-F*"), + Map("Test1" -> true, "Test2" -> true, "Foo" -> false) + ) } "cope with multiple exclusion filters, no includes" in { - assertFiltered(List("-A*", "-F*"), - Map("Test1" -> true, "Test2" -> true, "AAA" -> false, "Foo" -> false)) + assertFiltered( + List("-A*", "-F*"), + Map("Test1" -> true, "Test2" -> true, "AAA" -> false, "Foo" -> false) + ) } "cope with multiple exclusion filters with includes" in { - assertFiltered(List("T*", "-T*1", "-T*2"), - Map("Test1" -> false, "Test2" -> false, "Test3" -> true)) + assertFiltered( + List("T*", "-T*1", "-T*2"), + Map("Test1" -> false, "Test2" -> false, "Test3" -> true) + ) } } diff --git a/main/src/test/scala/Delegates.scala b/main/src/test/scala/Delegates.scala index af77a8e23..4fa6319e9 100644 --- a/main/src/test/scala/Delegates.scala +++ b/main/src/test/scala/Delegates.scala @@ -47,7 +47,8 @@ object Delegates extends Properties("delegates") { } } property("Initial scope present with all combinations of Global axes") = allAxes( - (s, ds, _) => globalCombinations(s, ds)) + (s, ds, _) => globalCombinations(s, ds) + ) property("initial scope first") = forAll { (keys: Keys) => allDelegates(keys) { (scope, ds) => diff --git a/main/src/test/scala/ParseKey.scala b/main/src/test/scala/ParseKey.scala index 23d9d0778..ecc6374dc 100644 --- a/main/src/test/scala/ParseKey.scala +++ b/main/src/test/scala/ParseKey.scala @@ -58,15 +58,15 @@ object ParseKey extends Properties("Key parser test") { } property( - "An unspecified configuration axis resolves to the first configuration directly defining the key or else Zero") = - forAll { (skm: StructureKeyMask) => - import skm.{ structure, key } - val mask = ScopeMask(config = false) - val resolvedConfig = Resolve.resolveConfig(structure.extra, key.key, mask)(key.scope).config - parseCheck(structure, key, mask)( - sk => (sk.scope.config == resolvedConfig) || (sk.scope == Scope.GlobalScope) - ) :| s"Expected configuration: ${resolvedConfig map (_.name)}" - } + "An unspecified configuration axis resolves to the first configuration directly defining the key or else Zero" + ) = forAll { (skm: StructureKeyMask) => + import skm.{ structure, key } + val mask = ScopeMask(config = false) + val resolvedConfig = Resolve.resolveConfig(structure.extra, key.key, mask)(key.scope).config + parseCheck(structure, key, mask)( + sk => (sk.scope.config == resolvedConfig) || (sk.scope == Scope.GlobalScope) + ) :| s"Expected configuration: ${resolvedConfig map (_.name)}" + } implicit val arbStructure: Arbitrary[Structure] = Arbitrary { for { diff --git a/main/src/test/scala/PluginCommandTest.scala b/main/src/test/scala/PluginCommandTest.scala index 8262326ae..d40863578 100644 --- a/main/src/test/scala/PluginCommandTest.scala +++ b/main/src/test/scala/PluginCommandTest.scala @@ -38,23 +38,29 @@ object PluginCommandTest extends Specification { "The `plugin` command" should { "should work for plugins within nested in one package" in { - val output = processCommand("plugin sbt.PluginCommandTestPlugin0", - PluginCommandTestPlugin0, - PluginCommandTestPlugin1) + val output = processCommand( + "plugin sbt.PluginCommandTestPlugin0", + PluginCommandTestPlugin0, + PluginCommandTestPlugin1 + ) output must contain("sbt.PluginCommandTestPlugin0 is activated.") } "should work for plugins nested more than one package" in { - val output = processCommand("plugin sbt.subpackage.PluginCommandTestPlugin1", - PluginCommandTestPlugin0, - PluginCommandTestPlugin1) + val output = processCommand( + "plugin sbt.subpackage.PluginCommandTestPlugin1", + PluginCommandTestPlugin0, + PluginCommandTestPlugin1 + ) output must contain("sbt.subpackage.PluginCommandTestPlugin1 is activated.") } "suggest a plugin when given an incorrect plugin with a similar name" in { - val output = processCommand("plugin PluginCommandTestPlugin0", - PluginCommandTestPlugin0, - PluginCommandTestPlugin1) + val output = processCommand( + "plugin PluginCommandTestPlugin0", + PluginCommandTestPlugin0, + PluginCommandTestPlugin1 + ) output must contain( "Not a valid plugin: PluginCommandTestPlugin0 (similar: sbt.PluginCommandTestPlugin0, sbt.subpackage.PluginCommandTestPlugin1)" ) @@ -123,14 +129,16 @@ object FakeState { val loadedBuildUnit = Load.resolveProjects(base.toURI, partBuildUnit, _ => testProject.id) val units = Map(base.toURI -> loadedBuildUnit) - val buildStructure = new BuildStructure(units, - base.toURI, - settings, - data, - structureIndex, - streams, - delegates, - scopeLocal) + val buildStructure = new BuildStructure( + units, + base.toURI, + settings, + data, + structureIndex, + streams, + delegates, + scopeLocal + ) val attributes = AttributeMap.empty ++ AttributeMap( AttributeEntry(Keys.sessionSettings, sessionSettings), @@ -145,9 +153,11 @@ object FakeState { List(), State.newHistory, attributes, - GlobalLogging.initial(MainAppender.globalDefault(ConsoleOut.systemOut), - File.createTempFile("sbt", ".log"), - ConsoleOut.systemOut), + GlobalLogging.initial( + MainAppender.globalDefault(ConsoleOut.systemOut), + File.createTempFile("sbt", ".log"), + ConsoleOut.systemOut + ), None, State.Continue ) diff --git a/main/src/test/scala/PluginsTest.scala b/main/src/test/scala/PluginsTest.scala index 620e6941c..52f381cd1 100644 --- a/main/src/test/scala/PluginsTest.scala +++ b/main/src/test/scala/PluginsTest.scala @@ -42,7 +42,8 @@ object PluginsTest extends Specification { message = s"""Contradiction in enabled plugins: - requested: sbt.AI\\$$S - enabled: sbt.AI\\$$S, sbt.AI\\$$Q, sbt.AI\\$$R, sbt.AI\\$$B, sbt.AI\\$$A - - conflict: sbt.AI\\$$R is enabled by sbt.AI\\$$Q; excluded by sbt.AI\\$$S""") + - conflict: sbt.AI\\$$R is enabled by sbt.AI\\$$Q; excluded by sbt.AI\\$$S""" + ) } "generates a detailed report on conflicting requirements" in { deducePlugin(T && U, log) must throwAn[AutoPluginException]( @@ -50,7 +51,8 @@ object PluginsTest extends Specification { - requested: sbt.AI\\$$T && sbt.AI\\$$U - enabled: sbt.AI\\$$U, sbt.AI\\$$T, sbt.AI\\$$A, sbt.AI\\$$Q, sbt.AI\\$$R, sbt.AI\\$$B - conflict: sbt.AI\\$$Q is enabled by sbt.AI\\$$A && sbt.AI\\$$B; required by sbt.AI\\$$T, sbt.AI\\$$R; excluded by sbt.AI\\$$U - - conflict: sbt.AI\\$$R is enabled by sbt.AI\\$$Q; excluded by sbt.AI\\$$T""") + - conflict: sbt.AI\\$$R is enabled by sbt.AI\\$$Q; excluded by sbt.AI\\$$T""" + ) } } } diff --git a/main/src/test/scala/sbt/internal/TestBuild.scala b/main/src/test/scala/sbt/internal/TestBuild.scala index e7bb2ead0..9d9750d04 100644 --- a/main/src/test/scala/sbt/internal/TestBuild.scala +++ b/main/src/test/scala/sbt/internal/TestBuild.scala @@ -48,11 +48,13 @@ abstract class TestBuild { lazy val delegated = scopes map env.delegates } - sealed case class Structure(env: Env, - current: ProjectRef, - data: Settings[Scope], - keyIndex: KeyIndex, - keyMap: Map[String, AttributeKey[_]]) { + sealed case class Structure( + env: Env, + current: ProjectRef, + data: Settings[Scope], + keyIndex: KeyIndex, + keyMap: Map[String, AttributeKey[_]] + ) { override def toString = env.toString + "\n" + "current: " + current + "\nSettings:\n\t" + showData + keyMap.keys .mkString("All keys:\n\t", ", ", "") @@ -64,13 +66,15 @@ abstract class TestBuild { } val extra: BuildUtil[Proj] = { val getp = (build: URI, project: String) => env.buildMap(build).projectMap(project) - new BuildUtil(keyIndex, - data, - env.root.uri, - env.rootProject, - getp, - _.configurations.map(c => ConfigKey(c.name)), - Relation.empty) + new BuildUtil( + keyIndex, + data, + env.root.uri, + env.rootProject, + getp, + _.configurations.map(c => ConfigKey(c.name)), + Relation.empty + ) } lazy val allAttributeKeys: Set[AttributeKey[_]] = { @@ -88,8 +92,10 @@ abstract class TestBuild { val taskAxesMappings = for ((scope, keys) <- data.data.toIterable; key <- keys.keys) yield - (ScopedKey(scope.copy(task = Zero), key), scope.task): (ScopedKey[_], - ScopeAxis[AttributeKey[_]]) + (ScopedKey(scope.copy(task = Zero), key), scope.task): ( + ScopedKey[_], + ScopeAxis[AttributeKey[_]] + ) val taskAxes = Relation.empty ++ taskAxesMappings val zero = new HashSet[ScopedKey[_]] @@ -270,10 +276,12 @@ abstract class TestBuild { listOfN(ig, g) } - implicit def genProjects(build: URI)(implicit genID: Gen[String], - maxDeps: Gen[Int], - count: Gen[Int], - confs: Gen[Seq[Configuration]]): Gen[Seq[Proj]] = + implicit def genProjects(build: URI)( + implicit genID: Gen[String], + maxDeps: Gen[Int], + count: Gen[Int], + confs: Gen[Seq[Configuration]] + ): Gen[Seq[Proj]] = genAcyclic(maxDeps, genID, count) { (id: String) => for (cs <- confs) yield { (deps: Seq[Proj]) => new Proj(id, deps.map { dep => @@ -282,21 +290,26 @@ abstract class TestBuild { } } - def genConfigs(implicit genName: Gen[String], - maxDeps: Gen[Int], - count: Gen[Int]): Gen[Seq[Configuration]] = + def genConfigs( + implicit genName: Gen[String], + maxDeps: Gen[Int], + count: Gen[Int] + ): Gen[Seq[Configuration]] = genAcyclicDirect[Configuration, String](maxDeps, genName, count)( (key, deps) => Configuration .of(key.capitalize, key) - .withExtendsConfigs(deps.toVector)) + .withExtendsConfigs(deps.toVector) + ) def genTasks(implicit genName: Gen[String], maxDeps: Gen[Int], count: Gen[Int]): Gen[Seq[Taskk]] = - genAcyclicDirect[Taskk, String](maxDeps, genName, count)((key, deps) => - new Taskk(AttributeKey[String](key), deps)) + genAcyclicDirect[Taskk, String](maxDeps, genName, count)( + (key, deps) => new Taskk(AttributeKey[String](key), deps) + ) def genAcyclicDirect[A, T](maxDeps: Gen[Int], keyGen: Gen[T], max: Gen[Int])( - make: (T, Seq[A]) => A): Gen[Seq[A]] = + make: (T, Seq[A]) => A + ): Gen[Seq[A]] = genAcyclic[A, T](maxDeps, keyGen, max) { t => Gen.const { deps => make(t, deps) @@ -304,7 +317,8 @@ abstract class TestBuild { } def genAcyclic[A, T](maxDeps: Gen[Int], keyGen: Gen[T], max: Gen[Int])( - make: T => Gen[Seq[A] => A]): Gen[Seq[A]] = + make: T => Gen[Seq[A] => A] + ): Gen[Seq[A]] = max flatMap { count => listOfN(count, keyGen) flatMap { keys => genAcyclic(maxDeps, keys.distinct)(make) @@ -325,9 +339,11 @@ abstract class TestBuild { (key, deps, mk) } - def genAcyclic[T](maxDeps: Gen[Int], - names: List[T], - acc: List[Gen[(T, Seq[T])]]): Gen[Seq[(T, Seq[T])]] = + def genAcyclic[T]( + maxDeps: Gen[Int], + names: List[T], + acc: List[Gen[(T, Seq[T])]] + ): Gen[Seq[(T, Seq[T])]] = names match { case Nil => sequence(acc) case x :: xs => diff --git a/main/src/test/scala/sbt/internal/parser/CheckIfParsedSpec.scala b/main/src/test/scala/sbt/internal/parser/CheckIfParsedSpec.scala index c230f8a68..807b31813 100644 --- a/main/src/test/scala/sbt/internal/parser/CheckIfParsedSpec.scala +++ b/main/src/test/scala/sbt/internal/parser/CheckIfParsedSpec.scala @@ -11,8 +11,8 @@ package parser abstract class CheckIfParsedSpec( implicit val splitter: SplitExpressions.SplitExpression = - EvaluateConfigurations.splitExpressions) - extends AbstractSpec { + EvaluateConfigurations.splitExpressions +) extends AbstractSpec { this.getClass.getName should { diff --git a/main/src/test/scala/sbt/internal/parser/CommentedXmlSpec.scala b/main/src/test/scala/sbt/internal/parser/CommentedXmlSpec.scala index a3cb444a7..95f53e91d 100644 --- a/main/src/test/scala/sbt/internal/parser/CommentedXmlSpec.scala +++ b/main/src/test/scala/sbt/internal/parser/CommentedXmlSpec.scala @@ -44,10 +44,7 @@ class CommentedXmlSpec extends CheckIfParsedSpec { | |publishMavenStyle := true | - """.stripMargin, - "Wrong Commented xml ", - false, - true), + """.stripMargin, "Wrong Commented xml ", false, true), (""" |val scmpom = taskKey[xml.NodeBuffer]("Node buffer") | @@ -67,23 +64,14 @@ class CommentedXmlSpec extends CheckIfParsedSpec { | |publishMavenStyle := true | - """.stripMargin, - "Commented xml ", - false, - true), + """.stripMargin, "Commented xml ", false, true), (""" |import sbt._ | |// - """.stripMargin, - "Xml in comment2", - false, - false) + """.stripMargin, "Xml in comment2", false, false) ) } diff --git a/main/src/test/scala/sbt/internal/parser/EmbeddedXmlSpec.scala b/main/src/test/scala/sbt/internal/parser/EmbeddedXmlSpec.scala index d939d5e8c..bc2c78d38 100644 --- a/main/src/test/scala/sbt/internal/parser/EmbeddedXmlSpec.scala +++ b/main/src/test/scala/sbt/internal/parser/EmbeddedXmlSpec.scala @@ -52,16 +52,10 @@ class EmbeddedXmlSpec extends CheckIfParsedSpec { protected val files = Seq( (""" |val p = - """.stripMargin, - "Xml modified closing tag at end of file", - false, - true), + """.stripMargin, "Xml modified closing tag at end of file", false, true), (""" |val p = - """.stripMargin, - "Xml at end of file", - false, - true), + """.stripMargin, "Xml at end of file", false, true), ("""| | |name := "play-html-compressor" @@ -98,10 +92,7 @@ class EmbeddedXmlSpec extends CheckIfParsedSpec { | |val tra = "" | - """.stripMargin, - "Xml in string", - false, - true), + """.stripMargin, "Xml in string", false, true), ("""| | |name := "play-html-compressor" @@ -131,10 +122,7 @@ class EmbeddedXmlSpec extends CheckIfParsedSpec { | | | - | """.stripMargin, - "Xml with attributes", - false, - true), + | """.stripMargin, "Xml with attributes", false, true), ( """ |scalaVersion := "2.10.2" diff --git a/main/src/test/scala/sbt/internal/parser/ErrorSpec.scala b/main/src/test/scala/sbt/internal/parser/ErrorSpec.scala index 845baccd7..77641efcc 100644 --- a/main/src/test/scala/sbt/internal/parser/ErrorSpec.scala +++ b/main/src/test/scala/sbt/internal/parser/ErrorSpec.scala @@ -50,7 +50,8 @@ class ErrorSpec extends AbstractSpec { buildSbt.length, 2, "fake.txt", - new MessageOnlyException("fake")) must throwA[MessageOnlyException] + new MessageOnlyException("fake") + ) must throwA[MessageOnlyException] } "handle xml error " in { diff --git a/main/src/test/scala/sbt/internal/parser/SessionSettingsSpec.scala b/main/src/test/scala/sbt/internal/parser/SessionSettingsSpec.scala index 3eb364ed9..8d8876091 100644 --- a/main/src/test/scala/sbt/internal/parser/SessionSettingsSpec.scala +++ b/main/src/test/scala/sbt/internal/parser/SessionSettingsSpec.scala @@ -33,8 +33,9 @@ abstract class AbstractSessionSettingsSpec(folder: String) extends AbstractSpec } } - private def runTestOnFiles(expectedResultAndMap: File => Seq[(List[String], Seq[SessionSetting])]) - : MatchResult[GenTraversableOnce[File]] = { + private def runTestOnFiles( + expectedResultAndMap: File => Seq[(List[String], Seq[SessionSetting])] + ): MatchResult[GenTraversableOnce[File]] = { val allFiles = rootDir .listFiles(new FilenameFilter() { diff --git a/main/src/test/scala/sbt/internal/parser/SplitExpressionsBehavior.scala b/main/src/test/scala/sbt/internal/parser/SplitExpressionsBehavior.scala index bda88bc0d..c4df5a0ce 100644 --- a/main/src/test/scala/sbt/internal/parser/SplitExpressionsBehavior.scala +++ b/main/src/test/scala/sbt/internal/parser/SplitExpressionsBehavior.scala @@ -15,7 +15,8 @@ import org.specs2.mutable.SpecificationLike trait SplitExpression { def split(s: String, file: File = new File("noFile"))( - implicit splitter: SplitExpressions.SplitExpression) = splitter(file, s.split("\n").toSeq) + implicit splitter: SplitExpressions.SplitExpression + ) = splitter(file, s.split("\n").toSeq) } trait SplitExpressionsBehavior extends SplitExpression { this: SpecificationLike => diff --git a/main/src/test/scala/sbt/internal/server/DefinitionTest.scala b/main/src/test/scala/sbt/internal/server/DefinitionTest.scala index 5cbff9163..d09c72e08 100644 --- a/main/src/test/scala/sbt/internal/server/DefinitionTest.scala +++ b/main/src/test/scala/sbt/internal/server/DefinitionTest.scala @@ -24,7 +24,8 @@ class DefinitionTest extends org.specs2.mutable.Specification { } "find valid standard scala identifier with comma" in { textProcessor.identifier("def foo(a: identifier, b: other) = ???", 13) must beSome( - "identifier") + "identifier" + ) } "find valid standard short scala identifier when caret is set at the start of it" in { textProcessor.identifier("val a = 0", 4) must beSome("a") @@ -88,11 +89,13 @@ class DefinitionTest extends org.specs2.mutable.Specification { } "match class in line version 4" in { textProcessor.classTraitObjectInLine("A")(" class A[A] ") must contain( - ("class A", 3)) + ("class A", 3) + ) } "match class in line version 5" in { textProcessor.classTraitObjectInLine("A")(" class A [A] ") must contain( - ("class A", 3)) + ("class A", 3) + ) } "match class in line version 6" in { textProcessor.classTraitObjectInLine("A")("class A[A[_]] {") must contain(("class A", 0)) @@ -111,11 +114,13 @@ class DefinitionTest extends org.specs2.mutable.Specification { } "match trait in line version 4" in { textProcessor.classTraitObjectInLine("A")(" trait A[A] ") must contain( - ("trait A", 3)) + ("trait A", 3) + ) } "match trait in line version 5" in { textProcessor.classTraitObjectInLine("A")(" trait A [A] ") must contain( - ("trait A", 3)) + ("trait A", 3) + ) } "match trait in line version 6" in { textProcessor.classTraitObjectInLine("A")("trait A[A[_]] {") must contain(("trait A", 0)) diff --git a/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala b/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala index a80819af1..b60121458 100644 --- a/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala +++ b/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala @@ -126,20 +126,24 @@ object SettingQueryTest extends org.specs2.mutable.Specification { val buildUnit: BuildUnit = { val loadedPlugins: LoadedPlugins = - noPlugins(projectStandard(baseFile), - config.copy(pluginManagement = config.pluginManagement.forPlugin)) + noPlugins( + projectStandard(baseFile), + config.copy(pluginManagement = config.pluginManagement.forPlugin) + ) val project: Project = { val project0 = Project("t", baseFile) settings projectSettings val fileToLoadedSbtFileMap = new mutable.HashMap[File, LoadedSbtFile] val autoPlugins = loadedPlugins.detected.deducePluginsFromProject(project0, state.log) val injectSettings = config.injectSettings - resolveProject(project0, - autoPlugins, - loadedPlugins, - injectSettings, - fileToLoadedSbtFileMap, - state.log) + resolveProject( + project0, + autoPlugins, + loadedPlugins, + injectSettings, + fileToLoadedSbtFileMap, + state.log + ) } val projects: Seq[Project] = Seq(project) @@ -160,7 +164,8 @@ object SettingQueryTest extends org.specs2.mutable.Specification { val units: Map[URI, LoadedBuildUnit] = loadedBuild.units val settings: Seq[Setting[_]] = finalTransforms( - buildConfigurations(loadedBuild, getRootProject(units), config.injectSettings)) + buildConfigurations(loadedBuild, getRootProject(units), config.injectSettings) + ) val delegates: Scope => Seq[Scope] = defaultDelegates(loadedBuild) val scopeLocal: ScopeLocal = EvaluateTask.injectStreams val display: Show[ScopedKey[_]] = Project showLoadingKey loadedBuild @@ -200,7 +205,8 @@ object SettingQueryTest extends org.specs2.mutable.Specification { "t/startYear" in qok("null", "scala.Option[Int]") "t/scalaArtifacts" in qok( """["scala-library","scala-compiler","scala-reflect","scala-actors","scalap"]""", - "scala.collection.Seq[java.lang.String]") + "scala.collection.Seq[java.lang.String]" + ) "t/libraryDependencies" in qok( """[{"organization":"org.scala-lang","name":"scala-library","revision":"2.12.1","isChanging":false,"isTransitive":true,"isForce":false,"explicitArtifacts":[],"inclusions":[],"exclusions":[],"extraAttributes":{},"crossVersion":{"type":"Disabled"}}]""", @@ -209,8 +215,10 @@ object SettingQueryTest extends org.specs2.mutable.Specification { "scalaVersion" in qko("Not a valid project ID: scalaVersion\\nscalaVersion\\n ^") "t/scalacOptions" in qko( - s"""Key ProjectRef(uri(\\"$baseUri\\"), \\"t\\") / Compile / scalacOptions is a task, can only query settings""") + s"""Key ProjectRef(uri(\\"$baseUri\\"), \\"t\\") / Compile / scalacOptions is a task, can only query settings""" + ) "t/fooo" in qko( - "Expected ':' (if selecting a configuration)\\nNot a valid key: fooo (similar: fork)\\nt/fooo\\n ^") + "Expected ':' (if selecting a configuration)\\nNot a valid key: fooo (similar: fork)\\nt/fooo\\n ^" + ) } } diff --git a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcNotificationMessageFormats.scala b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcNotificationMessageFormats.scala index b00ae5d07..d4fe42afe 100644 --- a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcNotificationMessageFormats.scala +++ b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcNotificationMessageFormats.scala @@ -17,7 +17,8 @@ trait JsonRpcNotificationMessageFormats { new JsonFormat[sbt.internal.protocol.JsonRpcNotificationMessage] { override def read[J]( jsOpt: Option[J], - unbuilder: Unbuilder[J]): sbt.internal.protocol.JsonRpcNotificationMessage = { + unbuilder: Unbuilder[J] + ): sbt.internal.protocol.JsonRpcNotificationMessage = { jsOpt match { case Some(js) => unbuilder.beginObject(js) @@ -32,8 +33,10 @@ trait JsonRpcNotificationMessageFormats { deserializationError("Expected JsObject but found None") } } - override def write[J](obj: sbt.internal.protocol.JsonRpcNotificationMessage, - builder: Builder[J]): Unit = { + override def write[J]( + obj: sbt.internal.protocol.JsonRpcNotificationMessage, + builder: Builder[J] + ): Unit = { builder.beginObject() builder.addField("jsonrpc", obj.jsonrpc) builder.addField("method", obj.method) diff --git a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcRequestMessageFormats.scala b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcRequestMessageFormats.scala index 8a4987741..1ed949b31 100644 --- a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcRequestMessageFormats.scala +++ b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcRequestMessageFormats.scala @@ -15,8 +15,10 @@ trait JsonRpcRequestMessageFormats { implicit lazy val JsonRpcRequestMessageFormat : JsonFormat[sbt.internal.protocol.JsonRpcRequestMessage] = new JsonFormat[sbt.internal.protocol.JsonRpcRequestMessage] { - override def read[J](jsOpt: Option[J], - unbuilder: Unbuilder[J]): sbt.internal.protocol.JsonRpcRequestMessage = { + override def read[J]( + jsOpt: Option[J], + unbuilder: Unbuilder[J] + ): sbt.internal.protocol.JsonRpcRequestMessage = { jsOpt match { case Some(js) => unbuilder.beginObject(js) @@ -39,8 +41,10 @@ trait JsonRpcRequestMessageFormats { deserializationError("Expected JsObject but found None") } } - override def write[J](obj: sbt.internal.protocol.JsonRpcRequestMessage, - builder: Builder[J]): Unit = { + override def write[J]( + obj: sbt.internal.protocol.JsonRpcRequestMessage, + builder: Builder[J] + ): Unit = { builder.beginObject() builder.addField("jsonrpc", obj.jsonrpc) builder.addField("id", obj.id) diff --git a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseErrorFormats.scala b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseErrorFormats.scala index 110a6cae2..d330ac5bb 100644 --- a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseErrorFormats.scala +++ b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseErrorFormats.scala @@ -15,8 +15,10 @@ trait JsonRpcResponseErrorFormats { implicit lazy val JsonRpcResponseErrorFormat : JsonFormat[sbt.internal.protocol.JsonRpcResponseError] = new JsonFormat[sbt.internal.protocol.JsonRpcResponseError] { - override def read[J](jsOpt: Option[J], - unbuilder: Unbuilder[J]): sbt.internal.protocol.JsonRpcResponseError = { + override def read[J]( + jsOpt: Option[J], + unbuilder: Unbuilder[J] + ): sbt.internal.protocol.JsonRpcResponseError = { jsOpt match { case Some(js) => unbuilder.beginObject(js) @@ -31,8 +33,10 @@ trait JsonRpcResponseErrorFormats { deserializationError("Expected JsObject but found None") } } - override def write[J](obj: sbt.internal.protocol.JsonRpcResponseError, - builder: Builder[J]): Unit = { + override def write[J]( + obj: sbt.internal.protocol.JsonRpcResponseError, + builder: Builder[J] + ): Unit = { builder.beginObject() builder.addField("code", obj.code) builder.addField("message", obj.message) diff --git a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala index a8794647f..db967420e 100644 --- a/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala +++ b/protocol/src/main/scala/sbt/internal/protocol/codec/JsonRpcResponseMessageFormats.scala @@ -25,7 +25,8 @@ trait JsonRpcResponseMessageFormats { new JsonFormat[sbt.internal.protocol.JsonRpcResponseMessage] { override def read[J]( jsOpt: Option[J], - unbuilder: Unbuilder[J]): sbt.internal.protocol.JsonRpcResponseMessage = { + unbuilder: Unbuilder[J] + ): sbt.internal.protocol.JsonRpcResponseMessage = { jsOpt match { case Some(js) => unbuilder.beginObject(js) @@ -50,8 +51,10 @@ trait JsonRpcResponseMessageFormats { deserializationError("Expected JsObject but found None") } } - override def write[J](obj: sbt.internal.protocol.JsonRpcResponseMessage, - builder: Builder[J]): Unit = { + override def write[J]( + obj: sbt.internal.protocol.JsonRpcResponseMessage, + builder: Builder[J] + ): Unit = { // Parse given id to Long or String judging by prefix def parseId(str: String): Either[Long, String] = { if (str.startsWith("\u2668")) Left(str.substring(1).toLong) diff --git a/run/src/main/scala/sbt/Fork.scala b/run/src/main/scala/sbt/Fork.scala index 74b70d024..3fc3e4142 100644 --- a/run/src/main/scala/sbt/Fork.scala +++ b/run/src/main/scala/sbt/Fork.scala @@ -64,9 +64,11 @@ final class Fork(val commandName: String, val runnerClass: Option[String]) { case out: CustomOutput => (process #> out.output).run(connectInput = false) } } - private[this] def makeOptions(jvmOptions: Seq[String], - bootJars: Iterable[File], - arguments: Seq[String]): Seq[String] = { + private[this] def makeOptions( + jvmOptions: Seq[String], + bootJars: Iterable[File], + arguments: Seq[String] + ): Seq[String] = { val boot = if (bootJars.isEmpty) None else diff --git a/run/src/main/scala/sbt/Run.scala b/run/src/main/scala/sbt/Run.scala index 15469f86e..a1fb598d6 100644 --- a/run/src/main/scala/sbt/Run.scala +++ b/run/src/main/scala/sbt/Run.scala @@ -29,8 +29,8 @@ class ForkRun(config: ForkOptions) extends ScalaRun { if (exitCode == 0) Success(()) else Failure( - new RuntimeException( - s"""Nonzero exit code returned from $label: $exitCode""".stripMargin)) + new RuntimeException(s"""Nonzero exit code returned from $label: $exitCode""".stripMargin) + ) val process = fork(mainClass, classpath, options, log) def cancel() = { log.warn("Run canceled.") @@ -77,10 +77,12 @@ class Run(instance: ScalaInstance, trapExit: Boolean, nativeTmp: File) extends S if (trapExit) Run.executeTrapExit(execute(), log) else directExecute() } - private def run0(mainClassName: String, - classpath: Seq[File], - options: Seq[String], - log: Logger): Unit = { + private def run0( + mainClassName: String, + classpath: Seq[File], + options: Seq[String], + log: Logger + ): Unit = { log.debug(" Classpath:\n\t" + classpath.mkString("\n\t")) val loader = ClasspathUtilities.makeLoader(classpath, instance, nativeTmp) val main = getMainMethod(mainClassName, loader) @@ -112,7 +114,8 @@ class Run(instance: ScalaInstance, trapExit: Boolean, nativeTmp: File) extends S /** This module is an interface to starting the scala interpreter or runner.*/ object Run { def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger)( - implicit runner: ScalaRun) = + implicit runner: ScalaRun + ) = runner.run(mainClass, classpath, options, log) /** Executes the given function, trapping calls to System.exit. */ diff --git a/run/src/main/scala/sbt/SelectMainClass.scala b/run/src/main/scala/sbt/SelectMainClass.scala index b840bf839..86bcbcdb9 100644 --- a/run/src/main/scala/sbt/SelectMainClass.scala +++ b/run/src/main/scala/sbt/SelectMainClass.scala @@ -9,8 +9,10 @@ package sbt object SelectMainClass { // Some(SimpleReader.readLine _) - def apply(promptIfMultipleChoices: Option[String => Option[String]], - mainClasses: Seq[String]): Option[String] = { + def apply( + promptIfMultipleChoices: Option[String => Option[String]], + mainClasses: Seq[String] + ): Option[String] = { mainClasses.toList match { case Nil => None case head :: Nil => Some(head) diff --git a/run/src/main/scala/sbt/TrapExit.scala b/run/src/main/scala/sbt/TrapExit.scala index 54cf83760..2dea6d17f 100644 --- a/run/src/main/scala/sbt/TrapExit.scala +++ b/run/src/main/scala/sbt/TrapExit.scala @@ -113,8 +113,9 @@ object TrapExit { * Recurses into the causes of the given exception looking for a cause of type CauseType. If one is found, `withType` is called with that cause. * If not, `notType` is called with the root cause. */ - private def withCause[CauseType <: Throwable, T](e: Throwable)(withType: CauseType => T)( - notType: Throwable => T)(implicit mf: Manifest[CauseType]): T = { + private def withCause[CauseType <: Throwable, T]( + e: Throwable + )(withType: CauseType => T)(notType: Throwable => T)(implicit mf: Manifest[CauseType]): T = { val clazz = mf.runtimeClass if (clazz.isInstance(e)) withType(e.asInstanceOf[CauseType]) @@ -348,8 +349,10 @@ private final class TrapExit(delegateManager: SecurityManager) extends SecurityM // takes a snapshot of the threads in `toProcess`, acquiring nested locks on each group to do so // the thread groups are accumulated in `accum` and then the threads in each are collected all at // once while they are all locked. This is the closest thing to a snapshot that can be accomplished. - private[this] def threadsInGroups(toProcess: List[ThreadGroup], - accum: List[ThreadGroup]): List[Thread] = toProcess match { + private[this] def threadsInGroups( + toProcess: List[ThreadGroup], + accum: List[ThreadGroup] + ): List[Thread] = toProcess match { case group :: tail => // ThreadGroup implementation synchronizes on its methods, so by synchronizing here, we can workaround its quirks somewhat group.synchronized { @@ -521,9 +524,10 @@ private final class ExitCode { * The default uncaught exception handler for managed executions. * It logs the thread and the exception. */ -private final class LoggingExceptionHandler(log: Logger, - delegate: Option[Thread.UncaughtExceptionHandler]) - extends Thread.UncaughtExceptionHandler { +private final class LoggingExceptionHandler( + log: Logger, + delegate: Option[Thread.UncaughtExceptionHandler] +) extends Thread.UncaughtExceptionHandler { def uncaughtException(t: Thread, e: Throwable): Unit = { log.error("(" + t.getName + ") " + e.toString) log.trace(e) diff --git a/sbt/src/test/scala/sbt/ServerSpec.scala b/sbt/src/test/scala/sbt/ServerSpec.scala index 87c129e90..0f147786c 100644 --- a/sbt/src/test/scala/sbt/ServerSpec.scala +++ b/sbt/src/test/scala/sbt/ServerSpec.scala @@ -22,7 +22,8 @@ class ServerSpec extends AsyncFlatSpec with Matchers { withBuildSocket("handshake") { (out, in, tkn) => writeLine( """{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "handshake/name" } }""", - out) + out + ) Thread.sleep(100) assert(waitFor(in, 10) { s => s contains """"id":3""" @@ -55,9 +56,11 @@ object ServerSpec { private val threadFactory = new ThreadFactory() { override def newThread(runnable: Runnable): Thread = { val thread = - new Thread(threadGroup, - runnable, - s"sbt-test-server-threads-${nextThreadId.getAndIncrement}") + new Thread( + threadGroup, + runnable, + s"sbt-test-server-threads-${nextThreadId.getAndIncrement}" + ) // Do NOT setDaemon because then the code in TaskExit.scala in sbt will insta-kill // the backgrounded process, at least for the case of the run task. thread @@ -84,8 +87,9 @@ object ServerSpec { def shutdown(): Unit = executor.shutdown() - def withBuildSocket(testBuild: String)( - f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = { + def withBuildSocket( + testBuild: String + )(f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = { IO.withTemporaryDirectory { temp => IO.copyDirectory(serverTestBase / testBuild, temp / testBuild) withBuildSocket(temp / testBuild)(f) @@ -162,8 +166,9 @@ object ServerSpec { writeEndLine } - def withBuildSocket(baseDirectory: File)( - f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = { + def withBuildSocket( + baseDirectory: File + )(f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = { backgroundRun(baseDirectory, Nil) val portfile = baseDirectory / "project" / "target" / "active.json" @@ -185,14 +190,16 @@ object ServerSpec { sendJsonRpc( """{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { } } }""", - out) + out + ) try { f(out, in, tkn) } finally { sendJsonRpc( """{ "jsonrpc": "2.0", "id": 9, "method": "sbt/exec", "params": { "commandLine": "exit" } }""", - out) + out + ) // shutdown() } } diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index dd9de5bc1..dabaeeca1 100644 --- a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -134,7 +134,8 @@ final class ScriptedTests( def logTests(size: Int, how: String) = log.info( - f"Running $size / $totalSize (${size * 100D / totalSize}%3.2f%%) scripted tests with $how") + f"Running $size / $totalSize (${size * 100D / totalSize}%3.2f%%) scripted tests with $how" + ) logTests(runFromSourceBasedTests.size, "RunFromSourceMain") logTests(launcherBasedTests.size, "sbt/launcher") diff --git a/tasks-standard/src/main/scala/sbt/Action.scala b/tasks-standard/src/main/scala/sbt/Action.scala index be5585cbd..6cd50824d 100644 --- a/tasks-standard/src/main/scala/sbt/Action.scala +++ b/tasks-standard/src/main/scala/sbt/Action.scala @@ -74,8 +74,10 @@ final case class Task[T](info: Info[T], work: Action[T]) { * @param attributes Arbitrary user-defined key/value pairs describing this task * @param post a transformation that takes the result of evaluating this task and produces user-defined key/value pairs. */ -final case class Info[T](attributes: AttributeMap = AttributeMap.empty, - post: T => AttributeMap = const(AttributeMap.empty)) { +final case class Info[T]( + attributes: AttributeMap = AttributeMap.empty, + post: T => AttributeMap = const(AttributeMap.empty) +) { import Info._ def name = attributes.get(Name) def description = attributes.get(Description) diff --git a/tasks-standard/src/main/scala/sbt/std/Streams.scala b/tasks-standard/src/main/scala/sbt/std/Streams.scala index 6206b9271..61f97f89e 100644 --- a/tasks-standard/src/main/scala/sbt/std/Streams.scala +++ b/tasks-standard/src/main/scala/sbt/std/Streams.scala @@ -126,10 +126,12 @@ object Streams { synchronized { streams.values.foreach(_.close()); streams.clear() } } - def apply[Key, J: IsoString](taskDirectory: Key => File, - name: Key => String, - mkLogger: (Key, PrintWriter) => ManagedLogger, - converter: SupportConverter[J]): Streams[Key] = new Streams[Key] { + def apply[Key, J: IsoString]( + taskDirectory: Key => File, + name: Key => String, + mkLogger: (Key, PrintWriter) => ManagedLogger, + converter: SupportConverter[J] + ): Streams[Key] = new Streams[Key] { def apply(a: Key): ManagedStreams[Key] = new ManagedStreams[Key] { private[this] var opened: List[Closeable] = Nil @@ -142,8 +144,9 @@ object Streams { make(a, sid)(f => new PlainOutput(new FileOutputStream(f), converter)) def readText(a: Key, sid: String = default): BufferedReader = - make(a, sid)(f => - new BufferedReader(new InputStreamReader(new FileInputStream(f), IO.defaultCharset))) + make(a, sid)( + f => new BufferedReader(new InputStreamReader(new FileInputStream(f), IO.defaultCharset)) + ) def readBinary(a: Key, sid: String = default): BufferedInputStream = make(a, sid)(f => new BufferedInputStream(new FileInputStream(f))) @@ -152,8 +155,13 @@ object Streams { make(a, sid)( f => new PrintWriter( - new DeferredWriter(new BufferedWriter( - new OutputStreamWriter(new FileOutputStream(f), IO.defaultCharset))))) + new DeferredWriter( + new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(f), IO.defaultCharset) + ) + ) + ) + ) def binary(sid: String = default): BufferedOutputStream = make(a, sid)(f => new BufferedOutputStream(new FileOutputStream(f))) diff --git a/tasks-standard/src/main/scala/sbt/std/System.scala b/tasks-standard/src/main/scala/sbt/std/System.scala index 060bc0d48..ed2739646 100644 --- a/tasks-standard/src/main/scala/sbt/std/System.scala +++ b/tasks-standard/src/main/scala/sbt/std/System.scala @@ -58,8 +58,9 @@ object Transform { def uniform[T, D](tasks: Seq[Task[D]])(f: Seq[Result[D]] => Either[Task[T], T]): Node[Task, T] = toNode[T, λ[L[x] => List[L[D]]]](tasks.toList)(f)(AList.seq[D]) - def toNode[T, k[L[x]]](inputs: k[Task])(f: k[Result] => Either[Task[T], T])( - implicit a: AList[k]): Node[Task, T] = new Node[Task, T] { + def toNode[T, k[L[x]]]( + inputs: k[Task] + )(f: k[Result] => Either[Task[T], T])(implicit a: AList[k]): Node[Task, T] = new Node[Task, T] { type K[L[x]] = k[L] val in = inputs val alist = a diff --git a/tasks-standard/src/main/scala/sbt/std/TaskExtra.scala b/tasks-standard/src/main/scala/sbt/std/TaskExtra.scala index b94ea5e1c..52e754a81 100644 --- a/tasks-standard/src/main/scala/sbt/std/TaskExtra.scala +++ b/tasks-standard/src/main/scala/sbt/std/TaskExtra.scala @@ -39,22 +39,26 @@ sealed trait SingleInTask[S] { @deprecated( "Use the `result` method to create a task that returns the full Result of this task. Then, call `map` on the new task.", - "0.13.0") + "0.13.0" + ) def mapR[T](f: Result[S] => T): Task[T] @deprecated( "Use the `failure` method to create a task that returns Incomplete when this task fails and then call `flatMap` on the new task.", - "0.13.0") + "0.13.0" + ) def flatFailure[T](f: Incomplete => Task[T]): Task[T] @deprecated( "Use the `failure` method to create a task that returns Incomplete when this task fails and then call `mapFailure` on the new task.", - "0.13.0") + "0.13.0" + ) def mapFailure[T](f: Incomplete => T): Task[T] @deprecated( "Use the `result` method to create a task that returns the full Result of this task. Then, call `flatMap` on the new task.", - "0.13.0") + "0.13.0" + ) def flatMapR[T](f: Result[S] => Task[T]): Task[T] } sealed trait TaskInfo[S] { @@ -160,10 +164,12 @@ trait TaskExtra { def andFinally(fin: => Unit): Task[S] = mapR(x => Result.tryValue[S]({ fin; x })) def doFinally(t: Task[Unit]): Task[S] = - flatMapR(x => - t.result.map { tx => - Result.tryValues[S](tx :: Nil, x) - }) + flatMapR( + x => + t.result.map { tx => + Result.tryValues[S](tx :: Nil, x) + } + ) def ||[T >: S](alt: Task[T]): Task[T] = flatMapR { case Value(v) => task(v); case Inc(_) => alt } @@ -175,8 +181,9 @@ trait TaskExtra { def named(s: String): Task[S] = in.copy(info = in.info.setName(s)) } - final implicit def pipeToProcess[Key](t: Task[_])(implicit streams: Task[TaskStreams[Key]], - key: Task[_] => Key): ProcessPipe = + final implicit def pipeToProcess[Key]( + t: Task[_] + )(implicit streams: Task[TaskStreams[Key]], key: Task[_] => Key): ProcessPipe = new ProcessPipe { def #|(p: ProcessBuilder): Task[Int] = pipe0(None, p) def pipe(sid: String)(p: ProcessBuilder): Task[Int] = pipe0(Some(sid), p) @@ -190,8 +197,9 @@ trait TaskExtra { } } - final implicit def binaryPipeTask[Key](in: Task[_])(implicit streams: Task[TaskStreams[Key]], - key: Task[_] => Key): BinaryPipe = + final implicit def binaryPipeTask[Key]( + in: Task[_] + )(implicit streams: Task[TaskStreams[Key]], key: Task[_] => Key): BinaryPipe = new BinaryPipe { def binary[T](f: BufferedInputStream => T): Task[T] = pipe0(None, f) def binary[T](sid: String)(f: BufferedInputStream => T): Task[T] = pipe0(Some(sid), f) @@ -206,8 +214,9 @@ trait TaskExtra { private def toFile(f: File) = (in: InputStream) => IO.transfer(in, f) } - final implicit def textPipeTask[Key](in: Task[_])(implicit streams: Task[TaskStreams[Key]], - key: Task[_] => Key): TextPipe = new TextPipe { + final implicit def textPipeTask[Key]( + in: Task[_] + )(implicit streams: Task[TaskStreams[Key]], key: Task[_] => Key): TextPipe = new TextPipe { def text[T](f: BufferedReader => T): Task[T] = pipe0(None, f) def text[T](sid: String)(f: BufferedReader => T): Task[T] = pipe0(Some(sid), f) @@ -216,8 +225,9 @@ trait TaskExtra { f(s.readText(key(in), sid)) } } - final implicit def linesTask[Key](in: Task[_])(implicit streams: Task[TaskStreams[Key]], - key: Task[_] => Key): TaskLines = new TaskLines { + final implicit def linesTask[Key]( + in: Task[_] + )(implicit streams: Task[TaskStreams[Key]], key: Task[_] => Key): TaskLines = new TaskLines { def lines: Task[List[String]] = lines0(None) def lines(sid: String): Task[List[String]] = lines0(Some(sid)) diff --git a/tasks-standard/src/test/scala/TaskGen.scala b/tasks-standard/src/test/scala/TaskGen.scala index 8fdace35d..ee7539ef9 100644 --- a/tasks-standard/src/test/scala/TaskGen.scala +++ b/tasks-standard/src/test/scala/TaskGen.scala @@ -24,9 +24,11 @@ object TaskGen extends std.TaskExtra { def run[T](root: Task[T], checkCycles: Boolean, maxWorkers: Int): Result[T] = { val (service, shutdown) = CompletionService[Task[_], Completed](maxWorkers) val dummies = std.Transform.DummyTaskMap(Nil) - val x = new Execute[Task](Execute.config(checkCycles), - Execute.noTriggers, - ExecuteProgress.empty[Task])(std.Transform(dummies)) + val x = new Execute[Task]( + Execute.config(checkCycles), + Execute.noTriggers, + ExecuteProgress.empty[Task] + )(std.Transform(dummies)) try { x.run(root)(service) } finally { shutdown() } } def tryRun[T](root: Task[T], checkCycles: Boolean, maxWorkers: Int): T = diff --git a/tasks-standard/src/test/scala/TaskSerial.scala b/tasks-standard/src/test/scala/TaskSerial.scala index 43aeea41f..8f2f8bf3c 100644 --- a/tasks-standard/src/test/scala/TaskSerial.scala +++ b/tasks-standard/src/test/scala/TaskSerial.scala @@ -50,9 +50,11 @@ object TaskSerial extends Properties("task serial") { } */ - def checkArbitrary(size: Int, - restrictions: ConcurrentRestrictions[Task[_]], - shouldSucceed: Boolean) = { + def checkArbitrary( + size: Int, + restrictions: ConcurrentRestrictions[Task[_]], + shouldSucceed: Boolean + ) = { val latch = task { new CountDownLatch(size) } def mktask = latch map { l => l.countDown() @@ -74,20 +76,26 @@ object TaskSerial extends Properties("task serial") { } object TaskTest { - def run[T](root: Task[T], - checkCycles: Boolean, - restrictions: ConcurrentRestrictions[Task[_]]): Result[T] = { + def run[T]( + root: Task[T], + checkCycles: Boolean, + restrictions: ConcurrentRestrictions[Task[_]] + ): Result[T] = { val (service, shutdown) = completionService[Task[_], Completed](restrictions, (x: String) => System.err.println(x)) - val x = new Execute[Task](Execute.config(checkCycles), - Execute.noTriggers, - ExecuteProgress.empty[Task])(taskToNode(idK[Task])) + val x = new Execute[Task]( + Execute.config(checkCycles), + Execute.noTriggers, + ExecuteProgress.empty[Task] + )(taskToNode(idK[Task])) try { x.run(root)(service) } finally { shutdown() } } - def tryRun[T](root: Task[T], - checkCycles: Boolean, - restrictions: ConcurrentRestrictions[Task[_]]): T = + def tryRun[T]( + root: Task[T], + checkCycles: Boolean, + restrictions: ConcurrentRestrictions[Task[_]] + ): T = run(root, checkCycles, restrictions) match { case Value(v) => v case Inc(i) => throw i diff --git a/tasks/src/main/scala/sbt/CompletionService.scala b/tasks/src/main/scala/sbt/CompletionService.scala index f88f6885a..622f60e12 100644 --- a/tasks/src/main/scala/sbt/CompletionService.scala +++ b/tasks/src/main/scala/sbt/CompletionService.scala @@ -37,14 +37,16 @@ object CompletionService { () => future.get() } - def manage[A, T](service: CompletionService[A, T])(setup: A => Unit, - cleanup: A => Unit): CompletionService[A, T] = + def manage[A, T]( + service: CompletionService[A, T] + )(setup: A => Unit, cleanup: A => Unit): CompletionService[A, T] = wrap(service) { (node, work) => () => setup(node) try { work() } finally { cleanup(node) } } - def wrap[A, T](service: CompletionService[A, T])( - w: (A, () => T) => (() => T)): CompletionService[A, T] = + def wrap[A, T]( + service: CompletionService[A, T] + )(w: (A, () => T) => (() => T)): CompletionService[A, T] = new CompletionService[A, T] { def submit(node: A, work: () => T) = service.submit(node, w(node, work)) def take() = service.take() diff --git a/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala b/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala index 7313eba62..b6e011f4c 100644 --- a/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala +++ b/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala @@ -124,8 +124,10 @@ object ConcurrentRestrictions { * @tparam A the task type * @tparam R the type of data that will be computed by the CompletionService. */ - def completionService[A, R](tags: ConcurrentRestrictions[A], - warn: String => Unit): (CompletionService[A, R], () => Unit) = { + def completionService[A, R]( + tags: ConcurrentRestrictions[A], + warn: String => Unit + ): (CompletionService[A, R], () => Unit) = { val pool = Executors.newCachedThreadPool() (completionService[A, R](pool, tags, warn), () => { pool.shutdownNow(); () }) } @@ -134,9 +136,11 @@ object ConcurrentRestrictions { * Constructs a CompletionService suitable for backing task execution based on the provided restrictions on concurrent task execution * and using the provided Executor to manage execution on threads. */ - def completionService[A, R](backing: Executor, - tags: ConcurrentRestrictions[A], - warn: String => Unit): CompletionService[A, R] = { + def completionService[A, R]( + backing: Executor, + tags: ConcurrentRestrictions[A], + warn: String => Unit + ): CompletionService[A, R] = { /** Represents submitted work for a task.*/ final class Enqueue(val node: A, val work: () => R) @@ -180,7 +184,8 @@ object ConcurrentRestrictions { tagState = tags.remove(tagState, node) if (!tags.valid(tagState)) warn( - "Invalid restriction: removing a completed node from a valid system must result in a valid system.") + "Invalid restriction: removing a completed node from a valid system must result in a valid system." + ) submitValid(new LinkedList) } private[this] def errorAddingToIdle() = diff --git a/tasks/src/main/scala/sbt/Execute.scala b/tasks/src/main/scala/sbt/Execute.scala index bba13f448..903dde54f 100644 --- a/tasks/src/main/scala/sbt/Execute.scala +++ b/tasks/src/main/scala/sbt/Execute.scala @@ -27,8 +27,10 @@ private[sbt] object Execute { def config(checkCycles: Boolean, overwriteNode: Incomplete => Boolean = const(false)): Config = new Config(checkCycles, overwriteNode) - final class Config private[sbt] (val checkCycles: Boolean, - val overwriteNode: Incomplete => Boolean) + final class Config private[sbt] ( + val checkCycles: Boolean, + val overwriteNode: Incomplete => Boolean + ) final val checkPreAndPostConditions = sys.props.get("sbt.execute.extrachecks").exists(java.lang.Boolean.parseBoolean) @@ -40,14 +42,17 @@ private[sbt] trait NodeView[A[_]] { def apply[T](a: A[T]): Node[A, T] def inline[T](a: A[T]): Option[() => T] } -final class Triggers[A[_]](val runBefore: collection.Map[A[_], Seq[A[_]]], - val injectFor: collection.Map[A[_], Seq[A[_]]], - val onComplete: RMap[A, Result] => RMap[A, Result]) +final class Triggers[A[_]]( + val runBefore: collection.Map[A[_], Seq[A[_]]], + val injectFor: collection.Map[A[_], Seq[A[_]]], + val onComplete: RMap[A, Result] => RMap[A, Result] +) private[sbt] final class Execute[A[_] <: AnyRef]( config: Config, triggers: Triggers[A], - progress: ExecuteProgress[A])(implicit view: NodeView[A]) { + progress: ExecuteProgress[A] +)(implicit view: NodeView[A]) { type Strategy = CompletionService[A[_], Completed] private[this] val forward = idMap[A[_], IDSet[A[_]]] @@ -204,11 +209,12 @@ private[sbt] final class Execute[A[_] <: AnyRef]( val v = register(node) val deps = dependencies(v) ++ runBefore(node) val active = IDSet[A[_]](deps filter notDone) - progressState = progress.registered(progressState, - node, - deps, - active.toList - /** active is mutable, so take a snapshot */ + progressState = progress.registered( + progressState, + node, + deps, + active.toList + /** active is mutable, so take a snapshot */ ) if (active.isEmpty) @@ -282,7 +288,8 @@ private[sbt] final class Execute[A[_] <: AnyRef]( } } private[this] def rewrap[T]( - rawResult: Either[Incomplete, Either[A[T], T]]): Either[A[T], Result[T]] = + rawResult: Either[Incomplete, Either[A[T], T]] + ): Either[A[T], Result[T]] = rawResult match { case Left(i) => Right(Inc(i)) case Right(Right(v)) => Right(Value(v)) @@ -376,9 +383,11 @@ private[sbt] final class Execute[A[_] <: AnyRef]( if (all contains target) cyclic(node, target, "Cyclic reference") } def cyclic[T](caller: A[T], target: A[T], msg: String) = - throw new Incomplete(Some(caller), - message = Some(msg), - directCause = Some(new CyclicException(caller, target, msg))) + throw new Incomplete( + Some(caller), + message = Some(msg), + directCause = Some(new CyclicException(caller, target, msg)) + ) final class CyclicException[T](val caller: A[T], val target: A[T], msg: String) extends Exception(msg) diff --git a/tasks/src/main/scala/sbt/Incomplete.scala b/tasks/src/main/scala/sbt/Incomplete.scala index ad8296196..78c042ebd 100644 --- a/tasks/src/main/scala/sbt/Incomplete.scala +++ b/tasks/src/main/scala/sbt/Incomplete.scala @@ -21,12 +21,13 @@ import Incomplete.{ Error, Value => IValue } * @param causes a list of incompletions that prevented `node` from completing * @param directCause the exception that caused `node` to not complete */ -final case class Incomplete(node: Option[AnyRef], - tpe: IValue = Error, - message: Option[String] = None, - causes: Seq[Incomplete] = Nil, - directCause: Option[Throwable] = None) - extends Exception(message.orNull, directCause.orNull) +final case class Incomplete( + node: Option[AnyRef], + tpe: IValue = Error, + message: Option[String] = None, + causes: Seq[Incomplete] = Nil, + directCause: Option[Throwable] = None +) extends Exception(message.orNull, directCause.orNull) with sbt.internal.util.UnprintableException { override def toString = "Incomplete(node=" + node + ", tpe=" + tpe + ", msg=" + message + ", causes=" + causes + ", directCause=" + directCause + ")" diff --git a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala index ad547c511..204e69b1a 100644 --- a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala +++ b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala @@ -80,7 +80,8 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { /** Junit XML reports don't differentiate between ignored, skipped or pending tests */ val ignoredSkippedPending = count(TStatus.Ignored) + count(TStatus.Skipped) + count( - TStatus.Pending) + TStatus.Pending + ) val result = diff --git a/testing/src/main/scala/sbt/TestFramework.scala b/testing/src/main/scala/sbt/TestFramework.scala index 95fa55788..11b4b51c8 100644 --- a/testing/src/main/scala/sbt/TestFramework.scala +++ b/testing/src/main/scala/sbt/TestFramework.scala @@ -41,9 +41,11 @@ final class TestFramework(val implClassNames: String*) extends Serializable { } @tailrec - private def createFramework(loader: ClassLoader, - log: ManagedLogger, - frameworkClassNames: List[String]): Option[Framework] = { + private def createFramework( + loader: ClassLoader, + log: ManagedLogger, + frameworkClassNames: List[String] + ): Option[Framework] = { frameworkClassNames match { case head :: tail => try { @@ -64,10 +66,12 @@ final class TestFramework(val implClassNames: String*) extends Serializable { def create(loader: ClassLoader, log: ManagedLogger): Option[Framework] = createFramework(loader, log, implClassNames.toList) } -final class TestDefinition(val name: String, - val fingerprint: Fingerprint, - val explicitlySpecified: Boolean, - val selectors: Array[Selector]) { +final class TestDefinition( + val name: String, + val fingerprint: Fingerprint, + val explicitlySpecified: Boolean, + val selectors: Array[Selector] +) { override def toString = "Test " + name + " : " + TestFramework.toString(fingerprint) override def equals(t: Any) = t match { @@ -87,13 +91,16 @@ final class TestRunner( delegate.tasks( testDefs .map(df => new TaskDef(df.name, df.fingerprint, df.explicitlySpecified, df.selectors)) - .toArray) + .toArray + ) final def run(taskDef: TaskDef, testTask: TestTask): (SuiteResult, Seq[TestTask]) = { - val testDefinition = new TestDefinition(taskDef.fullyQualifiedName, - taskDef.fingerprint, - taskDef.explicitlySpecified, - taskDef.selectors) + val testDefinition = new TestDefinition( + taskDef.fullyQualifiedName, + taskDef.fingerprint, + taskDef.explicitlySpecified, + taskDef.selectors + ) log.debug("Running " + taskDef) val name = testDefinition.name @@ -141,7 +148,8 @@ object TestFramework { it.foreach( i => try f(i) - catch { case NonFatal(e) => log.trace(e); log.error(e.toString) }) + catch { case NonFatal(e) => log.trace(e); log.error(e.toString) } + ) private[sbt] def hashCode(f: Fingerprint): Int = f match { case s: SubclassFingerprint => (s.isModule, s.superclassName).hashCode @@ -180,12 +188,16 @@ object TestFramework { }, mappedTests, tests, log, listeners) } - private[this] def order(mapped: Map[String, TestFunction], - inputs: Vector[TestDefinition]): Vector[(String, TestFunction)] = + private[this] def order( + mapped: Map[String, TestFunction], + inputs: Vector[TestDefinition] + ): Vector[(String, TestFunction)] = for (d <- inputs; act <- mapped.get(d.name)) yield (d.name, act) - private[this] def testMap(frameworks: Seq[Framework], - tests: Seq[TestDefinition]): Map[Framework, Set[TestDefinition]] = { + private[this] def testMap( + frameworks: Seq[Framework], + tests: Seq[TestDefinition] + ): Map[Framework, Set[TestDefinition]] = { import scala.collection.mutable.{ HashMap, HashSet, Set } val map = new HashMap[Framework, Set[TestDefinition]] def assignTest(test: TestDefinition): Unit = { @@ -200,13 +212,14 @@ object TestFramework { map.toMap.mapValues(_.toSet) } - private def createTestTasks(loader: ClassLoader, - runners: Map[Framework, TestRunner], - tests: Map[Framework, Set[TestDefinition]], - ordered: Vector[TestDefinition], - log: ManagedLogger, - listeners: Vector[TestReportListener]) - : (() => Unit, Vector[(String, TestFunction)], TestResult => (() => Unit)) = { + private def createTestTasks( + loader: ClassLoader, + runners: Map[Framework, TestRunner], + tests: Map[Framework, Set[TestDefinition]], + ordered: Vector[TestDefinition], + log: ManagedLogger, + listeners: Vector[TestReportListener] + ): (() => Unit, Vector[(String, TestFunction)], TestResult => (() => Unit)) = { val testsListeners = listeners collect { case tl: TestsListener => tl } def foreachListenerSafe(f: TestsListener => Unit): () => Unit = @@ -232,19 +245,23 @@ object TestFramework { Thread.currentThread.setContextClassLoader(loader) try { eval } finally { Thread.currentThread.setContextClassLoader(oldLoader) } } - def createTestLoader(classpath: Seq[File], - scalaInstance: ScalaInstance, - tempDir: File): ClassLoader = { + def createTestLoader( + classpath: Seq[File], + scalaInstance: ScalaInstance, + tempDir: File + ): ClassLoader = { val interfaceJar = IO.classLocationFile(classOf[testing.Framework]) val interfaceFilter = (name: String) => name.startsWith("org.scalatools.testing.") || name.startsWith("sbt.testing.") val notInterfaceFilter = (name: String) => !interfaceFilter(name) - val dual = new DualLoader(scalaInstance.loader, - notInterfaceFilter, - x => true, - getClass.getClassLoader, - interfaceFilter, - x => false) + val dual = new DualLoader( + scalaInstance.loader, + notInterfaceFilter, + x => true, + getClass.getClassLoader, + interfaceFilter, + x => false + ) val main = ClasspathUtilities.makeLoader(classpath, dual, scalaInstance, tempDir) // TODO - There's actually an issue with the classpath facility such that unmanagedScalaInstances are not added // to the classpath correctly. We have a temporary workaround here. @@ -253,20 +270,26 @@ object TestFramework { else scalaInstance.allJars ++ (interfaceJar +: classpath) ClasspathUtilities.filterByClasspath(cp, main) } - def createTestFunction(loader: ClassLoader, - taskDef: TaskDef, - runner: TestRunner, - testTask: TestTask): TestFunction = - new TestFunction(taskDef, - runner, - (r: TestRunner) => withContextLoader(loader) { r.run(taskDef, testTask) }) { + def createTestFunction( + loader: ClassLoader, + taskDef: TaskDef, + runner: TestRunner, + testTask: TestTask + ): TestFunction = + new TestFunction( + taskDef, + runner, + (r: TestRunner) => withContextLoader(loader) { r.run(taskDef, testTask) } + ) { def tags = testTask.tags } } -abstract class TestFunction(val taskDef: TaskDef, - val runner: TestRunner, - fun: (TestRunner) => (SuiteResult, Seq[TestTask])) { +abstract class TestFunction( + val taskDef: TaskDef, + val runner: TestRunner, + fun: (TestRunner) => (SuiteResult, Seq[TestTask]) +) { def apply(): (SuiteResult, Seq[TestTask]) = fun(runner) From ebe554022659d9e9af6a30df21d4dbc52d924ccf Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 5 Apr 2018 08:25:32 +0100 Subject: [PATCH 254/356] Reformat some corner cases --- main-actions/src/main/scala/sbt/ForkTests.scala | 15 +++++++-------- main-actions/src/main/scala/sbt/Package.scala | 6 ++---- .../src/main/scala/sbt/RawCompileLike.scala | 5 ++--- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/main-actions/src/main/scala/sbt/ForkTests.scala b/main-actions/src/main/scala/sbt/ForkTests.scala index 4299f54ac..9934eca97 100755 --- a/main-actions/src/main/scala/sbt/ForkTests.scala +++ b/main-actions/src/main/scala/sbt/ForkTests.scala @@ -87,15 +87,14 @@ private[sbt] object ForkTests { val config = new ForkConfiguration(ConsoleAppender.formatEnabledInEnv, parallel) os.writeObject(config) - val taskdefs = opts.tests.map( - t => - new TaskDef( - t.name, - forkFingerprint(t.fingerprint), - t.explicitlySpecified, - t.selectors + val taskdefs = opts.tests.map { t => + new TaskDef( + t.name, + forkFingerprint(t.fingerprint), + t.explicitlySpecified, + t.selectors ) - ) + } os.writeObject(taskdefs.toArray) os.writeInt(runners.size) diff --git a/main-actions/src/main/scala/sbt/Package.scala b/main-actions/src/main/scala/sbt/Package.scala index 8ecb1ccf7..e0c29e95c 100644 --- a/main-actions/src/main/scala/sbt/Package.scala +++ b/main-actions/src/main/scala/sbt/Package.scala @@ -67,11 +67,9 @@ object Package { } setVersion(main) + type Inputs = Map[File, String] :+: FilesInfo[ModifiedFileInfo] :+: Manifest :+: HNil val cachedMakeJar = inputChanged(cacheStoreFactory make "inputs") { - ( - inChanged, - inputs: Map[File, String] :+: FilesInfo[ModifiedFileInfo] :+: Manifest :+: HNil - ) => + (inChanged, inputs: Inputs) => import exists.format val sources :+: _ :+: manifest :+: HNil = inputs inputChanged(cacheStoreFactory make "output") { (outChanged, jar: PlainFileInfo) => diff --git a/main-actions/src/main/scala/sbt/RawCompileLike.scala b/main-actions/src/main/scala/sbt/RawCompileLike.scala index cc4c0fe80..c19d55a26 100644 --- a/main-actions/src/main/scala/sbt/RawCompileLike.scala +++ b/main-actions/src/main/scala/sbt/RawCompileLike.scala @@ -54,9 +54,8 @@ object RawCompileLike { ): Gen = (sources, classpath, outputDirectory, options, maxErrors, log) => { type Inputs = - FilesInfo[HashFileInfo] :+: FilesInfo[ModifiedFileInfo] :+: Seq[File] :+: File :+: Seq[ - String - ] :+: Int :+: HNil + FilesInfo[HashFileInfo] :+: FilesInfo[ModifiedFileInfo] :+: Seq[File] :+: File :+: + Seq[String] :+: Int :+: HNil val inputs : Inputs = hash(sources.toSet ++ optionFiles(options, fileInputOpts)) :+: lastModified( classpath.toSet From a0e27c719c20a5d6bafb176d3d932f2097115ac3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 26 Mar 2018 22:37:38 +0100 Subject: [PATCH 255/356] Cleanup Resolve --- .../src/main/scala/sbt/internal/Resolve.scala | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/main/src/main/scala/sbt/internal/Resolve.scala b/main/src/main/scala/sbt/internal/Resolve.scala index 7e3fe0676..761ba5dd3 100644 --- a/main/src/main/scala/sbt/internal/Resolve.scala +++ b/main/src/main/scala/sbt/internal/Resolve.scala @@ -15,19 +15,19 @@ object Resolve { index: BuildUtil[_], current: ScopeAxis[Reference], key: AttributeKey[_], - mask: ScopeMask + mask: ScopeMask, ): Scope => Scope = { - val rs = - resolveProject(current, mask) _ :: - resolveExtra(mask) _ :: - resolveTask(mask) _ :: - resolveConfig(index, key, mask) _ :: - Nil + val rs = ( + resolveProject(current, mask) _ + :: resolveExtra(mask) _ + :: resolveTask(mask) _ + :: resolveConfig(index, key, mask) _ + :: Nil + ) scope => - (scope /: rs) { (s, f) => - f(s) - } + rs.foldLeft(scope)((s, f) => f(s)) } + def resolveTask(mask: ScopeMask)(scope: Scope): Scope = if (mask.task) scope else scope.copy(task = Zero) @@ -41,17 +41,16 @@ object Resolve { else scope.copy(extra = Zero) def resolveConfig[P](index: BuildUtil[P], key: AttributeKey[_], mask: ScopeMask)( - scope: Scope + scope: Scope, ): Scope = if (mask.config) scope else { val (resolvedRef, proj) = scope.project match { + case Zero | This => (None, index.rootProject(index.root)) case Select(ref) => val r = index resolveRef ref (Some(r), index.projectFor(r)) - case Zero | This => - (None, index.rootProject(index.root)) } val task = scope.task.toOption val keyIndex = index.keyIndex From 8db585d62a889e31f5db490f6a04c2cf54982e3c Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 25 Apr 2018 14:12:17 +0100 Subject: [PATCH 256/356] Refer to sbt/website instead of "the website project" in CONTRIBUTING --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a715576a0..41367f772 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ Effective bug reports are more likely to be fixed. These guidelines explain how ### Notes about Documentation -Documentation fixes and contributions are as much welcome as to patching the core. Visit [the website project][documentation] to learn about how to contribute. +Documentation fixes and contributions are as much welcome as to patching the core. Visit [sbt/website][documentation] to learn about how to contribute. ### Preliminaries From 2f880c6b8fc8ffa5b04fffdc3ac3e145ee9a4c8d Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 25 Apr 2018 14:13:12 +0100 Subject: [PATCH 257/356] Merge support/contrib info into support/issues+prs sections of CONTRIBUTING --- CONTRIBUTING.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41367f772..7c38f19e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,12 +15,12 @@ Support [Lightbend] sponsors sbt and encourages contributions from the active community. Enterprises can adopt it for mission critical systems with confidence because Lightbend stands behind sbt with commercial support and services. -For community support please [ask] on StackOverflow with the tag "sbt". +For community support please [ask] on StackOverflow with the tag "sbt" (and the name of the sbt plugin(s) if any). - State the problem or question clearly and provide enough context. Code examples and `build.sbt` are often useful when appropriately edited. - There's also [Gitter sbt/sbt room][gitter], but Stackoverflow is recommended so others can benefit from the answers. -For professional support, [Lightbend], the maintainer of Scala compiler and sbt, provides: +For professional support, for instance if you need faster response times, [Lightbend], the maintainer of Scala compiler and sbt, provides: - [Lightbend Subscriptions][subscriptions], which includes Expert Support - Training @@ -47,6 +47,10 @@ When you find a bug in sbt we want to hear about it. Your bug reports play an im Effective bug reports are more likely to be fixed. These guidelines explain how to write such reports and pull requests. +Please open a GitHub issue when you are 90% sure it's an actual bug. + +If you have an enhancement idea, or a general discussion, bring it up to [sbt-contrib]. + ### Notes about Documentation Documentation fixes and contributions are as much welcome as to patching the core. Visit [sbt/website][documentation] to learn about how to contribute. @@ -58,14 +62,6 @@ Documentation fixes and contributions are as much welcome as to patching the cor - Open one case for each problem. - Proceed to the next steps for details. -### Where to get help and/or file a bug report - -sbt project uses GitHub Issues as a publicly visible todo list. Please open a GitHub issue when you are 90% sure it's an actual bug. - -- If you need help with sbt, please [ask] on StackOverflow with the tag "sbt" and the name of the sbt plugin if any. -- If you have an enhancement idea, or a general discussion, bring it up to [sbt-contrib]. -- If you need a faster response time, consider one of the [Lightbend subscriptions][subscriptions]. - ### What to report The developers need three things from you: **steps**, **problems**, and **expectations**. From f7d6cec030c22f552fb3982889f0dab35d45dd11 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 25 Apr 2018 14:21:24 +0100 Subject: [PATCH 258/356] Drop the references to the contributing guidelines from templates I believe GitHub does a better job displaying this prominently, particularly for first time contributors. We can always bring back the pull request template if we have new content. --- ISSUE_TEMPLATE.md | 2 -- PULL_REQUEST_TEMPLATE.md | 1 - 2 files changed, 3 deletions(-) delete mode 100644 PULL_REQUEST_TEMPLATE.md diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 3fab7acbb..30bd9fb9e 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,5 +1,3 @@ -(See the guidelines for contributing, linked above) - ## steps diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 0392fc0ee..000000000 --- a/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1 +0,0 @@ -(See the guidelines for contributing, linked above) From 2a12a7aeb952417866c46349f69b5af02ff22f23 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 25 Apr 2018 14:30:01 +0100 Subject: [PATCH 259/356] Fix section headers in CONTRIBUTING --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7c38f19e4..5e9393570 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,21 +66,21 @@ Documentation fixes and contributions are as much welcome as to patching the cor The developers need three things from you: **steps**, **problems**, and **expectations**. -### Steps +#### Steps The most important thing to remember about bug reporting is to clearly distinguish facts and opinions. What we need first is **the exact steps to reproduce your problems on our computers**. This is called *reproduction steps*, which is often shortened to "repro steps" or "steps." Describe your method of running sbt. Provide `build.sbt` that caused the problem and the version of sbt or Scala that was used. Provide sample Scala code if it's to do with incremental compilation. If possible, minimize the problem to reduce non-essential factors. Repro steps are the most important part of a bug report. If we cannot reproduce the problem in one way or the other, the problem can't be fixed. Telling us the error messages is not enough. -### Problems +#### Problems Next, describe the problems, or what *you think* is the problem. It might be "obvious" to you that it's a problem, but it could actually be an intentional behavior for some backward compatibility etc. For compilation errors, include the stack trace. The more raw info the better. -### Expectations +#### Expectations Same as the problems. Describe what *you think* should've happened. -### Notes +#### Notes Add an optional notes section to describe your analysis. From 701eda668baa9fba773533a4aec6611732691e5b Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 25 Apr 2018 14:30:11 +0100 Subject: [PATCH 260/356] typo in CONTRIBUTING --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5e9393570..773736be0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,7 +82,7 @@ Same as the problems. Describe what *you think* should've happened. #### Notes -Add an optional notes section to describe your analysis. +Add any optional notes section to describe your analysis. ### Subject From d7f66b0c00f8fa111e8ba91fc9326d85c40b08fc Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 25 Apr 2018 14:30:33 +0100 Subject: [PATCH 261/356] Shuffle a part of issue reporting in CONTRIBUTING --- CONTRIBUTING.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 773736be0..8aa09a581 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,9 +66,11 @@ Documentation fixes and contributions are as much welcome as to patching the cor The developers need three things from you: **steps**, **problems**, and **expectations**. +The most important thing to remember about bug reporting is to clearly distinguish facts and opinions. + #### Steps -The most important thing to remember about bug reporting is to clearly distinguish facts and opinions. What we need first is **the exact steps to reproduce your problems on our computers**. This is called *reproduction steps*, which is often shortened to "repro steps" or "steps." Describe your method of running sbt. Provide `build.sbt` that caused the problem and the version of sbt or Scala that was used. Provide sample Scala code if it's to do with incremental compilation. If possible, minimize the problem to reduce non-essential factors. +What we need first is **the exact steps to reproduce your problems on our computers**. This is called *reproduction steps*, which is often shortened to "repro steps" or "steps." Describe your method of running sbt. Provide `build.sbt` that caused the problem and the version of sbt or Scala that was used. Provide sample Scala code if it's to do with incremental compilation. If possible, minimize the problem to reduce non-essential factors. Repro steps are the most important part of a bug report. If we cannot reproduce the problem in one way or the other, the problem can't be fixed. Telling us the error messages is not enough. From 76a621b996e382972329accb52f75ed27e324492 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 25 Apr 2018 14:49:39 +0100 Subject: [PATCH 262/356] Not all PRs need notes --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8aa09a581..12195fb34 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -118,7 +118,7 @@ See below for the branch to work against. ### Adding notes -All pull requests are required to include a "Notes" file which documents the change. This file should reside in the +Most pull requests should include a "Notes" file which documents the change. This file should reside in the directory: From 2a4406717cbcb66a2f27a7aae5b524f99e17d2bb Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 25 Apr 2018 14:49:25 +0100 Subject: [PATCH 263/356] Move PROFILING out of CONTRIBUTING --- CONTRIBUTING.md | 151 +---------------------------------------------- PROFILING.md | 153 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 150 deletions(-) create mode 100644 PROFILING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 12195fb34..0961e1686 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -232,156 +232,7 @@ command. To run a single test, such as the test in Profiling sbt ------------- -There are several ways to profile sbt. The new hotness in profiling is FlameGraph. -You first collect stack trace samples, and then it is processed into svg graph. -See: - -- [Using FlameGraphs To Illuminate The JVM by Nitsan Wakart](https://www.youtube.com/watch?v=ugRrFdda_JQ) -- [USENIX ATC '17: Visualizing Performance with Flame Graphs](https://www.youtube.com/watch?v=D53T1Ejig1Q) - -### jvm-profiling-tools/async-profiler - -The first one I recommend is async-profiler. This is available for macOS and Linux, -and works fairly well. - -1. Download the installer from https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v1.2 -2. Make symbolic link to `build/` and `profiler.sh` to `$HOME/bin`, assuming you have PATH to `$HOME/bin`: - `ln -s ~/Applications/async-profiler/profiler.sh $HOME/bin/profiler.sh` - `ln -s ~/Applications/async-profiler/build $HOME/bin/build` - -Next, close all Java appliations and anything that may affect the profiling, and run sbt in one terminal: - -``` -$ sbt exit -``` - -In another terminal, run: - -``` -$ jps -92746 sbt-launch.jar -92780 Jps -``` - -This tells you the process ID of sbt. In this case, it's 92746. While it's running, run - -``` -$ profiler.sh -d 60 -Started [cpu] profiling ---- Execution profile --- -Total samples: 31602 -Non-Java: 3239 (10.25%) -GC active: 46 (0.15%) -Unknown (native): 14667 (46.41%) -Not walkable (native): 3 (0.01%) -Unknown (Java): 433 (1.37%) -Not walkable (Java): 8 (0.03%) -Thread exit: 1 (0.00%) -Deopt: 9 (0.03%) - -Frame buffer usage: 55.658% - -Total: 1932000000 (6.11%) samples: 1932 - [ 0] java.lang.ClassLoader$NativeLibrary.load - [ 1] java.lang.ClassLoader.loadLibrary0 - [ 2] java.lang.ClassLoader.loadLibrary - [ 3] java.lang.Runtime.loadLibrary0 - [ 4] java.lang.System.loadLibrary -.... -``` - -This should show a bunch of stacktraces that are useful. -To visualize this as a flamegraph, run: - -``` -$ profiler.sh -d 60 -f /tmp/flamegraph.svg -``` - -This should produce `/tmp/flamegraph.svg` at the end. - -![flamegraph](project/flamegraph_svg.png) - -See https://gist.github.com/eed3si9n/82d43acc95a002876d357bd8ad5f40d5 - -### running sbt with standby - -One of the tricky things you come across while profiling is figuring out the process ID, -while wnating to profile the beginning of the application. - -For this purpose, we've added `sbt.launcher.standby` JVM flag. -In the next version of sbt, you should be able to run: - -``` -$ sbt -J-Dsbt.launcher.standby=20s exit -``` - -This will count down for 20s before doing anything else. - -### jvm-profiling-tools/perf-map-agent - -If you want to try the mixed flamegraph, you can try perf-map-agent. -This uses `dtrace` on macOS and `perf` on Linux. - -You first have to compile https://github.com/jvm-profiling-tools/perf-map-agent. -For macOS, here to how to export `JAVA_HOME` before running `cmake .`: - -``` -$ export JAVA_HOME=$(/usr/libexec/java_home) -$ cmake . --- The C compiler identification is AppleClang 9.0.0.9000039 --- The CXX compiler identification is AppleClang 9.0.0.9000039 -... -$ make -``` - -In addition, you have to git clone https://github.com/brendangregg/FlameGraph - -In a fresh termimal, run sbt with `-XX:+PreserveFramePointer` flag: - -``` -$ sbt -J-Dsbt.launcher.standby=20s -J-XX:+PreserveFramePointer exit -``` - -In the terminal that you will run the perf-map: - -``` -$ cd quicktest/ -$ export JAVA_HOME=$(/usr/libexec/java_home) -$ export FLAMEGRAPH_DIR=$HOME/work/FlameGraph -$ jps -94592 Jps -94549 sbt-launch.jar -$ $HOME/work/perf-map-agent/bin/dtrace-java-flames 94549 -dtrace: system integrity protection is on, some features will not be available - -dtrace: description 'profile-99 ' matched 2 probes -Flame graph SVG written to DTRACE_FLAME_OUTPUT='/Users/xxx/work/quicktest/flamegraph-94549.svg'. -``` - -This would produce better flamegraph in theory, but the output looks too messy for `sbt exit` case. -See https://gist.github.com/eed3si9n/b5856ff3d987655513380d1a551aa0df -This might be because it assumes that the operations are already JITed. - -### ktoso/sbt-jmh - -https://github.com/ktoso/sbt-jmh - -Due to JIT warmup etc, benchmarking is difficult. JMH runs the same tests multiple times to -remove these effects and comes closer to measuring the performance of your code. - -There's also an integration with jvm-profiling-tools/async-profiler, apparently. - -### VisualVM - -I'd also mention traditional JVM profiling tool. Since VisualVM is opensource, -I'll mention this one: https://visualvm.github.io/ - -1. First VisualVM. -2. Start sbt from a terminal. -3. You should see `xsbt.boot.Boot` under Local. -4. Open it, and select either sampler or profiler, and hit CPU button at the point when you want to start. - -If you are familiar with YourKit, it also works similarly. +See [PROFILING](./PROFILING.md) Other notes for maintainers --------------------------- diff --git a/PROFILING.md b/PROFILING.md new file mode 100644 index 000000000..90f29a1b5 --- /dev/null +++ b/PROFILING.md @@ -0,0 +1,153 @@ +Profiling sbt +------------- + +There are several ways to profile sbt. The new hotness in profiling is FlameGraph. +You first collect stack trace samples, and then it is processed into svg graph. +See: + +- [Using FlameGraphs To Illuminate The JVM by Nitsan Wakart](https://www.youtube.com/watch?v=ugRrFdda_JQ) +- [USENIX ATC '17: Visualizing Performance with Flame Graphs](https://www.youtube.com/watch?v=D53T1Ejig1Q) + +### jvm-profiling-tools/async-profiler + +The first one I recommend is async-profiler. This is available for macOS and Linux, +and works fairly well. + +1. Download the installer from https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v1.2 +2. Make symbolic link to `build/` and `profiler.sh` to `$HOME/bin`, assuming you have PATH to `$HOME/bin`: + `ln -s ~/Applications/async-profiler/profiler.sh $HOME/bin/profiler.sh` + `ln -s ~/Applications/async-profiler/build $HOME/bin/build` + +Next, close all Java appliations and anything that may affect the profiling, and run sbt in one terminal: + +``` +$ sbt exit +``` + +In another terminal, run: + +``` +$ jps +92746 sbt-launch.jar +92780 Jps +``` + +This tells you the process ID of sbt. In this case, it's 92746. While it's running, run + +``` +$ profiler.sh -d 60 +Started [cpu] profiling +--- Execution profile --- +Total samples: 31602 +Non-Java: 3239 (10.25%) +GC active: 46 (0.15%) +Unknown (native): 14667 (46.41%) +Not walkable (native): 3 (0.01%) +Unknown (Java): 433 (1.37%) +Not walkable (Java): 8 (0.03%) +Thread exit: 1 (0.00%) +Deopt: 9 (0.03%) + +Frame buffer usage: 55.658% + +Total: 1932000000 (6.11%) samples: 1932 + [ 0] java.lang.ClassLoader$NativeLibrary.load + [ 1] java.lang.ClassLoader.loadLibrary0 + [ 2] java.lang.ClassLoader.loadLibrary + [ 3] java.lang.Runtime.loadLibrary0 + [ 4] java.lang.System.loadLibrary +.... +``` + +This should show a bunch of stacktraces that are useful. +To visualize this as a flamegraph, run: + +``` +$ profiler.sh -d 60 -f /tmp/flamegraph.svg +``` + +This should produce `/tmp/flamegraph.svg` at the end. + +![flamegraph](project/flamegraph_svg.png) + +See https://gist.github.com/eed3si9n/82d43acc95a002876d357bd8ad5f40d5 + +### running sbt with standby + +One of the tricky things you come across while profiling is figuring out the process ID, +while wnating to profile the beginning of the application. + +For this purpose, we've added `sbt.launcher.standby` JVM flag. +In the next version of sbt, you should be able to run: + +``` +$ sbt -J-Dsbt.launcher.standby=20s exit +``` + +This will count down for 20s before doing anything else. + +### jvm-profiling-tools/perf-map-agent + +If you want to try the mixed flamegraph, you can try perf-map-agent. +This uses `dtrace` on macOS and `perf` on Linux. + +You first have to compile https://github.com/jvm-profiling-tools/perf-map-agent. +For macOS, here to how to export `JAVA_HOME` before running `cmake .`: + +``` +$ export JAVA_HOME=$(/usr/libexec/java_home) +$ cmake . +-- The C compiler identification is AppleClang 9.0.0.9000039 +-- The CXX compiler identification is AppleClang 9.0.0.9000039 +... +$ make +``` + +In addition, you have to git clone https://github.com/brendangregg/FlameGraph + +In a fresh termimal, run sbt with `-XX:+PreserveFramePointer` flag: + +``` +$ sbt -J-Dsbt.launcher.standby=20s -J-XX:+PreserveFramePointer exit +``` + +In the terminal that you will run the perf-map: + +``` +$ cd quicktest/ +$ export JAVA_HOME=$(/usr/libexec/java_home) +$ export FLAMEGRAPH_DIR=$HOME/work/FlameGraph +$ jps +94592 Jps +94549 sbt-launch.jar +$ $HOME/work/perf-map-agent/bin/dtrace-java-flames 94549 +dtrace: system integrity protection is on, some features will not be available + +dtrace: description 'profile-99 ' matched 2 probes +Flame graph SVG written to DTRACE_FLAME_OUTPUT='/Users/xxx/work/quicktest/flamegraph-94549.svg'. +``` + +This would produce better flamegraph in theory, but the output looks too messy for `sbt exit` case. +See https://gist.github.com/eed3si9n/b5856ff3d987655513380d1a551aa0df +This might be because it assumes that the operations are already JITed. + +### ktoso/sbt-jmh + +https://github.com/ktoso/sbt-jmh + +Due to JIT warmup etc, benchmarking is difficult. JMH runs the same tests multiple times to +remove these effects and comes closer to measuring the performance of your code. + +There's also an integration with jvm-profiling-tools/async-profiler, apparently. + +### VisualVM + +I'd also mention traditional JVM profiling tool. Since VisualVM is opensource, +I'll mention this one: https://visualvm.github.io/ + +1. First VisualVM. +2. Start sbt from a terminal. +3. You should see `xsbt.boot.Boot` under Local. +4. Open it, and select either sampler or profiler, and hit CPU button at the point when you want to start. + +If you are familiar with YourKit, it also works similarly. From 702ff1f51655a90e41f23cd269759fe7ce4e4cbc Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 25 Apr 2018 14:51:19 +0100 Subject: [PATCH 264/356] Tweak Publishing VS Code Extensions --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0961e1686..b3d051950 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -239,8 +239,7 @@ Other notes for maintainers ### Publishing VS Code Extensions - -https://code.visualstudio.com/docs/extensions/publish-extension +Reference https://code.visualstudio.com/docs/extensions/publish-extension ``` $ sbt From 932ee5dc6aaf03f758974497e11b88008245e19a Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 25 Apr 2018 14:55:09 +0100 Subject: [PATCH 265/356] Split support into SUPPORT.md See https://help.github.com/articles/adding-support-resources-to-your-project/ --- CONTRIBUTING.md | 25 +++---------------------- SUPPORT.md | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 22 deletions(-) create mode 100644 SUPPORT.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b3d051950..9fd26b301 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,33 +1,14 @@ [StackOverflow]: http://stackoverflow.com/tags/sbt - [ask]: https://stackoverflow.com/questions/ask?tags=sbt [Setup]: http://www.scala-sbt.org/release/docs/Getting-Started/Setup [Issues]: https://github.com/sbt/sbt/issues - [sbt-dev]: https://groups.google.com/d/forum/sbt-dev [sbt-contrib]: https://gitter.im/sbt/sbt-contrib - [Lightbend]: https://www.lightbend.com/ - [subscriptions]: https://www.lightbend.com/platform/subscription [327]: https://github.com/sbt/sbt/issues/327 - [gitter]: https://gitter.im/sbt/sbt [documentation]: https://github.com/sbt/website -Support -======= +Contributing +============ -[Lightbend] sponsors sbt and encourages contributions from the active community. Enterprises can adopt it for mission critical systems with confidence because Lightbend stands behind sbt with commercial support and services. - -For community support please [ask] on StackOverflow with the tag "sbt" (and the name of the sbt plugin(s) if any). - -- State the problem or question clearly and provide enough context. Code examples and `build.sbt` are often useful when appropriately edited. -- There's also [Gitter sbt/sbt room][gitter], but Stackoverflow is recommended so others can benefit from the answers. - -For professional support, for instance if you need faster response times, [Lightbend], the maintainer of Scala compiler and sbt, provides: - -- [Lightbend Subscriptions][subscriptions], which includes Expert Support -- Training -- Consulting - -How to contribute to sbt -======================== +(For support, see [SUPPORT](./SUPPORT.md)) There are lots of ways to contribute to sbt ecosystem depending on your interests and skill level. diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 000000000..c3c7d0c44 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,20 @@ + [ask]: https://stackoverflow.com/questions/ask?tags=sbt + [Lightbend]: https://www.lightbend.com/ + [subscriptions]: https://www.lightbend.com/platform/subscription + [gitter]: https://gitter.im/sbt/sbt + +Support +======= + +[Lightbend] sponsors sbt and encourages contributions from the active community. Enterprises can adopt it for mission critical systems with confidence because Lightbend stands behind sbt with commercial support and services. + +For community support please [ask] on StackOverflow with the tag "sbt" (and the name of the sbt plugin(s) if any). + +- State the problem or question clearly and provide enough context. Code examples and `build.sbt` are often useful when appropriately edited. +- There's also [Gitter sbt/sbt room][gitter], but Stackoverflow is recommended so others can benefit from the answers. + +For professional support, for instance if you need faster response times, [Lightbend], the maintainer of Scala compiler and sbt, provides: + +- [Lightbend Subscriptions][subscriptions], which includes Expert Support +- Training +- Consulting From 8f76c48e634848515697e8710f37a8c8996eb643 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 25 Apr 2018 15:08:29 +0100 Subject: [PATCH 266/356] Document sbtOn in CONTRIBUTING.md --- CONTRIBUTING.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9fd26b301..7d94c648f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -183,6 +183,39 @@ When you run a locally built sbt, the JAR artifacts will be now cached under `$H One drawback of `-SNAPSHOT` version is that it's slow to resolve as it tries to hit all the resolvers. You can workaround that by using a version name like `1.$MINOR.$PATCH-LOCAL1`. A non-SNAPSHOT artifacts will now be cached under `$HOME/.ivy/cache/` directory, so you need to clear that out using [sbt-dirty-money](https://github.com/sbt/sbt-dirty-money)'s `cleanCache` task. +### Running sbt "from source" - `sbtOn` + +In addition to locally publishing a build of sbt, there is an alternative, experimental launcher within sbt/sbt +to be able to run sbt "from source", that is to compile sbt and run it from its resulting classfiles rather than +from published jar files. + +Such a launcher is available within sbt/sbt's build through a custom `sbtOn` command that takes as its first +argument the directory on which you want to run sbt, and the remaining arguments are passed _to_ that sbt +instance. For example: + +I have setup a minimal sbt build in the directory `/s/t`, to run sbt on that directory I call: + +```bash +> sbtOn /s/t +[info] Packaging /d/sbt/scripted/sbt/target/scala-2.12/scripted-sbt_2.12-1.2.0-SNAPSHOT.jar ... +[info] Done packaging. +[info] Running (fork) sbt.RunFromSourceMain /s/t +Listening for transport dt_socket at address: 5005 +[info] Loading settings from idea.sbt,global-plugins.sbt ... +[info] Loading global plugins from /Users/dnw/.dotfiles/.sbt/1.0/plugins +[info] Loading project definition from /s/t/project +[info] Set current project to t (in build file:/s/t/) +[info] sbt server started at local:///Users/dnw/.sbt/1.0/server/ce9baa494c7598e4d59b/sock +> show baseDirectory +[info] /s/t +> exit +[info] shutting down server +[success] Total time: 19 s, completed 25-Apr-2018 15:04:58 +``` + +Please note that this alternative launcher does _not_ have feature parity with sbt/launcher. (Meta) +contributions welcome! :-D + ### Diagnosing build failures Globally included plugins can interfere building `sbt`; if you are getting errors building sbt, try disabling all globally included plugins and try again. From 952858e68b7debc67e216c2a17445d25659c268a Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 25 Apr 2018 17:03:50 +0100 Subject: [PATCH 267/356] Restore references to CONTRIBUTING, with checkbox --- ISSUE_TEMPLATE.md | 2 ++ PULL_REQUEST_TEMPLATE.md | 1 + 2 files changed, 3 insertions(+) create mode 100644 PULL_REQUEST_TEMPLATE.md diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 30bd9fb9e..c5d873e6a 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,3 +1,5 @@ +- [ ] I've read the [CONTRIBUTING](https://github.com/sbt/sbt/blob/1.x/CONTRIBUTING.md) guidelines + ## steps diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..47947ca33 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1 @@ +- [ ] I've read the [CONTRIBUTING](https://github.com/sbt/sbt/blob/1.x/CONTRIBUTING.md) guidelines From c2c46a60fe710ba2b0212859a92ac0eb103d5705 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 25 Apr 2018 19:19:20 +0100 Subject: [PATCH 268/356] Document nightlies in CONTRIBUTING --- CONTRIBUTING.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d94c648f..04bf63037 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -177,6 +177,57 @@ $ sbt > compile ``` +### Using Jenkins sbt-snapshots nighties + +There is a Jenkins instance for sbt that every night builds and publishes (if successful) a timestamped version +of sbt to http://jenkins.scala-sbt.org/sbt-snapshots and is available for 4-5 weeks. To use it do the following: + +1. Set the `sbt.version` in `project/build.properties` + +```bash +echo "sbt.version=1.2.0-bin-20180423T192044" > project/build.properties +``` + +2. Create an sbt repositories file (`./repositories`) that includes that Maven repository: + +```properties +[repositories] + local + local-preloaded-ivy: file:///${sbt.preloaded-${sbt.global.base-${user.home}/.sbt}/preloaded/}, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext] + local-preloaded: file:///${sbt.preloaded-${sbt.global.base-${user.home}/.sbt}/preloaded/} + maven-central + sbt-maven-releases: https://repo.scala-sbt.org/scalasbt/maven-releases/, bootOnly + sbt-maven-snapshots: https://repo.scala-sbt.org/scalasbt/maven-snapshots/, bootOnly + typesafe-ivy-releases: https://repo.typesafe.com/typesafe/ivy-releases/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly + sbt-ivy-snapshots: https://repo.scala-sbt.org/scalasbt/ivy-snapshots/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly + sbt-snapshots: https://jenkins.scala-sbt.org/sbt-snapshots +``` + +3. Start sbt with a stable launcher and the custom repositories file: + +```bash +$ sbt -sbt-jar ~/.sbt/launchers/1.1.4/sbt-launch.jar -Dsbt.repository.config=repositories +Getting org.scala-sbt sbt 1.2.0-bin-20180423T192044 (this may take some time)... +downloading https://jenkins.scala-sbt.org/sbt-snapshots/org/scala-sbt/sbt/1.2.0-bin-20180423T192044/sbt-1.2.0-bin-20180423T192044.jar ... + [SUCCESSFUL ] org.scala-sbt#sbt;1.2.0-bin-20180423T192044!sbt.jar (139ms) +... +[info] sbt server started at local:///Users/dnw/.sbt/1.0/server/936e0f52ed9baf6b6d83/sock +> show sbtVersion +[info] 1.2.0-bin-20180423T192044 +``` + +### Using Jenkins maven-snapshots nightlies + +As an alternative you can request a build that publishes to https://repo.scala-sbt.org/scalasbt/maven-snapshots +and stays there forever by: + +1. Logging into https://jenkins.scala-sbt.org/job/sbt-validator/ +2. Clicking "Build with Parameters" +3. Making sure `deploy_to_bintray` is enabled +4. Hitting "Build" + +Afterwhich start sbt with a stable launcher: `sbt -sbt-jar ~/.sbt/launchers/1.1.4/sbt-launch.jar` + ### Clearing out boot and local cache When you run a locally built sbt, the JAR artifacts will be now cached under `$HOME/.sbt/boot/scala-2.12.4/org.scala-sbt/sbt/1.$MINOR.$PATCH-SNAPSHOT` directory. To clear this out run: `reboot dev` command from sbt's session of your test application. From 334789c919eb3c9fed2670fb26e3a53e7e2dc6dc Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 25 Apr 2018 16:15:19 -0400 Subject: [PATCH 269/356] 1.1.5-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index c1e10290a..33480b08c 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def buildLevelSettings: Seq[Setting[_]] = inThisBuild( Seq( organization := "org.scala-sbt", - version := "1.1.4-SNAPSHOT", + version := "1.1.5-SNAPSHOT", description := "sbt is an interactive build tool", bintrayOrganization := Some("sbt"), bintrayRepository := { From a798c4adffb66b631c6965bf53a999037f05323b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 25 Apr 2018 16:55:26 -0400 Subject: [PATCH 270/356] Re-fix console and JLine bug Fixes #3482 take 3 There are two bugs related REPL and JLine. 1. JLine getting disabled (up arrow not working) on the second run of `console` task. 2. Typed characters not showing up even on the `console` task. The first issue was fixed by #4054 which added `t.init`. When I noticed the second problem, I fixed it in #4082 (adds `t.restore`) but undid the first fix. This attempts to fix both the issues. --- main-actions/src/main/scala/sbt/Console.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main-actions/src/main/scala/sbt/Console.scala b/main-actions/src/main/scala/sbt/Console.scala index 61bfb9e47..9bc20d676 100644 --- a/main-actions/src/main/scala/sbt/Console.scala +++ b/main-actions/src/main/scala/sbt/Console.scala @@ -41,7 +41,8 @@ final class Console(compiler: AnalyzingCompiler) { implicit log: Logger): Try[Unit] = { def console0() = compiler.console(classpath, options, initialCommands, cleanupCommands, log)(loader, bindings) - JLine.usingTerminal { _ => + JLine.usingTerminal { t => + t.init Run.executeTrapExit(console0, log) } } From 06d7be0365cc087a079f47fc236f77ea6eaab00c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 26 Apr 2018 04:44:12 -0400 Subject: [PATCH 271/356] handle X / y --- .../main/scala/sbt/std/TaskLinterDSL.scala | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala b/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala index 1c47216fa..98e26d4fe 100644 --- a/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala +++ b/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala @@ -24,9 +24,6 @@ abstract class BaseTaskLinterDSL extends LinterDSL { val isTask = convert.asPredicate(ctx) class traverser extends Traverser { private val unchecked = symbolOf[sbt.sbtUnchecked].asClass - private val taskKeyType = typeOf[sbt.TaskKey[_]] - private val settingKeyType = typeOf[sbt.SettingKey[_]] - private val inputKeyType = typeOf[sbt.InputKey[_]] private val initializeType = typeOf[sbt.Def.Initialize[_]] private val uncheckedWrappers = MutableSet.empty[Tree] var insideIf: Boolean = false @@ -57,8 +54,8 @@ abstract class BaseTaskLinterDSL extends LinterDSL { } } - @inline def isKey(tpe: Type): Boolean = - tpe <:< initializeType || tpe <:< taskKeyType || tpe <:< settingKeyType || tpe <:< inputKeyType + @inline def isKey(tpe: Type): Boolean = isInitialize(tpe) + @inline def isInitialize(tpe: Type): Boolean = tpe <:< initializeType def detectAndErrorOnKeyMissingValue(i: Ident): Unit = { if (isKey(i.tpe)) { @@ -74,6 +71,13 @@ abstract class BaseTaskLinterDSL extends LinterDSL { } else () } + def detectAndErrorOnKeyMissingValue(a: Apply): Unit = { + if (isInitialize(a.tpe)) { + val expr = "X / y" + ctx.error(a.pos, TaskLinterDSLFeedback.missingValueForInitialize(expr)) + } else () + } + override def traverse(tree: ctx.universe.Tree): Unit = { tree match { case ap @ Apply(TypeApply(Select(_, nme), tpe :: Nil), qual :: Nil) => @@ -128,11 +132,13 @@ abstract class BaseTaskLinterDSL extends LinterDSL { rhs match { case i: Ident => detectAndErrorOnKeyMissingValue(i) case s: Select => detectAndErrorOnKeyMissingValue(s) + case a: Apply => detectAndErrorOnKeyMissingValue(a) case _ => () } case i: Ident => detectAndErrorOnKeyMissingValue(i) case s: Select => detectAndErrorOnKeyMissingValue(s) - case t => () + case a: Apply => detectAndErrorOnKeyMissingValue(a) + case _ => () } } traverseTrees(stmts) @@ -171,14 +177,13 @@ object TaskLinterDSLFeedback { private final val startGreen = if (ConsoleAppender.formatEnabledInEnv) AnsiColor.GREEN else "" private final val reset = if (ConsoleAppender.formatEnabledInEnv) AnsiColor.RESET else "" - private final val ProblemHeader = s"${startRed}Problem${reset}" - private final val SolutionHeader = s"${startGreen}Solution${reset}" + private final val ProblemHeader = s"${startRed}problem${reset}" + private final val SolutionHeader = s"${startGreen}solution${reset}" def useOfValueInsideAnon(task: String) = s"""${startBold}The evaluation of `$task` inside an anonymous function is prohibited.$reset | |${ProblemHeader}: Task invocations inside anonymous functions are evaluated independently of whether the anonymous function is invoked or not. - | |${SolutionHeader}: | 1. Make `$task` evaluation explicit outside of the function body if you don't care about its evaluation. | 2. Use a dynamic task to evaluate `$task` and pass that value as a parameter to an anonymous function. @@ -189,7 +194,6 @@ object TaskLinterDSLFeedback { | |${ProblemHeader}: `$task` is inside the if expression of a regular task. | Regular tasks always evaluate task inside the bodies of if expressions. - | |${SolutionHeader}: | 1. If you only want to evaluate it when the if predicate is true or false, use a dynamic task. | 2. Otherwise, make the static evaluation explicit by evaluating `$task` outside the if expression. @@ -198,8 +202,14 @@ object TaskLinterDSLFeedback { def missingValueForKey(key: String) = s"""${startBold}The key `$key` is not being invoked inside the task definition.$reset | - |${ProblemHeader}: Keys missing `.value` are not initialized and their dependency is not registered. - | + |${ProblemHeader}: Keys missing `.value` are not initialized and their dependency is not registered. |${SolutionHeader}: Replace `$key` by `$key.value` or remove it if unused. """.stripMargin + + def missingValueForInitialize(expr: String) = + s"""${startBold}The setting/task `$expr` is not being invoked inside the task definition.$reset + | + |${ProblemHeader}: Settings/tasks missing `.value` are not initialized and their dependency is not registered. + |${SolutionHeader}: Replace `$expr` by `($expr).value` or remove it if unused. + """.stripMargin } From d90f2734207acfd4dd0b8f3e86e0bf986673916a Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 26 Apr 2018 05:02:21 -0400 Subject: [PATCH 272/356] Remove deprecated commands -, --, and --- https://github.com/sbt/sbt/commit/86ae3c8c59df546dadb8c62901bac50c175af78a deprecated -, --, and ---. This removes the deprecated commands. --- main-command/src/main/scala/sbt/BasicCommands.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index e9d5b8a19..38e7fb663 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -56,7 +56,7 @@ object BasicCommands { client, read, alias - ) ++ compatCommands + ) def nop: Command = Command.custom(s => success(() => s)) def ignore: Command = Command.command(FailureWall)(idFun) From 3a4bc8dc0ba833a6d093b9bdb9b39494ab17d055 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 26 Apr 2018 14:03:35 +0100 Subject: [PATCH 273/356] Dropped deprecated "compat" commands & strings Fixes #4089 --- build.sbt | 5 +++++ .../main/scala/sbt/BasicCommandStrings.scala | 14 -------------- .../src/main/scala/sbt/BasicCommands.scala | 19 ------------------- main-command/src/main/scala/sbt/State.scala | 6 +----- 4 files changed, 6 insertions(+), 38 deletions(-) diff --git a/build.sbt b/build.sbt index 1c24cf775..39fa79c84 100644 --- a/build.sbt +++ b/build.sbt @@ -428,6 +428,11 @@ lazy val commandProj = (project in file("main-command")) contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats, mimaSettings, mimaBinaryIssueFilters ++= Vector( + // dropped private[sbt] method + exclude[DirectMissingMethodProblem]("sbt.BasicCommands.compatCommands"), + // dropped mainly internal command strings holder + exclude[MissingClassProblem]("sbt.BasicCommandStrings$Compat$"), + exclude[DirectMissingMethodProblem]("sbt.BasicCommands.rebootOptionParser"), // Changed the signature of Server method. nacho cheese. exclude[DirectMissingMethodProblem]("sbt.internal.server.Server.*"), diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index 6baee9509..b819f77a5 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -187,20 +187,6 @@ $AliasCommand name= def StashOnFailure = "sbtStashOnFailure" def PopOnFailure = "sbtPopOnFailure" - // commands with poor choices for names since they clash with the usual conventions for command line options - // these are not documented and are mainly internal commands and can be removed without a full deprecation cycle - object Compat { - def OnFailure = "-" - def ClearOnFailure = "--" - def FailureWall = "---" - def OnFailureDeprecated = deprecatedAlias(OnFailure, BasicCommandStrings.OnFailure) - def ClearOnFailureDeprecated = - deprecatedAlias(ClearOnFailure, BasicCommandStrings.ClearOnFailure) - def FailureWallDeprecated = deprecatedAlias(FailureWall, BasicCommandStrings.FailureWall) - private[this] def deprecatedAlias(oldName: String, newName: String): String = - s"The `$oldName` command is deprecated in favor of `$newName` and will be removed in a later version" - } - def FailureWall = "resumeFromFailure" def ClearOnFailure = "sbtClearOnFailure" diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index 38e7fb663..439821a6d 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -152,25 +152,6 @@ object BasicCommands { (s, arg) => s.copy(onFailure = Some(Exec(arg, s.source))) ) - private[sbt] def compatCommands = Seq( - Command.command(Compat.ClearOnFailure) { s => - s.log.warn(Compat.ClearOnFailureDeprecated) - s.copy(onFailure = None) - }, - Command.arb( - s => - token(Compat.OnFailure, hide = const(true)) - .flatMap(_ => otherCommandParser(s)) - ) { (s, arg) => - s.log.warn(Compat.OnFailureDeprecated) - s.copy(onFailure = Some(Exec(arg, s.source))) - }, - Command.command(Compat.FailureWall) { s => - s.log.warn(Compat.FailureWallDeprecated) - s - } - ) - def clearOnFailure: Command = Command.command(ClearOnFailure)(s => s.copy(onFailure = None)) def stashOnFailure: Command = diff --git a/main-command/src/main/scala/sbt/State.scala b/main-command/src/main/scala/sbt/State.scala index e65e58a03..f79b9d70d 100644 --- a/main-command/src/main/scala/sbt/State.scala +++ b/main-command/src/main/scala/sbt/State.scala @@ -285,11 +285,7 @@ object State { def log = s.globalLogging.full def handleError(t: Throwable): State = handleException(t, s, log) def fail = { - import BasicCommandStrings.Compat.{ FailureWall => CompatFailureWall } - val remaining = - s.remainingCommands.dropWhile( - c => c.commandLine != FailureWall && c.commandLine != CompatFailureWall - ) + val remaining = s.remainingCommands.dropWhile(c => c.commandLine != FailureWall) if (remaining.isEmpty) applyOnFailure(s, Nil, exit(ok = false)) else From 0de8345e33a40964edbb5420a0fd03a5a37e5bcb Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Fri, 13 Apr 2018 14:11:07 -0700 Subject: [PATCH 274/356] Remove watch loops When source generators write into the unmanaged source directory, bad things can happen. Continuous builds will loop indefinitely and compiling will fail because the generated sources get added to the source list twice, causing the incremental compiler to complain about compiling classes it has already seen. My two-pronged solution is to de-duplicate the sources task and to filter out managed source files in watch sources. The drawback to the latter is that it causes the source generation task to be executed twice per compile. --- main/src/main/scala/sbt/Defaults.scala | 15 +++++++++++++-- sbt/src/sbt-test/tests/watch-loop/build.sbt | 17 +++++++++++++++++ .../watch-loop/project/SourceWrapper.scala | 6 ++++++ .../tests/watch-loop/src/main/scala/Bar.scala | 1 + .../tests/watch-loop/src/main/scala/Foo.scala | 1 + sbt/src/sbt-test/tests/watch-loop/test | 1 + 6 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 sbt/src/sbt-test/tests/watch-loop/build.sbt create mode 100644 sbt/src/sbt-test/tests/watch-loop/project/SourceWrapper.scala create mode 100644 sbt/src/sbt-test/tests/watch-loop/src/main/scala/Bar.scala create mode 100644 sbt/src/sbt-test/tests/watch-loop/src/main/scala/Foo.scala create mode 100644 sbt/src/sbt-test/tests/watch-loop/test diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 82af2a9e4..847d95ee2 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -329,7 +329,18 @@ object Defaults extends BuildCommon { val baseDir = baseDirectory.value val bases = unmanagedSourceDirectories.value val include = (includeFilter in unmanagedSources).value - val exclude = (excludeFilter in unmanagedSources).value + val exclude = (excludeFilter in unmanagedSources).value match { + case e => + (managedSources in ThisScope).value match { + case l if l.nonEmpty => + e || new FileFilter { + private val files = l.toSet + override def accept(pathname: File): Boolean = files.contains(pathname) + override def toString = s"ManagedSourcesFilter($files)" + } + case _ => e + } + } val baseSources = if (sourcesInBase.value) Seq(new Source(baseDir, include, exclude, recursive = false)) else Nil @@ -341,7 +352,7 @@ object Defaults extends BuildCommon { sourceDirectories := Classpaths .concatSettings(unmanagedSourceDirectories, managedSourceDirectories) .value, - sources := Classpaths.concat(unmanagedSources, managedSources).value + sources := Classpaths.concatDistinct(unmanagedSources, managedSources).value ) lazy val resourceConfigPaths = Seq( resourceDirectory := sourceDirectory.value / "resources", diff --git a/sbt/src/sbt-test/tests/watch-loop/build.sbt b/sbt/src/sbt-test/tests/watch-loop/build.sbt new file mode 100644 index 000000000..effea2c79 --- /dev/null +++ b/sbt/src/sbt-test/tests/watch-loop/build.sbt @@ -0,0 +1,17 @@ +import java.nio.file.Files + +lazy val watchLoopTest = taskKey[Unit]("Check that managed sources are filtered") + +sourceGenerators in Compile += Def.task { + val path = baseDirectory.value.toPath.resolve("src/main/scala/Foo.scala") + Files.write(path, "object Foo".getBytes).toFile :: Nil +} + +watchLoopTest := { + val watched = watchSources.value + val managedSource = (managedSources in Compile).value.head + assert(!SourceWrapper.accept(watched, managedSource)) + assert((sources in Compile).value.foldLeft((true, Set.empty[File])) { + case ((res, set), f) => (res && !set.contains(f), set + f) + }._1) +} diff --git a/sbt/src/sbt-test/tests/watch-loop/project/SourceWrapper.scala b/sbt/src/sbt-test/tests/watch-loop/project/SourceWrapper.scala new file mode 100644 index 000000000..ff351168a --- /dev/null +++ b/sbt/src/sbt-test/tests/watch-loop/project/SourceWrapper.scala @@ -0,0 +1,6 @@ +package sbt + +object SourceWrapper { + def accept(sources: Seq[sbt.internal.io.Source], file: File): Boolean = + sources.exists(_.accept(file.toPath)) +} diff --git a/sbt/src/sbt-test/tests/watch-loop/src/main/scala/Bar.scala b/sbt/src/sbt-test/tests/watch-loop/src/main/scala/Bar.scala new file mode 100644 index 000000000..3b43dece6 --- /dev/null +++ b/sbt/src/sbt-test/tests/watch-loop/src/main/scala/Bar.scala @@ -0,0 +1 @@ +object Bar diff --git a/sbt/src/sbt-test/tests/watch-loop/src/main/scala/Foo.scala b/sbt/src/sbt-test/tests/watch-loop/src/main/scala/Foo.scala new file mode 100644 index 000000000..d37c10456 --- /dev/null +++ b/sbt/src/sbt-test/tests/watch-loop/src/main/scala/Foo.scala @@ -0,0 +1 @@ +object Foo \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/watch-loop/test b/sbt/src/sbt-test/tests/watch-loop/test new file mode 100644 index 000000000..41d621982 --- /dev/null +++ b/sbt/src/sbt-test/tests/watch-loop/test @@ -0,0 +1 @@ +> watchLoopTest From e72696480956ce7a078a036c06eb6fea138351e6 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 27 Apr 2018 19:59:56 -0400 Subject: [PATCH 275/356] Bump npm modules for VS Code extension --- vscode-sbt-scala/client/package-lock.json | 1033 ++++++++++++--------- vscode-sbt-scala/client/package.json | 5 +- vscode-sbt-scala/package-lock.json | 12 +- vscode-sbt-scala/package.json | 4 +- vscode-sbt-scala/server/package-lock.json | 56 +- 5 files changed, 625 insertions(+), 485 deletions(-) diff --git a/vscode-sbt-scala/client/package-lock.json b/vscode-sbt-scala/client/package-lock.json index c20e59f73..7b49514a7 100644 --- a/vscode-sbt-scala/client/package-lock.json +++ b/vscode-sbt-scala/client/package-lock.json @@ -1,16 +1,42 @@ { "name": "vscode-sbt-scala", - "version": "0.0.2", + "version": "0.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { "co": "4.6.0", - "json-stable-stringify": "1.0.1" + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "requires": { + "ansi-wrap": "0.1.0" } }, "ansi-regex": { @@ -23,12 +49,18 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" + }, "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", "requires": { - "arr-flatten": "1.1.0" + "arr-flatten": "1.1.0", + "array-slice": "0.2.3" } }, "arr-flatten": { @@ -36,11 +68,21 @@ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=" + }, "array-differ": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=" + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -85,9 +127,9 @@ "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" }, "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" }, "balanced-match": { "version": "1.0.0", @@ -122,12 +164,19 @@ "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", "requires": { "hoek": "2.16.3" + }, + "dependencies": { + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + } } }, "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -153,6 +202,11 @@ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" }, + "buffer-from": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", + "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==" + }, "caseless": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", @@ -171,9 +225,9 @@ } }, "clone": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", - "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=" + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=" }, "clone-buffer": { "version": "1.0.0", @@ -186,13 +240,13 @@ "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=" }, "cloneable-readable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.0.0.tgz", - "integrity": "sha1-pikNQT8hemEjL5XkWP84QYz7ARc=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz", + "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", "requires": { "inherits": "2.0.3", - "process-nextick-args": "1.0.7", - "through2": "2.0.3" + "process-nextick-args": "2.0.0", + "readable-stream": "2.3.6" } }, "co": { @@ -200,18 +254,23 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { "delayed-stream": "1.0.0" } }, "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" }, "concat-map": { "version": "0.0.1", @@ -219,9 +278,9 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "convert-source-map": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", - "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=" }, "core-util-is": { "version": "1.0.2", @@ -252,14 +311,14 @@ } }, "dateformat": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.0.0.tgz", - "integrity": "sha1-J0Pjq7XD/CRi5SfcpEXgTp9N7hc=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" }, "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { "ms": "2.0.0" } @@ -278,9 +337,9 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "diff": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=" + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==" }, "duplexer": { "version": "0.1.1", @@ -319,13 +378,13 @@ } }, "duplexify": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.1.tgz", - "integrity": "sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.4.tgz", + "integrity": "sha512-JzYSLYMhoVVBe8+mbHQ4KgpvHpm0DZpJuL8PY93Vyv1fW7jYJ90LoXa1di/CVbJM+TgMs91rbDapE/RNIfnJsA==", "requires": { - "end-of-stream": "1.4.0", + "end-of-stream": "1.4.1", "inherits": "2.0.3", - "readable-stream": "2.3.3", + "readable-stream": "2.3.6", "stream-shift": "1.0.0" } }, @@ -339,9 +398,9 @@ } }, "end-of-stream": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", - "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "requires": { "once": "1.4.0" } @@ -387,11 +446,11 @@ "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", "requires": { - "is-extendable": "0.1.1" + "kind-of": "1.1.0" } }, "extglob": { @@ -415,14 +474,25 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fancy-log": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.0.tgz", - "integrity": "sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", + "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", "requires": { - "chalk": "1.1.3", + "ansi-gray": "0.1.1", + "color-support": "1.1.3", "time-stamp": "1.1.0" } }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, "fd-slicer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", @@ -477,8 +547,8 @@ "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", "requires": { "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" + "combined-stream": "1.0.6", + "mime-types": "2.1.18" } }, "from": { @@ -644,9 +714,9 @@ } }, "glogg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", - "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.1.tgz", + "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==", "requires": { "sparkles": "1.0.0" } @@ -656,15 +726,10 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" - }, "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=" + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==" }, "gulp-chmod": { "version": "2.0.0", @@ -677,29 +742,24 @@ } }, "gulp-filter": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-5.0.1.tgz", - "integrity": "sha512-5olRzAhFdXB2klCu1lnazP65aO9YdA/5WfC9VdInIc8PrUeDIoZfaA3Edb0yUBGhVdHv4eHKL9Fg5tUoEJ9z5A==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-5.1.0.tgz", + "integrity": "sha1-oF4Rr/sHz33PQafeHLe2OsN4PnM=", "requires": { - "gulp-util": "3.0.8", "multimatch": "2.1.0", - "streamfilter": "1.0.5" + "plugin-error": "0.1.2", + "streamfilter": "1.0.7" } }, "gulp-gunzip": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/gulp-gunzip/-/gulp-gunzip-0.0.3.tgz", - "integrity": "sha1-e24HsPWP09QlFcSOrVpj3wVy9i8=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulp-gunzip/-/gulp-gunzip-1.0.0.tgz", + "integrity": "sha1-FbdBFF6Dqcb1CIYkG1fMWHHxUak=", "requires": { "through2": "0.6.5", "vinyl": "0.4.6" }, "dependencies": { - "clone": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", - "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=" - }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -729,15 +789,6 @@ "readable-stream": "1.0.34", "xtend": "4.0.1" } - }, - "vinyl": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", - "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", - "requires": { - "clone": "0.2.0", - "clone-stats": "0.0.1" - } } } }, @@ -753,25 +804,25 @@ "vinyl": "2.0.2" }, "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, "clone-stats": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=" }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" - }, "request": { "version": "2.79.0", "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", "requires": { "aws-sign2": "0.6.0", - "aws4": "1.6.0", + "aws4": "1.7.0", "caseless": "0.11.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", "form-data": "2.1.4", @@ -781,13 +832,13 @@ "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", + "mime-types": "2.1.18", "oauth-sign": "0.8.2", "qs": "6.3.2", "stringstream": "0.0.5", - "tough-cookie": "2.3.2", + "tough-cookie": "2.3.4", "tunnel-agent": "0.4.3", - "uuid": "3.1.0" + "uuid": "3.2.1" } }, "vinyl": { @@ -795,10 +846,10 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.0.2.tgz", "integrity": "sha1-CjcT2NTpIhxY8QyhbAEWyeJe2nw=", "requires": { - "clone": "1.0.2", + "clone": "1.0.4", "clone-buffer": "1.0.0", "clone-stats": "1.0.0", - "cloneable-readable": "1.0.0", + "cloneable-readable": "1.1.2", "is-stream": "1.1.0", "remove-trailing-separator": "1.1.0", "replace-ext": "1.0.0" @@ -811,19 +862,29 @@ "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz", "integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=", "requires": { - "convert-source-map": "1.5.0", + "convert-source-map": "1.5.1", "graceful-fs": "4.1.11", "strip-bom": "2.0.0", "through2": "2.0.3", "vinyl": "1.2.0" }, "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" + }, "vinyl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "requires": { - "clone": "1.0.2", + "clone": "1.0.4", "clone-stats": "0.0.1", "replace-ext": "0.0.1" } @@ -862,8 +923,8 @@ "array-uniq": "1.0.3", "beeper": "1.1.1", "chalk": "1.1.3", - "dateformat": "2.0.0", - "fancy-log": "1.3.0", + "dateformat": "2.2.0", + "fancy-log": "1.3.2", "gulplog": "1.0.0", "has-gulplog": "0.1.0", "lodash._reescape": "3.0.0", @@ -876,64 +937,83 @@ "replace-ext": "0.0.1", "through2": "2.0.3", "vinyl": "0.5.3" - } - }, - "gulp-vinyl-zip": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/gulp-vinyl-zip/-/gulp-vinyl-zip-1.4.0.tgz", - "integrity": "sha1-VjgvLMtXIxuwR4x4c3zNVylzvuE=", - "requires": { - "event-stream": "3.3.4", - "queue": "3.1.0", - "through2": "0.6.5", - "vinyl": "0.4.6", - "vinyl-fs": "2.4.4", - "yauzl": "2.8.0", - "yazl": "2.4.2" }, "dependencies": { "clone": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", - "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, - "isarray": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" + }, + "replace-ext": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" + "clone": "1.0.4", + "clone-stats": "0.0.1", + "replace-ext": "0.0.1" } + } + } + }, + "gulp-vinyl-zip": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gulp-vinyl-zip/-/gulp-vinyl-zip-2.1.0.tgz", + "integrity": "sha1-JOQGhdwFtxSZlSRQmeBZAmO+ja0=", + "requires": { + "event-stream": "3.3.4", + "queue": "4.4.2", + "through2": "2.0.3", + "vinyl": "2.1.0", + "vinyl-fs": "2.4.4", + "yauzl": "2.9.1", + "yazl": "2.4.3" + }, + "dependencies": { + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=" }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=" }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "queue": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-4.4.2.tgz", + "integrity": "sha512-fSMRXbwhMwipcDZ08enW2vl+YDmAmhcNcr43sCJL8DIg+CFOsoRLG23ctxA+fwNk1w55SePSiS7oqQQSgQoVJQ==", "requires": { - "readable-stream": "1.0.34", - "xtend": "4.0.1" + "inherits": "2.0.3" } }, "vinyl": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", - "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", + "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", "requires": { - "clone": "0.2.0", - "clone-stats": "0.0.1" + "clone": "2.1.1", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.2", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" } } } @@ -943,13 +1023,13 @@ "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", "requires": { - "glogg": "1.0.0" + "glogg": "1.0.1" } }, "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "2.0.6", @@ -957,8 +1037,8 @@ "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", "requires": { "chalk": "1.1.3", - "commander": "2.11.0", - "is-my-json-valid": "2.16.1", + "commander": "2.15.1", + "is-my-json-valid": "2.17.2", "pinkie-promise": "2.0.1" } }, @@ -971,9 +1051,9 @@ } }, "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" }, "has-gulplog": { "version": "0.1.0", @@ -992,6 +1072,13 @@ "cryptiles": "2.0.5", "hoek": "2.16.3", "sntp": "1.0.9" + }, + "dependencies": { + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + } } }, "he": { @@ -1000,9 +1087,9 @@ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" }, "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" }, "http-signature": { "version": "1.1.1", @@ -1011,7 +1098,7 @@ "requires": { "assert-plus": "0.2.0", "jsprim": "1.4.1", - "sshpk": "1.13.1" + "sshpk": "1.14.1" } }, "inflight": { @@ -1034,9 +1121,9 @@ "integrity": "sha1-0Kwq1V63sL7JJqUmb2xmKqqD3KU=" }, "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-dotfile": { "version": "1.0.3", @@ -1069,13 +1156,19 @@ "is-extglob": "2.1.1" } }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==" + }, "is-my-json-valid": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz", - "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==", + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", + "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", "requires": { "generate-function": "2.0.0", "generate-object-property": "1.2.0", + "is-my-ip-valid": "1.0.0", "jsonpointer": "4.0.1", "xtend": "4.0.1" } @@ -1086,6 +1179,16 @@ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "requires": { "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } } }, "is-obj": { @@ -1157,6 +1260,11 @@ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, "json-stable-stringify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", @@ -1170,11 +1278,6 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=" - }, "jsonify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", @@ -1204,28 +1307,16 @@ } }, "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "1.1.5" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=" }, "lazystream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "requires": { - "readable-stream": "2.3.3" - } - }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "requires": { - "lodash._basecopy": "3.0.1", - "lodash.keys": "3.1.2" + "readable-stream": "2.3.6" } }, "lodash._basecopy": { @@ -1233,11 +1324,6 @@ "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" }, - "lodash._basecreate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=" - }, "lodash._basetostring": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", @@ -1278,16 +1364,6 @@ "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" }, - "lodash.create": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", - "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "requires": { - "lodash._baseassign": "3.2.0", - "lodash._basecreate": "3.0.3", - "lodash._isiterateecall": "3.0.9" - } - }, "lodash.escape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", @@ -1361,7 +1437,7 @@ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", "requires": { - "readable-stream": "2.3.3" + "readable-stream": "2.3.6" } }, "micromatch": { @@ -1384,6 +1460,14 @@ "regex-cache": "0.4.4" }, "dependencies": { + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "requires": { + "arr-flatten": "1.1.0" + } + }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", @@ -1396,20 +1480,28 @@ "requires": { "is-extglob": "1.0.0" } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } } } }, "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" }, "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "requires": { - "mime-db": "1.30.0" + "mime-db": "1.33.0" } }, "minimatch": { @@ -1417,13 +1509,13 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "1.1.8" + "brace-expansion": "1.1.11" } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mkdirp": { "version": "0.5.1", @@ -1431,61 +1523,36 @@ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } } }, "mocha": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", - "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", + "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", "requires": { "browser-stdout": "1.3.0", - "commander": "2.9.0", - "debug": "2.6.8", - "diff": "3.2.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", "escape-string-regexp": "1.0.5", - "glob": "7.1.1", - "growl": "1.9.2", + "glob": "7.1.2", + "growl": "1.10.3", "he": "1.1.1", - "json3": "3.3.2", - "lodash.create": "3.1.1", "mkdirp": "0.5.1", - "supports-color": "3.1.2" + "supports-color": "4.4.0" }, "dependencies": { "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "requires": { - "graceful-readlink": "1.0.1" - } - }, - "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" }, "supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", "requires": { - "has-flag": "1.0.0" + "has-flag": "2.0.0" } } } @@ -1536,9 +1603,9 @@ "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" }, "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object.omit": { "version": "2.0.1", @@ -1563,7 +1630,7 @@ "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", "requires": { "is-stream": "1.1.0", - "readable-stream": "2.3.3" + "readable-stream": "2.3.6" } }, "parse-glob": { @@ -1616,9 +1683,9 @@ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pinkie": { "version": "2.0.4", @@ -1633,15 +1700,27 @@ "pinkie": "2.0.4" } }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "requires": { + "ansi-cyan": "0.1.1", + "ansi-red": "0.1.1", + "arr-diff": "1.1.0", + "arr-union": "2.1.0", + "extend-shallow": "1.1.4" + } + }, "preserve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" }, "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "punycode": { "version": "1.4.1", @@ -1654,9 +1733,9 @@ "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=" }, "querystringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz", - "integrity": "sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", + "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==" }, "queue": { "version": "3.1.0", @@ -1688,7 +1767,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } @@ -1698,22 +1777,22 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", "util-deprecate": "1.0.2" } }, @@ -1741,64 +1820,144 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" }, "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "version": "2.85.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", + "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", + "aws-sign2": "0.7.0", + "aws4": "1.7.0", "caseless": "0.12.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", + "mime-types": "2.1.18", "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.1.1", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.2", "stringstream": "0.0.5", - "tough-cookie": "2.3.2", + "tough-cookie": "2.3.4", "tunnel-agent": "0.6.0", - "uuid": "3.1.0" + "uuid": "3.2.1" }, "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "requires": { + "hoek": "4.2.1" + } + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "requires": { + "hoek": "4.2.1" + } + } + } + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" + } + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" } }, "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "requires": { + "hoek": "4.2.1" + } }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" } } } @@ -1817,14 +1976,14 @@ } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" }, "sntp": { "version": "1.0.9", @@ -1832,19 +1991,27 @@ "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", "requires": { "hoek": "2.16.3" + }, + "dependencies": { + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + } } }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.5.tgz", + "integrity": "sha512-mR7/Nd5l1z6g99010shcXJiNEaf3fEtmLhRB/sBcQVJGodcHCULPp2y4Sfa43Kv2zq7T+Izmfp/WHCR6dYkQCA==", "requires": { - "source-map": "0.5.7" + "buffer-from": "1.0.0", + "source-map": "0.6.1" } }, "sparkles": { @@ -1861,9 +2028,9 @@ } }, "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", + "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", "requires": { "asn1": "0.2.3", "assert-plus": "1.0.0", @@ -1901,11 +2068,11 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" }, "streamfilter": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-1.0.5.tgz", - "integrity": "sha1-h1BxEb644phFFxe1Ec/tjwAqv1M=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-1.0.7.tgz", + "integrity": "sha512-Gk6KZM+yNA1JpW0KzlZIhjo3EaBJDkYfXtYSbOwNIQ7Zd6006E6+sCFlW1NDvFG/vnXhKmw6TJJgiEQg/8lXfQ==", "requires": { - "readable-stream": "2.3.3" + "readable-stream": "2.3.6" } }, "streamifier": { @@ -1914,11 +2081,11 @@ "integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=" }, "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" } }, "stringstream": { @@ -1976,7 +2143,7 @@ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "requires": { - "readable-stream": "2.3.3", + "readable-stream": "2.3.6", "xtend": "4.0.1" } }, @@ -2000,12 +2167,22 @@ "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=", "requires": { "extend-shallow": "2.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } } }, "tough-cookie": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "requires": { "punycode": "1.4.1" } @@ -2031,11 +2208,11 @@ } }, "url-parse": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.1.9.tgz", - "integrity": "sha1-xn8dd11R8KGJEd17P/rSe7nlvRk=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.0.tgz", + "integrity": "sha512-ERuGxDiQ6Xw/agN4tuoCRbmwRuZP0cJ1lJxJubXr5Q/5cDa78+Dc4wfvtxzhzhkm5VvmW6Mf8EVj9SPGN4l8Lg==", "requires": { - "querystringify": "1.0.0", + "querystringify": "2.0.0", "requires-port": "1.0.0" } }, @@ -2045,9 +2222,9 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" }, "vali-date": { "version": "1.0.0", @@ -2072,13 +2249,12 @@ } }, "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "requires": { - "clone": "1.0.2", - "clone-stats": "0.0.1", - "replace-ext": "0.0.1" + "clone": "0.2.0", + "clone-stats": "0.0.1" } }, "vinyl-fs": { @@ -2086,7 +2262,7 @@ "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", "requires": { - "duplexify": "3.5.1", + "duplexify": "3.5.4", "glob-stream": "5.3.5", "graceful-fs": "4.1.11", "gulp-sourcemaps": "1.6.0", @@ -2096,7 +2272,7 @@ "merge-stream": "1.0.1", "mkdirp": "0.5.1", "object-assign": "4.1.1", - "readable-stream": "2.3.3", + "readable-stream": "2.3.6", "strip-bom": "2.0.0", "strip-bom-stream": "1.0.0", "through2": "2.0.3", @@ -2105,17 +2281,22 @@ "vinyl": "1.2.0" }, "dependencies": { - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" }, "vinyl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "requires": { - "clone": "1.0.2", + "clone": "1.0.4", "clone-stats": "0.0.1", "replace-ext": "0.0.1" } @@ -2123,108 +2304,64 @@ } }, "vinyl-source-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-source-stream/-/vinyl-source-stream-1.1.0.tgz", - "integrity": "sha1-RMvlEIIFJ53rDFZTwJSiiHk4sas=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vinyl-source-stream/-/vinyl-source-stream-1.1.2.tgz", + "integrity": "sha1-YrU6E1YQqJbpjKlr7jqH8Aio54A=", "requires": { - "through2": "0.6.5", + "through2": "2.0.3", "vinyl": "0.4.6" - }, - "dependencies": { - "clone": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", - "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "requires": { - "readable-stream": "1.0.34", - "xtend": "4.0.1" - } - }, - "vinyl": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", - "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", - "requires": { - "clone": "0.2.0", - "clone-stats": "0.0.1" - } - } } }, "vscode": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.5.tgz", - "integrity": "sha1-EOsQQAGEDD3QgTgV/UoF+PyILRQ=", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.16.tgz", + "integrity": "sha512-nNsfYANLqqNfa2+flHyrIrqSIumIoV7R+R9H3gn0SAzZL3RGPEy8db3EYjXKPW5NEfssj/HZhSMI3J3pMNmhPA==", "requires": { "glob": "7.1.2", "gulp-chmod": "2.0.0", - "gulp-filter": "5.0.1", - "gulp-gunzip": "0.0.3", + "gulp-filter": "5.1.0", + "gulp-gunzip": "1.0.0", "gulp-remote-src": "0.4.3", "gulp-symdest": "1.1.0", "gulp-untar": "0.0.6", - "gulp-vinyl-zip": "1.4.0", - "mocha": "3.5.3", - "request": "2.81.0", - "semver": "5.4.1", - "source-map-support": "0.4.18", - "url-parse": "1.1.9", - "vinyl-source-stream": "1.1.0" + "gulp-vinyl-zip": "2.1.0", + "mocha": "4.1.0", + "request": "2.85.0", + "semver": "5.5.0", + "source-map-support": "0.5.5", + "url-parse": "1.4.0", + "vinyl-source-stream": "1.1.2" } }, - "vscode-jsonrpc": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.4.0.tgz", - "integrity": "sha1-qpWsWDvzHYD3JdV8J8CfTCz+n6k=" - }, "vscode-languageclient": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.4.2.tgz", - "integrity": "sha1-LtbuoHJVC6q8m9MkgP8CeHVZiXI=", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz", + "integrity": "sha512-GTQ+hSq/o4c/y6GYmyP9XNrVoIu0NFZ67KltSkqN+tO0eUNDIlrVNX+3DJzzyLhSsrctuGzuYWm3t87mNAcBmQ==", "requires": { - "vscode-languageserver-protocol": "3.4.1" + "vscode-languageserver-protocol": "3.5.1" + }, + "dependencies": { + "vscode-jsonrpc": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz", + "integrity": "sha1-hyOdnhZrLXNSJFuKgTWXgEwdY6o=" + }, + "vscode-languageserver-protocol": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz", + "integrity": "sha512-1fPDIwsAv1difCV+8daOrJEGunClNJWqnUHq/ncWrjhitKWXgGmRCjlwZ3gDUTt54yRcvXz1PXJDaRNvNH6pYA==", + "requires": { + "vscode-jsonrpc": "3.5.0", + "vscode-languageserver-types": "3.5.0" + } + }, + "vscode-languageserver-types": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz", + "integrity": "sha1-5I15li8LjgLelV4/UkkI4rGcA3Q=" + } } }, - "vscode-languageserver-protocol": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.4.1.tgz", - "integrity": "sha1-lrfIo+1opOvTCkB7BCenY3k1/h8=", - "requires": { - "vscode-jsonrpc": "3.4.0", - "vscode-languageserver-types": "3.4.0" - } - }, - "vscode-languageserver-types": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.4.0.tgz", - "integrity": "sha1-UEOuR+5KwWrwe7PQylYSNeDA0vo=" - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -2236,18 +2373,18 @@ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, "yauzl": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.8.0.tgz", - "integrity": "sha1-eUUK/yKyqcWkHvVOAtuQfM+/nuI=", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha1-qBmB6nCleUYTOIPwKcWCGok1mn8=", "requires": { "buffer-crc32": "0.2.13", "fd-slicer": "1.0.1" } }, "yazl": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.4.2.tgz", - "integrity": "sha1-FMsZCD4eJacAksFYiqvg9OTdTYg=", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.4.3.tgz", + "integrity": "sha1-7CblzIfVYBud+EMtvdPNLlFzoHE=", "requires": { "buffer-crc32": "0.2.13" } diff --git a/vscode-sbt-scala/client/package.json b/vscode-sbt-scala/client/package.json index c8d8a4472..1e729fc32 100644 --- a/vscode-sbt-scala/client/package.json +++ b/vscode-sbt-scala/client/package.json @@ -52,7 +52,8 @@ "postinstall": "node ./node_modules/vscode/bin/install" }, "dependencies": { - "vscode": "^1.1.5", - "vscode-languageclient": "^3.4.2" + "vscode": "^1.1.16", + "vscode-languageclient": "^3.4.2", + "hoek": "^4.2.1" } } diff --git a/vscode-sbt-scala/package-lock.json b/vscode-sbt-scala/package-lock.json index 5b8616285..71296d214 100644 --- a/vscode-sbt-scala/package-lock.json +++ b/vscode-sbt-scala/package-lock.json @@ -11,15 +11,15 @@ "dev": true }, "@types/node": { - "version": "6.0.88", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.88.tgz", - "integrity": "sha512-bYDPZTX0/s1aihdjLuAgogUAT5M+TpoWChEMea2p0yOcfn5bu3k6cJb9cp6nw268XeSNIGGr+4+/8V5K6BGzLQ==", + "version": "7.0.61", + "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.61.tgz", + "integrity": "sha512-X4MNN+Z36OmVPv7n08wxq46/t61/rauW4+xeyxGPueDQ9t7SetHnuEPS0p9n6wU/15HvJLGjzfLTc/RwN7id3A==", "dev": true }, "typescript": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.2.tgz", - "integrity": "sha1-A4qV99m7tCCxvzW6MdTFwd0//jQ=", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz", + "integrity": "sha512-K7g15Bb6Ra4lKf7Iq2l/I5/En+hLIHmxWZGq3D4DIRNFxMNV6j2SHSvDOqs2tGd4UvD/fJvrwopzQXjLrT7Itw==", "dev": true } } diff --git a/vscode-sbt-scala/package.json b/vscode-sbt-scala/package.json index e57bb7b9a..256dadcca 100644 --- a/vscode-sbt-scala/package.json +++ b/vscode-sbt-scala/package.json @@ -20,7 +20,7 @@ }, "devDependencies": { "@types/mocha": "^2.2.42", - "@types/node": "^6.0.88", - "typescript": "^2.5.2" + "@types/node": "^7.0.10", + "typescript": "^2.7.2" } } diff --git a/vscode-sbt-scala/server/package-lock.json b/vscode-sbt-scala/server/package-lock.json index d56b2fe0e..0b52e89e7 100644 --- a/vscode-sbt-scala/server/package-lock.json +++ b/vscode-sbt-scala/server/package-lock.json @@ -4,38 +4,40 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "vscode-jsonrpc": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.4.0.tgz", - "integrity": "sha1-qpWsWDvzHYD3JdV8J8CfTCz+n6k=" - }, "vscode-languageserver": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.4.2.tgz", - "integrity": "sha1-CMvlDuJpAdN91LXcUsJbkJNjwfE=", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.5.1.tgz", + "integrity": "sha512-RYUKn0DgHTFcS8kS4VaNCjNMaQXYqiXdN9bKrFjXzu5RPKfjIYcoh47oVWwZj4L3R/DPB0Se7HPaDatvYY2XgQ==", "requires": { - "vscode-languageserver-protocol": "3.4.1", - "vscode-uri": "1.0.1" + "vscode-languageserver-protocol": "3.5.1", + "vscode-uri": "1.0.3" + }, + "dependencies": { + "vscode-jsonrpc": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz", + "integrity": "sha1-hyOdnhZrLXNSJFuKgTWXgEwdY6o=" + }, + "vscode-languageserver-protocol": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz", + "integrity": "sha512-1fPDIwsAv1difCV+8daOrJEGunClNJWqnUHq/ncWrjhitKWXgGmRCjlwZ3gDUTt54yRcvXz1PXJDaRNvNH6pYA==", + "requires": { + "vscode-jsonrpc": "3.5.0", + "vscode-languageserver-types": "3.5.0" + } + }, + "vscode-languageserver-types": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz", + "integrity": "sha1-5I15li8LjgLelV4/UkkI4rGcA3Q=" + } } }, - "vscode-languageserver-protocol": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.4.1.tgz", - "integrity": "sha1-lrfIo+1opOvTCkB7BCenY3k1/h8=", - "requires": { - "vscode-jsonrpc": "3.4.0", - "vscode-languageserver-types": "3.4.0" - } - }, - "vscode-languageserver-types": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.4.0.tgz", - "integrity": "sha1-UEOuR+5KwWrwe7PQylYSNeDA0vo=" - }, "vscode-uri": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.1.tgz", - "integrity": "sha1-Eahr7+rDxKo+wIYjZRo8gabQu8g=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.3.tgz", + "integrity": "sha1-Yxvb9xbcyrDmUpGo3CXCMjIIWlI=" } } } From 4477a42b5af6273788ef9a386b93d3d967cfe4d9 Mon Sep 17 00:00:00 2001 From: Seth Tisue Date: Fri, 27 Apr 2018 19:07:08 +0200 Subject: [PATCH 276/356] upgrade Scala 2.12.4 -> 2.12.6 I have no specific user-visible benefit in mind other than fewer JARs to download for people who are on current versions of things --- CONTRIBUTING.md | 2 +- main/src/main/scala/sbt/PluginCross.scala | 2 +- project/Dependencies.scala | 2 +- project/plugins.sbt | 2 +- sbt/src/sbt-test/tests/fork-async/build.sbt | 2 +- sbt/src/test/scala/sbt/RunFromSourceMain.scala | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7dc91fcdd..f7356793c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -200,7 +200,7 @@ $ sbt ### Clearing out boot and local cache -When you run a locally built sbt, the JAR artifacts will be now cached under `$HOME/.sbt/boot/scala-2.12.4/org.scala-sbt/sbt/1.$MINOR.$PATCH-SNAPSHOT` directory. To clear this out run: `reboot dev` command from sbt's session of your test application. +When you run a locally built sbt, the JAR artifacts will be now cached under `$HOME/.sbt/boot/scala-2.12.6/org.scala-sbt/sbt/1.$MINOR.$PATCH-SNAPSHOT` directory. To clear this out run: `reboot dev` command from sbt's session of your test application. One drawback of `-SNAPSHOT` version is that it's slow to resolve as it tries to hit all the resolvers. You can workaround that by using a version name like `1.$MINOR.$PATCH-LOCAL1`. A non-SNAPSHOT artifacts will now be cached under `$HOME/.ivy/cache/` directory, so you need to clear that out using [sbt-dirty-money](https://github.com/sbt/sbt-dirty-money)'s `cleanCache` task. diff --git a/main/src/main/scala/sbt/PluginCross.scala b/main/src/main/scala/sbt/PluginCross.scala index 9828b42a7..91e823bbc 100644 --- a/main/src/main/scala/sbt/PluginCross.scala +++ b/main/src/main/scala/sbt/PluginCross.scala @@ -93,7 +93,7 @@ private[sbt] object PluginCross { VersionNumber(sv) match { case VersionNumber(Seq(0, 12, _*), _, _) => "2.9.2" case VersionNumber(Seq(0, 13, _*), _, _) => "2.10.7" - case VersionNumber(Seq(1, 0, _*), _, _) => "2.12.4" + case VersionNumber(Seq(1, 0, _*), _, _) => "2.12.6" case _ => sys.error(s"Unsupported sbt binary version: $sv") } } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index bcefc8b4e..b1e9fd0ab 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -4,7 +4,7 @@ import sbt.contraband.ContrabandPlugin.autoImport._ object Dependencies { // WARNING: Please Scala update versions in PluginCross.scala too - val scala212 = "2.12.5" + val scala212 = "2.12.6" val baseScalaVersion = scala212 // sbt modules diff --git a/project/plugins.sbt b/project/plugins.sbt index 68b01d14b..b13554d41 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -scalaVersion := "2.12.4" +scalaVersion := "2.12.6" scalacOptions ++= Seq("-feature", "-language:postfixOps") addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.6") diff --git a/sbt/src/sbt-test/tests/fork-async/build.sbt b/sbt/src/sbt-test/tests/fork-async/build.sbt index bb42b0c17..3ddfca63c 100644 --- a/sbt/src/sbt-test/tests/fork-async/build.sbt +++ b/sbt/src/sbt-test/tests/fork-async/build.sbt @@ -2,7 +2,7 @@ testFrameworks += new TestFramework("utest.runner.Framework") lazy val root = (project in file(".")). settings( - scalaVersion := "2.12.4", + scalaVersion := "2.12.6", libraryDependencies += "com.lihaoyi" %% "utest" % "0.6.4" % Test, fork in Test := true ) diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala index c0bd9cfc1..a625da9a8 100644 --- a/sbt/src/test/scala/sbt/RunFromSourceMain.scala +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -14,8 +14,8 @@ import buildinfo.TestBuildInfo import xsbti._ object RunFromSourceMain { - private val sbtVersion = "1.0.3" // "dev" - private val scalaVersion = "2.12.4" + private val sbtVersion = "1.1.0" // "dev" + private val scalaVersion = "2.12.6" def fork(workingDirectory: File): Try[Unit] = { val fo = ForkOptions() From ccd03eeba20b8479565789fda546b33257794823 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 27 Apr 2018 19:28:20 -0400 Subject: [PATCH 277/356] resolve Scala compiler using Ivy When Scala compiler is not found in the boot directory, use Ivy to resolve it. --- .../test/scala/sbt/RunFromSourceMain.scala | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala index a625da9a8..7212d570e 100644 --- a/sbt/src/test/scala/sbt/RunFromSourceMain.scala +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -67,9 +67,44 @@ object RunFromSourceMain { def apply[T](lockFile: File, run: java.util.concurrent.Callable[T]) = run.call() } + private lazy val bootDirectory: File = file(sys.props("user.home")) / ".sbt" / "boot" + private lazy val scalaHome: File = { + val scalaHome0 = bootDirectory / s"scala-$scalaVersion" + if (scalaHome0.exists) scalaHome0 + else { + val target = new File("target").getAbsoluteFile + val fakeboot = target / "fakeboot" + val scalaHome1 = fakeboot / s"scala-$scalaVersion" + val scalaHome1Lib = scalaHome1 / "lib" + val scalaHome1Temp = scalaHome1 / "temp" + if (scalaHome1Lib.exists) () + else { + IO.createDirectories(List(scalaHome1Lib, scalaHome1Temp)) + val log = sbt.util.LogExchange.logger("run-from-source") + val lm = { + import sbt.librarymanagement.ivy.IvyDependencyResolution + val ivyConfig = InlineIvyConfiguration().withLog(log) + IvyDependencyResolution(ivyConfig) + } + val Name = """(.*)(\-[\d|\.]+)\.jar""".r + val module = "org.scala-lang" % "scala-compiler" % scalaVersion + lm.retrieve(module, scalaModuleInfo = None, scalaHome1Temp, log) match { + case Right(_) => + (scalaHome1Temp ** "*.jar").get foreach { x => + val Name(head, _) = x.getName + IO.copyFile(x, scalaHome1Lib / (head + ".jar")) + } + case Left(w) => sys.error(w.toString) + } + } + scalaHome1 + } + } + private def getConf(baseDir: File, args: Seq[String]): AppConfiguration = new AppConfiguration { def baseDirectory = baseDir def arguments = args.toArray + def provider = new AppProvider { appProvider => def scalaProvider = new ScalaProvider { scalaProvider => def scalaOrg = "org.scala-lang" @@ -80,7 +115,7 @@ object RunFromSourceMain { def app(id: xsbti.ApplicationID, version: String) = appProvider def topLoader = new java.net.URLClassLoader(Array(), null) def globalLock = noGlobalLock - def bootDirectory = file(sys.props("user.home")) / ".sbt" / "boot" + def bootDirectory = RunFromSourceMain.bootDirectory def ivyRepositories = Array() def appRepositories = Array() def isOverrideRepositories = false @@ -88,11 +123,14 @@ object RunFromSourceMain { def checksums = Array("sha1", "md5") } def version = scalaVersion - def libDir: File = launcher.bootDirectory / s"scala-$version" / "lib" + lazy val libDir: File = RunFromSourceMain.scalaHome / "lib" def jar(name: String): File = libDir / s"$name.jar" - def libraryJar = jar("scala-library") - def compilerJar = jar("scala-compiler") - def jars = libDir.listFiles(f => !f.isDirectory && f.getName.endsWith(".jar")) + lazy val libraryJar = jar("scala-library") + lazy val compilerJar = jar("scala-compiler") + lazy val jars = { + assert(libDir.exists) + libDir.listFiles(f => !f.isDirectory && f.getName.endsWith(".jar")) + } def loader = new java.net.URLClassLoader(jars map (_.toURI.toURL), null) def app(id: xsbti.ApplicationID) = appProvider } From 4086f6c85b6466957ba29100e15c8e33ace1cf7e Mon Sep 17 00:00:00 2001 From: Robert Walker Date: Sat, 28 Apr 2018 19:44:15 -0400 Subject: [PATCH 278/356] start sbt in VS Code terminal window --- vscode-sbt-scala/client/src/extension.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/vscode-sbt-scala/client/src/extension.ts b/vscode-sbt-scala/client/src/extension.ts index 768e0fc03..c5d3a1282 100644 --- a/vscode-sbt-scala/client/src/extension.ts +++ b/vscode-sbt-scala/client/src/extension.ts @@ -3,10 +3,30 @@ import * as path from 'path'; let fs = require('fs'); +import * as vscode from 'vscode'; import { ExtensionContext, workspace } from 'vscode'; // workspace, import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient'; -export function activate(context: ExtensionContext) { +export async function activate(context: ExtensionContext) { + // Start sbt + const terminal = vscode.window.createTerminal(`sbt`); + terminal.show(); + terminal.sendText("sbt"); + + function delay(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + // Wait for SBT server to start + let retries = 30; + while (retries > 0) { + retries--; + await delay(1000); + if (fs.existsSync(path.join(workspace.rootPath, 'project', 'target', 'active.json'))) { + break; + } + } + // The server is implemented in node let serverModule = context.asAbsolutePath(path.join('server', 'server.js')); // The debug options for the server From c29b88ed5c5281e4061b3f4ee3224f36983cc27b Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sun, 29 Apr 2018 13:14:54 -0700 Subject: [PATCH 279/356] Minor grammar for contributing text. --- CONTRIBUTING.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7dc91fcdd..07f014c9a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,9 +31,10 @@ How to contribute to sbt There are lots of ways to contribute to sbt ecosystem depending on your interests and skill level. -- Help someone at work or online help their build problem. +- Help someone at work or online fix their build problem. - Answer StackOverflow questions. -- Create plugins that extends sbt's feature. +- Ask StackOverflow questions. +- Create plugins that extend sbt's features. - Maintain and update [documentation]. - Garden the issue tracker. - Report issues. From 136dcb16d96d537c81290d48994c3c4bcaee248a Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 30 Apr 2018 22:50:23 -0400 Subject: [PATCH 280/356] Fix VS Code extension startup The change contributed in https://github.com/sbt/sbt/pull/4130 to start sbt within VS Code looked ok at first, but when I published the extension and started using it, I realized that it's a bit broken. Basically there's no cleanup logic (that I could find), so simply closing VS Code would leave `project/target/active.json` behind, which lets VS Code extension to attempt connection, and it fails because there's no server running. To workaround this, I'll attempt to connect to the socket to confirm sbt server is up. --- vscode-sbt-scala/client/package.json | 2 +- vscode-sbt-scala/client/src/extension.ts | 73 ++++++++++++++++++++---- vscode-sbt-scala/server/src/server.ts | 36 ++++++------ 3 files changed, 83 insertions(+), 28 deletions(-) diff --git a/vscode-sbt-scala/client/package.json b/vscode-sbt-scala/client/package.json index 1e729fc32..f4316539e 100644 --- a/vscode-sbt-scala/client/package.json +++ b/vscode-sbt-scala/client/package.json @@ -1,7 +1,7 @@ { "name": "vscode-sbt-scala", "displayName": "Scala (sbt)", - "version": "0.1.0", + "version": "0.2.0", "author": "Lightbend, Inc.", "license": "BSD-3-Clause", "publisher": "lightbend", diff --git a/vscode-sbt-scala/client/src/extension.ts b/vscode-sbt-scala/client/src/extension.ts index c5d3a1282..ff8206a37 100644 --- a/vscode-sbt-scala/client/src/extension.ts +++ b/vscode-sbt-scala/client/src/extension.ts @@ -1,28 +1,39 @@ 'use strict'; import * as path from 'path'; - -let fs = require('fs'); +import * as url from 'url'; +import * as net from 'net'; +let fs = require('fs'), + os = require('os'); import * as vscode from 'vscode'; import { ExtensionContext, workspace } from 'vscode'; // workspace, import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient'; +let terminal: vscode.Terminal = null; + +function delay(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +export async function deactivate() { + if (terminal != null) { + terminal.sendText("exit"); + await delay(1000); + terminal.dispose(); + } +} + export async function activate(context: ExtensionContext) { // Start sbt - const terminal = vscode.window.createTerminal(`sbt`); + terminal = vscode.window.createTerminal(`sbt`); terminal.show(); terminal.sendText("sbt"); - - function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - // Wait for SBT server to start - let retries = 30; + let retries = 60; while (retries > 0) { retries--; await delay(1000); - if (fs.existsSync(path.join(workspace.rootPath, 'project', 'target', 'active.json'))) { + if (isServerUp()) { break; } } @@ -46,7 +57,47 @@ export async function activate(context: ExtensionContext) { return discoverToken(); } } - + + // Don't start VS Code connection until sbt server is confirmed to be up and running. + function isServerUp(): boolean { + let isFileThere = fs.existsSync(path.join(workspace.rootPath, 'project', 'target', 'active.json')); + if (!isFileThere) { + return false; + } else { + let skt = new net.Socket(); + try { + connectSocket(skt); + } catch(e) { + return false; + } + skt.end(); + return true; + } + } + + function connectSocket(socket: net.Socket):  net.Socket { + let u = discoverUrl(); + // let socket = net.Socket(); + if (u.protocol == 'tcp:') { + socket.connect(+u.port, '127.0.0.1'); + } else if (u.protocol == 'local:' && os.platform() == 'win32') { + let pipePath = '\\\\.\\pipe\\' + u.hostname; + socket.connect(pipePath); + } else if (u.protocol == 'local:') { + socket.connect(u.path); + } else { + throw 'Unknown protocol ' + u.protocol; + } + return socket; + } + + // the port file is hardcoded to a particular location relative to the build. + function discoverUrl(): url.Url { + let pf = path.join(process.cwd(), 'project', 'target', 'active.json'); + let portfile = JSON.parse(fs.readFileSync(pf)); + return url.parse(portfile.uri); + } + // the port file is hardcoded to a particular location relative to the build. function discoverToken(): any { let pf = path.join(workspace.rootPath, 'project', 'target', 'active.json'); diff --git a/vscode-sbt-scala/server/src/server.ts b/vscode-sbt-scala/server/src/server.ts index 05932d451..8404c7b74 100644 --- a/vscode-sbt-scala/server/src/server.ts +++ b/vscode-sbt-scala/server/src/server.ts @@ -2,32 +2,20 @@ import * as path from 'path'; import * as url from 'url'; -let net = require('net'), - fs = require('fs'), +import * as net from 'net'; +let fs = require('fs'), os = require('os'), stdin = process.stdin, stdout = process.stdout; -let u = discoverUrl(); - -let socket = net.Socket(); +let socket = new net.Socket(); socket.on('data', (chunk: any) => { // send it back to stdout stdout.write(chunk); }).on('end', () => { stdin.pause(); }); - -if (u.protocol == 'tcp:') { - socket.connect(u.port, '127.0.0.1'); -} else if (u.protocol == 'local:' && os.platform() == 'win32') { - let pipePath = '\\\\.\\pipe\\' + u.hostname; - socket.connect(pipePath); -} else if (u.protocol == 'local:') { - socket.connect(u.path); -} else { - throw 'Unknown protocol ' + u.protocol; -} +connectSocket(socket); stdin.resume(); stdin.on('data', (chunk: any) => { @@ -36,6 +24,22 @@ stdin.on('data', (chunk: any) => { socket.end(); }); +function connectSocket(socket: net.Socket): net.Socket { + let u = discoverUrl(); + // let socket = net.Socket(); + if (u.protocol == 'tcp:') { + socket.connect(+u.port, '127.0.0.1'); + } else if (u.protocol == 'local:' && os.platform() == 'win32') { + let pipePath = '\\\\.\\pipe\\' + u.hostname; + socket.connect(pipePath); + } else if (u.protocol == 'local:') { + socket.connect(u.path); + } else { + throw 'Unknown protocol ' + u.protocol; + } + return socket; +} + // the port file is hardcoded to a particular location relative to the build. function discoverUrl(): url.Url { let pf = path.join(process.cwd(), 'project', 'target', 'active.json'); From a2f70342dfd9dc0c65c18d9e19e0e06dd9839314 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 2 May 2018 01:09:30 -0400 Subject: [PATCH 281/356] Uncomment server customization --- sbt/src/server-test/handshake/build.sbt | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/sbt/src/server-test/handshake/build.sbt b/sbt/src/server-test/handshake/build.sbt index 9c4f59ef8..02f5f81fd 100644 --- a/sbt/src/server-test/handshake/build.sbt +++ b/sbt/src/server-test/handshake/build.sbt @@ -1,23 +1,23 @@ -// import sbt.internal.ServerHandler +import sbt.internal.server.{ ServerHandler, ServerIntent } lazy val root = (project in file(".")) .settings( Global / serverLog / logLevel := Level.Debug, // custom handler - // Global / serverHandlers += ServerHandler({ callback => - // import callback._ - // import sjsonnew.BasicJsonProtocol._ - // import sbt.internal.protocol.JsonRpcRequestMessage - // ServerIntent( - // { - // case r: JsonRpcRequestMessage if r.method == "lunar/helo" => - // jsonRpcNotify("lunar/oleh", "") - // () - // }, - // PartialFunction.empty - // ) - // }), + Global / serverHandlers += ServerHandler({ callback => + import callback._ + import sjsonnew.BasicJsonProtocol._ + import sbt.internal.protocol.JsonRpcRequestMessage + ServerIntent( + { + case r: JsonRpcRequestMessage if r.method == "lunar/helo" => + jsonRpcNotify("lunar/oleh", "") + () + }, + PartialFunction.empty + ) + }), name := "handshake", scalaVersion := "2.12.3", From 5d63035a273670e75984eb837292bd4a863ecab1 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Mon, 9 Apr 2018 10:15:32 +0200 Subject: [PATCH 282/356] Fix #4073: support detect dotty plugins --- main/src/main/scala/sbt/Defaults.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 82af2a9e4..6e99870fe 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2887,7 +2887,8 @@ object Classpaths { def autoPlugins(report: UpdateReport, internalPluginClasspath: Seq[File]): Seq[String] = { val pluginClasspath = report.matching(configurationFilter(CompilerPlugin.name)) ++ internalPluginClasspath - val plugins = sbt.internal.inc.classpath.ClasspathUtilities.compilerPlugins(pluginClasspath) + val version = scalaVersion.value + val plugins = sbt.internal.inc.classpath.ClasspathUtilities.compilerPlugins(pluginClasspath, scalaInstance.isDotty(version)) plugins.map("-Xplugin:" + _.getAbsolutePath).toSeq } From 9ad1b120c10471c654d03972e6250a57f6a8aefc Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Mon, 9 Apr 2018 10:51:58 +0200 Subject: [PATCH 283/356] add notes --- notes/1.1.1/dotty-plugin.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 notes/1.1.1/dotty-plugin.md diff --git a/notes/1.1.1/dotty-plugin.md b/notes/1.1.1/dotty-plugin.md new file mode 100644 index 000000000..6250c30dd --- /dev/null +++ b/notes/1.1.1/dotty-plugin.md @@ -0,0 +1,9 @@ +[@liufengyun]: https://github.com/liufengyun + +[4073]: https://github.com/sbt/sbt/issues/4073 +[4084]: https://github.com/sbt/sbt/pull/4084 + + +### Improvements + +- Detect dotty plugins which have descriptor file named `plugin.properties` instead of `scalac-plugin.xml`. [#4073][4073]/[#4084][4084] by [@liufengyun][@liufengyun] From 83212785b02060ee9907e31ee80dc44ce2307dac Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Mon, 9 Apr 2018 10:59:46 +0200 Subject: [PATCH 284/356] add dotty plugin test --- .../dotty-compiler-plugin/build.sbt | 19 +++++++++ .../plugin/DivideZero.scala | 40 +++++++++++++++++++ .../src/main/resources/plugin.properties | 1 + .../dotty-compiler-plugin/project/plugins.sbt | 1 + .../src/main/scala/hello/Hello.scala | 13 ++++++ .../dotty-compiler-plugin/test | 2 + 6 files changed, 76 insertions(+) create mode 100644 sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/build.sbt create mode 100644 sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/plugin/DivideZero.scala create mode 100644 sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/plugin/src/main/resources/plugin.properties create mode 100644 sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/project/plugins.sbt create mode 100644 sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/src/main/scala/hello/Hello.scala create mode 100644 sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/test diff --git a/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/build.sbt b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/build.sbt new file mode 100644 index 000000000..19d921ef1 --- /dev/null +++ b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/build.sbt @@ -0,0 +1,19 @@ +lazy val dottyVersion = dottyLatestNightlyBuild + +lazy val pluginSetting = Seq( + name := "dividezero", + version := "0.0.1", + organization := "ch.epfl.lamp", + scalaVersion := dottyVersion, + + libraryDependencies ++= Seq( + "ch.epfl.lamp" %% "dotty" % "provided" + ) +) + +lazy val plugin = (project in file("plugin")).settings(pluginSetting: _*) + +lazy val app = (project in file(".")).settings( + scalaVersion := dottyVersion, + libraryDependencies += compilerPlugin("ch.epfl.lamp" %% "dividezero" % "0.0.1") +) diff --git a/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/plugin/DivideZero.scala b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/plugin/DivideZero.scala new file mode 100644 index 000000000..1dfe23cf5 --- /dev/null +++ b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/plugin/DivideZero.scala @@ -0,0 +1,40 @@ +package dividezero + +import dotty.tools.dotc._ +import core._ +import Contexts.Context +import plugins._ +import Phases.Phase +import ast.tpd +import transform.MegaPhase.MiniPhase +import Decorators._ +import Symbols.Symbol +import Constants.Constant +import transform.{LinkAll, Pickler} + +class DivideZero extends PluginPhase with StandardPlugin { + val name: String = "divideZero" + override val description: String = "divide zero check" + + val phaseName = name + + override val runsAfter = Set(Pickler.phaseName) + override val runsBefore = Set(LinkAll.phaseName) + + override def init(options: List[String]): List[PluginPhase] = this :: Nil + + private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = { + def test(tpe: String): Boolean = + (sym.owner eq ctx.requiredClass(tpe.toTermName)) && sym.name.show == "/" + + test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.Float") || test("scala.Double") + } + + override def transformApply(tree: tpd.Apply)(implicit ctx: Context): tpd.Tree = tree match { + case tpd.Apply(fun, tpd.Literal(Constants.Constant(v)) :: Nil) if isNumericDivide(fun.symbol) && v == 0 => + ctx.warning("divide by zero", tree.pos) + tpd.Literal(Constant(0)) + case _ => + tree + } +} diff --git a/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/plugin/src/main/resources/plugin.properties b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/plugin/src/main/resources/plugin.properties new file mode 100644 index 000000000..db215842c --- /dev/null +++ b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/plugin/src/main/resources/plugin.properties @@ -0,0 +1 @@ +pluginClass=dividezero.DivideZero \ No newline at end of file diff --git a/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/project/plugins.sbt b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/project/plugins.sbt new file mode 100644 index 000000000..57022ad6b --- /dev/null +++ b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.1.7") diff --git a/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/src/main/scala/hello/Hello.scala b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/src/main/scala/hello/Hello.scala new file mode 100644 index 000000000..99ae2d0d7 --- /dev/null +++ b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/src/main/scala/hello/Hello.scala @@ -0,0 +1,13 @@ +package hello +object Hello { + def main(args: Array[String]): Unit = { + val dotty: Int | String = "dotty" + + val y = 5 / 0 // error + 100 + 6 / 0 // error + 6L / 0L // error + val z = 7 / 0.0 // error + + println(s"Hello $dotty!") + } +} diff --git a/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/test b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/test new file mode 100644 index 000000000..28c06d4d6 --- /dev/null +++ b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/test @@ -0,0 +1,2 @@ +> plugin/publishLocal +> app/run From 06cf5e56356bcded9f65dd162722a0c813592db1 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 13 Apr 2018 14:16:23 +0200 Subject: [PATCH 285/356] update sbt-dotty version --- .../compiler-project/dotty-compiler-plugin/project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/project/plugins.sbt b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/project/plugins.sbt index 57022ad6b..ba89aa2bb 100644 --- a/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/project/plugins.sbt +++ b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/project/plugins.sbt @@ -1 +1 @@ -addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.1.7") +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.2.0") From ecbfdfef25f20ec497448cf6bbbe6aa1fa55d13b Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 3 May 2018 22:36:17 +0200 Subject: [PATCH 286/356] fix compilation error --- main/src/main/scala/sbt/Defaults.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 6e99870fe..3aaf9858a 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2885,10 +2885,9 @@ object Classpaths { excl: FileFilter): Classpath = (base * (filter -- excl) +++ (base / config.name).descendantsExcept(filter, excl)).classpath - def autoPlugins(report: UpdateReport, internalPluginClasspath: Seq[File]): Seq[String] = { + def autoPlugins(report: UpdateReport, internalPluginClasspath: Seq[File], scalaVersion: String): Seq[String] = { val pluginClasspath = report.matching(configurationFilter(CompilerPlugin.name)) ++ internalPluginClasspath - val version = scalaVersion.value - val plugins = sbt.internal.inc.classpath.ClasspathUtilities.compilerPlugins(pluginClasspath, scalaInstance.isDotty(version)) + val plugins = sbt.internal.inc.classpath.ClasspathUtilities.compilerPlugins(pluginClasspath, ScalaInstance.isDotty(scalaVersion)) plugins.map("-Xplugin:" + _.getAbsolutePath).toSeq } @@ -2908,7 +2907,7 @@ object Classpaths { lazy val compilerPluginConfig = Seq( scalacOptions := { val options = scalacOptions.value - val newPlugins = autoPlugins(update.value, internalCompilerPluginClasspath.value.files) + val newPlugins = autoPlugins(update.value, internalCompilerPluginClasspath.value.files, scalaVersion.value) val existing = options.toSet if (autoCompilerPlugins.value) options ++ newPlugins.filterNot(existing) else options } From adf045d4f86225064215dd9a73abc3897b7ee2e2 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 May 2018 16:39:05 +0200 Subject: [PATCH 287/356] move notes to 1.1.5 --- notes/{1.1.1 => 1.1.5}/dotty-plugin.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename notes/{1.1.1 => 1.1.5}/dotty-plugin.md (100%) diff --git a/notes/1.1.1/dotty-plugin.md b/notes/1.1.5/dotty-plugin.md similarity index 100% rename from notes/1.1.1/dotty-plugin.md rename to notes/1.1.5/dotty-plugin.md From 50f2ebce8817a28bbba2522ddb7f7639b04a3400 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 May 2018 17:00:25 +0200 Subject: [PATCH 288/356] overload autoPlugins for binary compatibility --- main/src/main/scala/sbt/Defaults.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 3aaf9858a..fa65596fa 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2885,9 +2885,14 @@ object Classpaths { excl: FileFilter): Classpath = (base * (filter -- excl) +++ (base / config.name).descendantsExcept(filter, excl)).classpath - def autoPlugins(report: UpdateReport, internalPluginClasspath: Seq[File], scalaVersion: String): Seq[String] = { + + @deprecated("The method only works for Scalac, use the overloaded version to support both Scalac and Dotty", "1.1.5") + def autoPlugins(report: UpdateReport, internalPluginClasspath: Seq[File]): Seq[String] = + autoPlugins(report, internalPluginClasspath, isDotty = false) + + def autoPlugins(report: UpdateReport, internalPluginClasspath: Seq[File], isDotty: Boolean): Seq[String] = { val pluginClasspath = report.matching(configurationFilter(CompilerPlugin.name)) ++ internalPluginClasspath - val plugins = sbt.internal.inc.classpath.ClasspathUtilities.compilerPlugins(pluginClasspath, ScalaInstance.isDotty(scalaVersion)) + val plugins = sbt.internal.inc.classpath.ClasspathUtilities.compilerPlugins(pluginClasspath, isDotty) plugins.map("-Xplugin:" + _.getAbsolutePath).toSeq } @@ -2907,7 +2912,7 @@ object Classpaths { lazy val compilerPluginConfig = Seq( scalacOptions := { val options = scalacOptions.value - val newPlugins = autoPlugins(update.value, internalCompilerPluginClasspath.value.files, scalaVersion.value) + val newPlugins = autoPlugins(update.value, internalCompilerPluginClasspath.value.files, ScalaInstance.isDotty(scalaVersion.value)) val existing = options.toSet if (autoCompilerPlugins.value) options ++ newPlugins.filterNot(existing) else options } From 1057dcd2914af02924513661a74f85a4b1915053 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 May 2018 17:10:50 +0200 Subject: [PATCH 289/356] update deprecated message --- main/src/main/scala/sbt/Defaults.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index fa65596fa..ff93c1097 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2886,7 +2886,7 @@ object Classpaths { (base * (filter -- excl) +++ (base / config.name).descendantsExcept(filter, excl)).classpath - @deprecated("The method only works for Scalac, use the overloaded version to support both Scalac and Dotty", "1.1.5") + @deprecated("The method only works for Scala 2, use the overloaded version to support both Scala 2 and Scala 3", "1.1.5") def autoPlugins(report: UpdateReport, internalPluginClasspath: Seq[File]): Seq[String] = autoPlugins(report, internalPluginClasspath, isDotty = false) From 94a57dcf00cfe5b5ff628fc873d1b133fd4898cb Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 4 May 2018 18:08:15 -0400 Subject: [PATCH 290/356] Move fakeboot to home https://github.com/sbt/sbt/pull/4143 kept failing, and while I was debugging, I noticed that the target that it was downloading scala-compiler was in /tmp/... for the remote instance. This moves the fakeboot to a full path, which should make the build less susceptible to failure due to random network issues. --- project/build.properties | 2 +- sbt/src/test/scala/sbt/RunFromSourceMain.scala | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/project/build.properties b/project/build.properties index 05313438a..64cf32f7f 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.2 +sbt.version=1.1.4 diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala index 7212d570e..d70f02cb0 100644 --- a/sbt/src/test/scala/sbt/RunFromSourceMain.scala +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -69,18 +69,19 @@ object RunFromSourceMain { private lazy val bootDirectory: File = file(sys.props("user.home")) / ".sbt" / "boot" private lazy val scalaHome: File = { + val log = sbt.util.LogExchange.logger("run-from-source") val scalaHome0 = bootDirectory / s"scala-$scalaVersion" - if (scalaHome0.exists) scalaHome0 + if ((scalaHome0 / "lib").exists) scalaHome0 else { - val target = new File("target").getAbsoluteFile - val fakeboot = target / "fakeboot" + log.info(s"""scalaHome ($scalaHome0) wasn't found""") + val fakeboot = file(sys.props("user.home")) / ".sbt" / "fakeboot" val scalaHome1 = fakeboot / s"scala-$scalaVersion" val scalaHome1Lib = scalaHome1 / "lib" val scalaHome1Temp = scalaHome1 / "temp" - if (scalaHome1Lib.exists) () + if (scalaHome1Lib.exists) log.info(s"""using $scalaHome1 that was found""") else { + log.info(s"""creating $scalaHome1 by downloading scala-compiler $scalaVersion""") IO.createDirectories(List(scalaHome1Lib, scalaHome1Temp)) - val log = sbt.util.LogExchange.logger("run-from-source") val lm = { import sbt.librarymanagement.ivy.IvyDependencyResolution val ivyConfig = InlineIvyConfiguration().withLog(log) From 38d53a941a8d1ac374ba02b853fa4cf1f549efe5 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 5 May 2018 04:53:34 -0400 Subject: [PATCH 291/356] IO 1.1.7, Zinc 1.1.6 --- project/Dependencies.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index b1e9fd0ab..a3e2d35fa 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,10 +8,10 @@ object Dependencies { val baseScalaVersion = scala212 // sbt modules - private val ioVersion = "1.1.6" + private val ioVersion = "1.1.7" private val utilVersion = "1.1.3" private val lmVersion = "1.1.4" - private val zincVersion = "1.1.5" + private val zincVersion = "1.1.6" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion From 094c2c602dd808a4cf0ed5acc76710e74e919274 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 5 May 2018 04:57:14 -0400 Subject: [PATCH 292/356] Formatting --- main/src/main/scala/sbt/Defaults.scala | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index ff93c1097..f78eaeb7b 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2884,15 +2884,18 @@ object Classpaths { filter: FileFilter, excl: FileFilter): Classpath = (base * (filter -- excl) +++ (base / config.name).descendantsExcept(filter, excl)).classpath - - - @deprecated("The method only works for Scala 2, use the overloaded version to support both Scala 2 and Scala 3", "1.1.5") + @deprecated( + "The method only works for Scala 2, use the overloaded version to support both Scala 2 and Scala 3", + "1.1.5") def autoPlugins(report: UpdateReport, internalPluginClasspath: Seq[File]): Seq[String] = autoPlugins(report, internalPluginClasspath, isDotty = false) - def autoPlugins(report: UpdateReport, internalPluginClasspath: Seq[File], isDotty: Boolean): Seq[String] = { + def autoPlugins(report: UpdateReport, + internalPluginClasspath: Seq[File], + isDotty: Boolean): Seq[String] = { val pluginClasspath = report.matching(configurationFilter(CompilerPlugin.name)) ++ internalPluginClasspath - val plugins = sbt.internal.inc.classpath.ClasspathUtilities.compilerPlugins(pluginClasspath, isDotty) + val plugins = + sbt.internal.inc.classpath.ClasspathUtilities.compilerPlugins(pluginClasspath, isDotty) plugins.map("-Xplugin:" + _.getAbsolutePath).toSeq } @@ -2912,7 +2915,9 @@ object Classpaths { lazy val compilerPluginConfig = Seq( scalacOptions := { val options = scalacOptions.value - val newPlugins = autoPlugins(update.value, internalCompilerPluginClasspath.value.files, ScalaInstance.isDotty(scalaVersion.value)) + val newPlugins = autoPlugins(update.value, + internalCompilerPluginClasspath.value.files, + ScalaInstance.isDotty(scalaVersion.value)) val existing = options.toSet if (autoCompilerPlugins.value) options ++ newPlugins.filterNot(existing) else options } From d8fe09f00776ad45d404fe0319df237bef8cc00f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 5 May 2018 05:02:53 -0400 Subject: [PATCH 293/356] Adjust to upstream change --- main-command/src/main/scala/sbt/Watched.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index 029702841..73e177f83 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -12,7 +12,7 @@ import java.nio.file.FileSystems import sbt.BasicCommandStrings.ClearOnFailure import sbt.State.FailureWall -import sbt.internal.io.{ EventMonitor, Source, SourceModificationWatch, WatchState } +import sbt.internal.io.{ EventMonitor, Source, WatchState } import sbt.internal.util.AttributeKey import sbt.internal.util.Types.const import sbt.io._ @@ -119,7 +119,7 @@ object Watched { ) case Some(eventMonitor) => printIfDefined(watched watchingMessage eventMonitor.state) - val triggered = try eventMonitor.watch() + val triggered = try eventMonitor.awaitEvent() catch { case e: Exception => log.error( From 2b099c86b546a14439972494edbe12275db51f92 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 5 May 2018 15:19:47 -0400 Subject: [PATCH 294/356] Fix Dotty plugin test Ref https://github.com/sbt/sbt/pull/4084 --- .../dotty-compiler-plugin/build.sbt | 29 +++++++++---------- .../plugin/DivideZero.scala | 4 +-- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/build.sbt b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/build.sbt index 19d921ef1..099cd9f71 100644 --- a/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/build.sbt +++ b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/build.sbt @@ -1,19 +1,16 @@ -lazy val dottyVersion = dottyLatestNightlyBuild +// hardcode dottyVersion to make test deterministic +lazy val dottyVersion = "0.8.0-bin-20180424-e77604d-NIGHTLY" -lazy val pluginSetting = Seq( - name := "dividezero", - version := "0.0.1", - organization := "ch.epfl.lamp", - scalaVersion := dottyVersion, - - libraryDependencies ++= Seq( - "ch.epfl.lamp" %% "dotty" % "provided" +lazy val plugin = (project in file("plugin")) + .settings( + name := "dividezero", + version := "0.0.1", + organization := "ch.epfl.lamp", + scalaVersion := dottyVersion, ) -) -lazy val plugin = (project in file("plugin")).settings(pluginSetting: _*) - -lazy val app = (project in file(".")).settings( - scalaVersion := dottyVersion, - libraryDependencies += compilerPlugin("ch.epfl.lamp" %% "dividezero" % "0.0.1") -) +lazy val app = (project in file(".")) + .settings( + scalaVersion := dottyVersion, + libraryDependencies += compilerPlugin("ch.epfl.lamp" %% "dividezero" % "0.0.1"), + ) diff --git a/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/plugin/DivideZero.scala b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/plugin/DivideZero.scala index 1dfe23cf5..c110a7cee 100644 --- a/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/plugin/DivideZero.scala +++ b/sbt/src/sbt-test/compiler-project/dotty-compiler-plugin/plugin/DivideZero.scala @@ -18,8 +18,8 @@ class DivideZero extends PluginPhase with StandardPlugin { val phaseName = name - override val runsAfter = Set(Pickler.phaseName) - override val runsBefore = Set(LinkAll.phaseName) + override val runsAfter = Set(Pickler.name) + override val runsBefore = Set(LinkAll.name) override def init(options: List[String]): List[PluginPhase] = this :: Nil From 24946189f093003246f0cfbf06d5a4955717d116 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 7 May 2018 16:18:46 -0400 Subject: [PATCH 295/356] Zinc 1.1.7 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index a3e2d35fa..c97af475b 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -11,7 +11,7 @@ object Dependencies { private val ioVersion = "1.1.7" private val utilVersion = "1.1.3" private val lmVersion = "1.1.4" - private val zincVersion = "1.1.6" + private val zincVersion = "1.1.7" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion From d3ac5274b3ba767d12a6266a6ca9958ea30c1b14 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Tue, 8 May 2018 13:18:13 -0700 Subject: [PATCH 296/356] Revert back to non-blocking watch termination condition A thread blocking on System.in.read() cannot be interrupted, so check System.in.available before blocking. This is how it used to work. It requires https://github.com/sbt/io/pull/149 or else a cpu will be pegged by the EventMonitor user input thread spinning on System.in.available. --- main-command/src/main/scala/sbt/Watched.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index 73e177f83..4daea053b 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -100,7 +100,7 @@ object Watched { def executeContinuously(watched: Watched, s: State, next: String, repeat: String): State = { @tailrec def shouldTerminate: Boolean = - watched.terminateWatch(System.in.read()) || shouldTerminate + (System.in.available > 0) && (watched.terminateWatch(System.in.read()) || shouldTerminate) val log = s.log val logger = new EventMonitor.Logger { override def debug(msg: => Any): Unit = log.debug(msg.toString) From b849894a369759a1d8ff95542326c71206254d8e Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 8 May 2018 17:52:30 -0400 Subject: [PATCH 297/356] IO 1.1.8 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index c97af475b..9f25f76e9 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,7 +8,7 @@ object Dependencies { val baseScalaVersion = scala212 // sbt modules - private val ioVersion = "1.1.7" + private val ioVersion = "1.1.8" private val utilVersion = "1.1.3" private val lmVersion = "1.1.4" private val zincVersion = "1.1.7" From add6bde3961d3443ea90138b1be68588d4df7109 Mon Sep 17 00:00:00 2001 From: Tim Harper Date: Mon, 14 May 2018 12:39:44 -0600 Subject: [PATCH 298/356] Add timestamp field to JUnitXML report --- testing/src/main/scala/sbt/JUnitXmlTestsListener.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala index 2f92523e8..0ac2a6997 100644 --- a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala +++ b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala @@ -9,6 +9,7 @@ package sbt import java.io.{ File, IOException, PrintWriter, StringWriter } import java.net.InetAddress +import java.time.LocalDateTime import java.util.Hashtable import scala.collection.mutable.ListBuffer @@ -59,7 +60,7 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { * Gathers data for one Test Suite. We map test groups to TestSuites. * Each TestSuite gets its own output file. */ - class TestSuite(val name: String) { + class TestSuite(val name: String, timestamp: LocalDateTime) { val events: ListBuffer[TEvent] = new ListBuffer() /**Adds one test result to this suite.*/ @@ -83,7 +84,7 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { ) val result = - + { properties } { for (e <- events) yield Date: Mon, 14 May 2018 17:29:20 -0600 Subject: [PATCH 299/356] Remove milliseconds from format in order to comply with JUnit spec --- .../src/main/scala/sbt/JUnitXmlTestsListener.scala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala index 0ac2a6997..33057b6e7 100644 --- a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala +++ b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala @@ -10,6 +10,8 @@ package sbt import java.io.{ File, IOException, PrintWriter, StringWriter } import java.net.InetAddress import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit import java.util.Hashtable import scala.collection.mutable.ListBuffer @@ -84,7 +86,7 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { ) val result = - + { properties } { for (e <- events) yield * * @@ -201,6 +205,12 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { // contort the user into not using spaces. private[this] def normalizeName(s: String) = s.replaceAll("""\s+""", "-") + /** + * Format the date, without milliseconds or the timezone, per the JUnit spec. + */ + private[this] def formatISO8601DateTime(d: LocalDateTime): String = + d.truncatedTo(ChronoUnit.SECONDS).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + private def writeSuite() = { val file = new File(targetDir, s"${normalizeName(withTestSuite(_.name))}.xml").getAbsolutePath // TODO would be nice to have a logger and log this with level debug From 3702132019e41f409256c0ab4cb55c9b42eb8d98 Mon Sep 17 00:00:00 2001 From: Tim Harper Date: Mon, 14 May 2018 17:31:05 -0600 Subject: [PATCH 300/356] Keep single parameter constructor for backwards bin compat. --- testing/src/main/scala/sbt/JUnitXmlTestsListener.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala index 33057b6e7..fc4c47118 100644 --- a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala +++ b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala @@ -63,6 +63,8 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { * Each TestSuite gets its own output file. */ class TestSuite(val name: String, timestamp: LocalDateTime) { + def this(name: String) = this(name, LocalDateTime.now()) + val events: ListBuffer[TEvent] = new ListBuffer() /**Adds one test result to this suite.*/ @@ -142,10 +144,7 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { /** * Starts a new, initially empty Suite with the given name. */ - override def startGroup(name: String): Unit = { - val timestamp = LocalDateTime.now() - testSuite.set(Some(new TestSuite(name, timestamp))) - } + override def startGroup(name: String): Unit = testSuite.set(Some(new TestSuite(name))) /** * Adds all details for the given even to the current suite. From 5cc0038a65a12c93cd9dcd7345128cf4ab99beee Mon Sep 17 00:00:00 2001 From: Tim Harper Date: Mon, 14 May 2018 20:57:04 -0600 Subject: [PATCH 301/356] Little better comment. Add timestamp to example. --- testing/src/main/scala/sbt/JUnitXmlTestsListener.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala index fc4c47118..9344305f5 100644 --- a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala +++ b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala @@ -156,11 +156,10 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { /** * called for each class or equivalent grouping * We map one group to one Testsuite, so for each Group - * we create an XML which implements the [[https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd JUnit xml - * spec]], and looks like this: + * we create [[https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd JUnit XML file]], and looks like this: * * - * + * * * * ... From c8a4dc10e72e995df0cf53e0d1dd803ddcf46f18 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 6 Feb 2018 15:18:50 +0000 Subject: [PATCH 302/356] Add MavenCentral to RunFromSourceMain's repos --- sbt/src/test/scala/sbt/RunFromSourceMain.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala index d70f02cb0..09e9758b1 100644 --- a/sbt/src/test/scala/sbt/RunFromSourceMain.scala +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -117,8 +117,10 @@ object RunFromSourceMain { def topLoader = new java.net.URLClassLoader(Array(), null) def globalLock = noGlobalLock def bootDirectory = RunFromSourceMain.bootDirectory - def ivyRepositories = Array() - def appRepositories = Array() + final case class PredefRepo(id: Predefined) extends PredefinedRepository + import Predefined._ + def ivyRepositories = Array(PredefRepo(Local), PredefRepo(MavenCentral)) + def appRepositories = Array(PredefRepo(Local), PredefRepo(MavenCentral)) def isOverrideRepositories = false def ivyHome = file(sys.props("user.home")) / ".ivy2" def checksums = Array("sha1", "md5") From 7dfe6097fbcd53f98457573f2205c2668c8306bc Mon Sep 17 00:00:00 2001 From: Alexander Samsig Date: Tue, 15 May 2018 12:13:51 +0200 Subject: [PATCH 303/356] Removed Load and LoadCommand since they are no longer in use. Changed add-defaults-commands directly since it is only used internally. Added deprecation warning for LoadFailed and LastGrep. --- main/src/main/scala/sbt/Main.scala | 29 ++++++++++++++++--- .../scala/sbt/internal/CommandStrings.scala | 10 ++----- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index ac1ad03be..2117f26f0 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -187,6 +187,7 @@ object BuiltinCommands { inspect, loadProjectImpl, loadFailed, + oldLoadFailed, Cross.crossBuild, Cross.switchVersion, PluginCross.pluginCross, @@ -468,13 +469,21 @@ object BuiltinCommands { @deprecated("Use `lastGrep` instead.", "1.2.0") def oldLastGrep: Command = - lastGrepCommand(OldLastGrepCommand, oldLastGrepBrief, oldLastGrepDetailed) + lastGrepCommand(OldLastGrepCommand, oldLastGrepBrief, oldLastGrepDetailed, { s => + s.log.warn(deprecationWarningText(OldLastGrepCommand, LastGrepCommand)) + lastGrepParser(s) + }) def lastGrep: Command = - lastGrepCommand(LastGrepCommand, lastGrepBrief, lastGrepDetailed) + lastGrepCommand(LastGrepCommand, lastGrepBrief, lastGrepDetailed, lastGrepParser) - private def lastGrepCommand(name: String, briefHelp: (String, String), detail: String): Command = - Command(name, briefHelp, detail)(lastGrepParser) { + private def lastGrepCommand( + name: String, + briefHelp: (String, String), + detail: String, + parser: State => Parser[(String, Option[AnyKeys])] + ): Command = + Command(name, briefHelp, detail)(parser) { case (s, (pattern, Some(sks))) => val (str, _, display) = extractLast(s) Output.lastGrep(sks, str.streams(s), pattern, printLast)(display) @@ -670,6 +679,18 @@ object BuiltinCommands { Command.make(ProjectCommand, projectBrief, projectDetailed)(ProjectNavigation.command) def loadFailed: Command = Command(LoadFailed)(loadProjectParser)(doLoadFailed) + @deprecated("Use `loadFailed` instead.", "1.2.0") + def oldLoadFailed: Command = + Command(OldLoadFailed) { s => + s.log.warn( + deprecationWarningText(OldLoadFailed, LoadFailed) + ) + loadProjectParser(s) + }(doLoadFailed) + + private[this] def deprecationWarningText(oldCommand: String, newCommand: String) = { + s"The `$oldCommand` command is deprecated in favor of `$newCommand` and will be removed in a later version" + } @tailrec private[this] def doLoadFailed(s: State, loadArg: String): State = { diff --git a/main/src/main/scala/sbt/internal/CommandStrings.scala b/main/src/main/scala/sbt/internal/CommandStrings.scala index 1239cc034..99ec4637e 100644 --- a/main/src/main/scala/sbt/internal/CommandStrings.scala +++ b/main/src/main/scala/sbt/internal/CommandStrings.scala @@ -280,16 +280,12 @@ $ProjectsCommand remove + def sbtrc = ".sbtrc" - def DefaultsCommand = "add-default-commands" + def DefaultsCommand = "addDefaultCommands" def DefaultsBrief = (DefaultsCommand, DefaultsDetailed) def DefaultsDetailed = "Registers default built-in commands" - def Load = "load" - def LoadLabel = "a project" - def LoadCommand = "load-commands" - def LoadCommandLabel = "commands" - - def LoadFailed = "load-failed" + def LoadFailed = "loadFailed" + def OldLoadFailed = "load-failed" def LoadProjectImpl = "loadp" def LoadProject = "reload" From 34cc8cd2730318db99770dbadce29fc455ba91ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20H=C3=B8is=C3=A6ther?= Date: Tue, 15 May 2018 13:42:41 +0200 Subject: [PATCH 304/356] Only use first line for multiline descriptions --- main/src/main/scala/sbt/Main.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index da44d0cb8..2a9c88505 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -357,7 +357,7 @@ object BuiltinCommands { } def taskStrings(key: AttributeKey[_]): Option[(String, String)] = key.description map { d => - (key.label, d) + (key.label, d.split("\r?\n")(0)) } def defaults = Command.command(DefaultsCommand) { s => From b30159aded43bc945744ae6e21b69b30a873e5f3 Mon Sep 17 00:00:00 2001 From: alodavi Date: Tue, 15 May 2018 14:06:50 +0200 Subject: [PATCH 305/356] [alodavi/improving_loading_settings_messaging] logging the path instead of just the name --- main/src/main/scala/sbt/internal/Load.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 6d620ec7c..0a3cce27d 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -1039,7 +1039,7 @@ private[sbt] object Load { // Grab all the settings we already loaded from sbt files def settings(files: Seq[File]): Seq[Setting[_]] = { if (files.nonEmpty) - log.info(s"${files.map(_.getName).mkString("Loading settings from ", ",", " ...")}") + log.info(s"${files.map(_.getPath).mkString("Loading settings from ", ",", " ...")}") for { file <- files config <- (memoSettings get file).toSeq From e83013d3c87a2f8e4113525c2e120fe514e4a039 Mon Sep 17 00:00:00 2001 From: dadarakt Date: Tue, 15 May 2018 14:09:27 +0200 Subject: [PATCH 306/356] Using outputChanged to track the change of the jar file instead of unit --- main-actions/src/main/scala/sbt/Package.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/main-actions/src/main/scala/sbt/Package.scala b/main-actions/src/main/scala/sbt/Package.scala index 76333963e..9970561d4 100644 --- a/main-actions/src/main/scala/sbt/Package.scala +++ b/main-actions/src/main/scala/sbt/Package.scala @@ -23,7 +23,7 @@ import sbt.internal.util.HNil import sbt.internal.util.HListFormats._ import sbt.util.FileInfo.{ exists, lastModified } import sbt.util.CacheImplicits._ -import sbt.util.Tracked.inputChanged +import sbt.util.Tracked.{ inputChanged, outputChanged } sealed trait PackageOption object Package { @@ -71,10 +71,11 @@ object Package { inputs: Map[File, String] :+: FilesInfo[ModifiedFileInfo] :+: Manifest :+: HNil) => import exists.format val sources :+: _ :+: manifest :+: HNil = inputs - inputChanged(cacheStoreFactory make "output") { (outChanged, jar: PlainFileInfo) => - if (inChanged || outChanged) + outputChanged(cacheStoreFactory make "output") { (outChanged, jar: PlainFileInfo) => + if (inChanged || outChanged) { makeJar(sources.toSeq, jar.file, manifest, log) - else + jar.file + } else log.debug("Jar uptodate: " + jar.file) } } From 39cac14ea7ba08e07d486ab7292afe228220e9db Mon Sep 17 00:00:00 2001 From: dadarakt Date: Tue, 15 May 2018 14:25:49 +0200 Subject: [PATCH 307/356] Adapted syntax to match example --- main-actions/src/main/scala/sbt/Package.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main-actions/src/main/scala/sbt/Package.scala b/main-actions/src/main/scala/sbt/Package.scala index 9970561d4..54d107917 100644 --- a/main-actions/src/main/scala/sbt/Package.scala +++ b/main-actions/src/main/scala/sbt/Package.scala @@ -82,7 +82,7 @@ object Package { val map = conf.sources.toMap val inputs = map :+: lastModified(map.keySet) :+: manifest :+: HNil - cachedMakeJar(inputs)(exists(conf.jar)) + cachedMakeJar(inputs)(() => exists(conf.jar)) } def setVersion(main: Attributes): Unit = { val version = Attributes.Name.MANIFEST_VERSION From 6214408783f96f896998cc9b3214047109ebb1c4 Mon Sep 17 00:00:00 2001 From: Holden Karau Date: Tue, 15 May 2018 05:31:58 -0700 Subject: [PATCH 308/356] Add support for --error. --- main-command/src/main/scala/sbt/BasicCommandStrings.scala | 6 ++++-- main-command/src/main/scala/sbt/BasicCommands.scala | 3 ++- sbt/src/sbt-test/actions/early-command/test | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index abc2bc9c1..e9e73fba2 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -67,7 +67,7 @@ $HelpCommand 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. --$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. This is useful as a startup option: @@ -77,7 +77,9 @@ $HelpCommand def runEarly(command: String) = s"$EarlyCommand($command)" private[sbt] def isEarlyCommand(s: String): Boolean = { - val levelOptions = Level.values.toSeq map { "-" + _ } + val levelOptions = Level.values.toSeq flatMap { elem => + List("-" + elem, "--" + elem) + } (s.startsWith(EarlyCommand + "(") && s.endsWith(")")) || (levelOptions contains s) } diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index 82bb7b883..97ed9d1d6 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -69,7 +69,8 @@ object BasicCommands { private[this] def earlyParser: State => Parser[String] = (s: State) => { val p1 = token(EarlyCommand + "(") flatMap (_ => otherCommandParser(s) <~ token(")")) val p2 = token("-") flatMap (_ => levelParser) - p1 | p2 + val p3 = token("--") flatMap (_ => levelParser) + p1 | p2 | p3 } private[this] def earlyHelp = Help(EarlyCommand, EarlyCommandBrief, EarlyCommandDetailed) diff --git a/sbt/src/sbt-test/actions/early-command/test b/sbt/src/sbt-test/actions/early-command/test index 532cef013..66e241ad4 100644 --- a/sbt/src/sbt-test/actions/early-command/test +++ b/sbt/src/sbt-test/actions/early-command/test @@ -1,2 +1,3 @@ > -error > early(error) +> --error \ No newline at end of file From 667a6be99e78fb675644d069a1228a9c6e78d56d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20H=C3=B8is=C3=A6ther?= Date: Tue, 15 May 2018 14:33:24 +0200 Subject: [PATCH 309/356] Show all lines when running help , but only first line when listing tasks --- main/src/main/scala/sbt/Main.scala | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 2a9c88505..65b436be0 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -313,8 +313,14 @@ object BuiltinCommands { 'v'.id.+.map(_.size + 1) | ("V" ^^^ Int.MaxValue) )) + + def taskDetail(keys: Seq[AttributeKey[_]], firstOnly: Boolean): Seq[(String, String)] = + sortByLabel(withDescription(keys)) flatMap { t => + taskStrings(t, firstOnly) + } + def taskDetail(keys: Seq[AttributeKey[_]]): Seq[(String, String)] = - sortByLabel(withDescription(keys)) flatMap taskStrings + taskDetail(keys, false) def allTaskAndSettingKeys(s: State): Seq[AttributeKey[_]] = { val extracted = Project.extract(s) @@ -349,16 +355,19 @@ object BuiltinCommands { def tasksHelp(s: State, filter: Seq[AttributeKey[_]] => Seq[AttributeKey[_]], arg: Option[String]): String = { - val commandAndDescription = taskDetail(filter(allTaskAndSettingKeys(s))) + val commandAndDescription = taskDetail(filter(allTaskAndSettingKeys(s)), true) arg match { case Some(selected) => detail(selected, commandAndDescription.toMap) case None => aligned(" ", " ", commandAndDescription) mkString ("\n", "\n", "") } } - def taskStrings(key: AttributeKey[_]): Option[(String, String)] = key.description map { d => - (key.label, d.split("\r?\n")(0)) - } + def taskStrings(key: AttributeKey[_], firstOnly: Boolean): Option[(String, String)] = + key.description map { d => + if (firstOnly) (key.label, d.split("\r?\n")(0)) else (key.label, d) + } + + def taskStrings(key: AttributeKey[_]): Option[(String, String)] = taskStrings(key, false) def defaults = Command.command(DefaultsCommand) { s => s.copy(definedCommands = DefaultCommands) From 80601e78ad5711cc9ac1023c034e50b4f9484f12 Mon Sep 17 00:00:00 2001 From: alodavi Date: Tue, 15 May 2018 14:44:00 +0200 Subject: [PATCH 310/356] [alodavi/improving_loading_settings_messaging] added notes on the Pr --- .../improving_loading_settings_messaging.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 notes/1.1.4/improving_loading_settings_messaging.md diff --git a/notes/1.1.4/improving_loading_settings_messaging.md b/notes/1.1.4/improving_loading_settings_messaging.md new file mode 100644 index 000000000..683846bbb --- /dev/null +++ b/notes/1.1.4/improving_loading_settings_messaging.md @@ -0,0 +1,22 @@ +### Improvements + +Now when loading a project that has multiple build.sbt files the logger shows the path as well. +Before it was: + +```bash +[info] Loading settings from build.sbt ... +[info] Loading settings from build.sbt ... +[info] Loading settings from build.sbt ... +[info] Loading settings from build.sbt ... +``` + +Now it's: + +```bash +[info] Loading settings from /home/user/Work/personal/someProject/build.sbt ... +[info] Loading settings from /home/user/Work/personal/someProject/subProject1/build.sbt ... +[info] Loading settings from /home/user/Work/personal/someProject/subProject2/build.sbt ... +[info] Loading settings from /home/user/Work/personal/someProject/subProject3/build.sbt ... +``` + +This should solve the issue: https://github.com/sbt/sbt/issues/3607 \ No newline at end of file From b7c9862f163931d04a574ea7bc93c4e7dbfe3c42 Mon Sep 17 00:00:00 2001 From: tiqwab Date: Tue, 15 May 2018 23:09:43 +0900 Subject: [PATCH 311/356] Fix kebab-case commands: notify-users-about-shell, write-sbt-version --- main/src/main/scala/sbt/Main.scala | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 2117f26f0..b7c61ccb7 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -197,7 +197,9 @@ object BuiltinCommands { plugin, plugins, writeSbtVersion, + oldWriteSbtVersion, notifyUsersAboutShell, + oldNotifyUsersAboutShell, shell, startServer, eval, @@ -853,12 +855,19 @@ object BuiltinCommands { if (!java.lang.Boolean.getBoolean("sbt.skip.version.write") && !intendsToInvokeNew(state)) writeSbtVersionUnconditionally(state) - private def WriteSbtVersion = "write-sbt-version" + private def WriteSbtVersion = "writeSbtVersion" + private def OldWriteSbtVersion = "write-sbt-version" private def writeSbtVersion: Command = Command.command(WriteSbtVersion) { state => writeSbtVersion(state); state } + @deprecated("Use `writeSbtVersion` instead", "1.2.0") + private def oldWriteSbtVersion: Command = + Command.command(OldWriteSbtVersion) { state => + state.log.warn(deprecationWarningText(OldWriteSbtVersion, WriteSbtVersion)) + writeSbtVersion(state); state + } private def intendsToInvokeCompile(state: State) = state.remainingCommands exists (_.commandLine == Keys.compile.key.label) @@ -869,10 +878,17 @@ object BuiltinCommands { state.log info "Executing in batch mode. For better performance use sbt's shell" } - private def NotifyUsersAboutShell = "notify-users-about-shell" + private def NotifyUsersAboutShell = "notifyUsersAboutShell" + private def OldNotifyUsersAboutShell = "notify-users-about-shell" private def notifyUsersAboutShell: Command = Command.command(NotifyUsersAboutShell) { state => notifyUsersAboutShell(state); state } + @deprecated("Use `notifyUsersAboutShell` instead", "1.2.0") + private def oldNotifyUsersAboutShell: Command = + Command.command(OldNotifyUsersAboutShell) { state => + state.log.warn(deprecationWarningText(OldNotifyUsersAboutShell, NotifyUsersAboutShell)) + notifyUsersAboutShell(state); state + } } From ab35c21c9818d9effe38fd3682ba4c6d59df4862 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 21 May 2018 17:24:38 +0100 Subject: [PATCH 312/356] Drop deprecated write-sbt-version & notify-users-about-shell Introduced in https://github.com/sbt/sbt/pull/4169, these commands aren't "user-facing" and are quite new. So no need to keep the old kebab syntax. --- main/src/main/scala/sbt/Main.scala | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index b7c61ccb7..b29cca948 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -197,9 +197,7 @@ object BuiltinCommands { plugin, plugins, writeSbtVersion, - oldWriteSbtVersion, notifyUsersAboutShell, - oldNotifyUsersAboutShell, shell, startServer, eval, @@ -856,18 +854,11 @@ object BuiltinCommands { writeSbtVersionUnconditionally(state) private def WriteSbtVersion = "writeSbtVersion" - private def OldWriteSbtVersion = "write-sbt-version" private def writeSbtVersion: Command = Command.command(WriteSbtVersion) { state => writeSbtVersion(state); state } - @deprecated("Use `writeSbtVersion` instead", "1.2.0") - private def oldWriteSbtVersion: Command = - Command.command(OldWriteSbtVersion) { state => - state.log.warn(deprecationWarningText(OldWriteSbtVersion, WriteSbtVersion)) - writeSbtVersion(state); state - } private def intendsToInvokeCompile(state: State) = state.remainingCommands exists (_.commandLine == Keys.compile.key.label) @@ -879,16 +870,9 @@ object BuiltinCommands { } private def NotifyUsersAboutShell = "notifyUsersAboutShell" - private def OldNotifyUsersAboutShell = "notify-users-about-shell" private def notifyUsersAboutShell: Command = Command.command(NotifyUsersAboutShell) { state => notifyUsersAboutShell(state); state } - @deprecated("Use `notifyUsersAboutShell` instead", "1.2.0") - private def oldNotifyUsersAboutShell: Command = - Command.command(OldNotifyUsersAboutShell) { state => - state.log.warn(deprecationWarningText(OldNotifyUsersAboutShell, NotifyUsersAboutShell)) - notifyUsersAboutShell(state); state - } } From 67efea6248cd70a0b129fa39a8ca6fa347b5fba5 Mon Sep 17 00:00:00 2001 From: alodavi Date: Wed, 23 May 2018 17:20:41 +0200 Subject: [PATCH 313/356] [alodavi/improving_loading_settings_messaging] logging file and project name instead of only file name --- main/src/main/scala/sbt/internal/Load.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 0a3cce27d..8a316bbc1 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -1039,7 +1039,7 @@ private[sbt] object Load { // Grab all the settings we already loaded from sbt files def settings(files: Seq[File]): Seq[Setting[_]] = { if (files.nonEmpty) - log.info(s"${files.map(_.getPath).mkString("Loading settings from ", ",", " ...")}") + log.info(s"${files.map(_.getName).mkString(s"Loading settings for project ${p.id} from ", ",", " ...")}") for { file <- files config <- (memoSettings get file).toSeq From 833e61635e7e1afd8f31f056165a72ff83850af1 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 27 May 2018 22:38:58 -0400 Subject: [PATCH 314/356] IO 1.1.10 and Zinc 1.1.5 --- project/Dependencies.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 9f25f76e9..bb2a81649 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,9 +8,9 @@ object Dependencies { val baseScalaVersion = scala212 // sbt modules - private val ioVersion = "1.1.8" + private val ioVersion = "1.1.10" private val utilVersion = "1.1.3" - private val lmVersion = "1.1.4" + private val lmVersion = "1.1.5" private val zincVersion = "1.1.7" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion From f2a7e1f1c3911fc9fa911d3fc76f61de28fd7a52 Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Mon, 28 May 2018 14:09:13 +0200 Subject: [PATCH 315/356] Fix for #4148 (SessionSettingsSpec intermittently fails) It turns out that `syntaxAnalyzer.UnitParser()` in global now also needs to be synchronized. The alternative is adding `synchronizeNames = true` in the global constructor, but that already proved unreliable in the case of #3743 (see comment https://github.com/sbt/sbt/issues/3170#issuecomment-355218833) --- main/src/main/scala/sbt/internal/parser/SbtParser.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/parser/SbtParser.scala b/main/src/main/scala/sbt/internal/parser/SbtParser.scala index 18dfa2b5e..1a7f52d57 100644 --- a/main/src/main/scala/sbt/internal/parser/SbtParser.scala +++ b/main/src/main/scala/sbt/internal/parser/SbtParser.scala @@ -148,7 +148,9 @@ private[sbt] object SbtParser { reporter.reset() val wrapperFile = new BatchSourceFile(reporterId, code) val unit = new CompilationUnit(wrapperFile) - val parser = new syntaxAnalyzer.UnitParser(unit) + val parser = SbtParser.synchronized { // see https://github.com/sbt/sbt/issues/4148 + new syntaxAnalyzer.UnitParser(unit) + } val parsedTrees = SbtParser.synchronized { // see https://github.com/scala/bug/issues/10605 parser.templateStats() } From c31583e4f8f19a6c2d060b6a75b274050439c203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= Date: Wed, 18 Apr 2018 16:28:43 +0100 Subject: [PATCH 316/356] Discovery of java homes --- .../contraband-scala/sbt/JavaVersion.scala | 40 +++++++++++++++ main/src/main/contraband/main.contra | 6 +++ main/src/main/scala/sbt/Defaults.scala | 4 ++ main/src/main/scala/sbt/Keys.scala | 4 ++ .../main/scala/sbt/internal/CrossJava.scala | 50 +++++++++++++++++++ .../sbt-test/java/home-discovery/build.sbt | 5 ++ sbt/src/sbt-test/java/home-discovery/test | 1 + 7 files changed, 110 insertions(+) create mode 100644 main/src/main/contraband-scala/sbt/JavaVersion.scala create mode 100644 main/src/main/scala/sbt/internal/CrossJava.scala create mode 100644 sbt/src/sbt-test/java/home-discovery/build.sbt create mode 100644 sbt/src/sbt-test/java/home-discovery/test diff --git a/main/src/main/contraband-scala/sbt/JavaVersion.scala b/main/src/main/contraband-scala/sbt/JavaVersion.scala new file mode 100644 index 000000000..68d3941f1 --- /dev/null +++ b/main/src/main/contraband-scala/sbt/JavaVersion.scala @@ -0,0 +1,40 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt +final class JavaVersion private ( + val vendor: Option[String], + val version: String) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: JavaVersion => (this.vendor == x.vendor) && (this.version == x.version) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "sbt.JavaVersion".##) + vendor.##) + version.##) + } + override def toString: String = { + "JavaVersion(" + vendor + ", " + version + ")" + } + private[this] def copy(vendor: Option[String] = vendor, version: String = version): JavaVersion = { + new JavaVersion(vendor, version) + } + def withVendor(vendor: Option[String]): JavaVersion = { + copy(vendor = vendor) + } + def withVendor(vendor: String): JavaVersion = { + copy(vendor = Option(vendor)) + } + def withVersion(version: String): JavaVersion = { + copy(version = version) + } +} +object JavaVersion { + def apply(version: String): JavaVersion = new JavaVersion(None, version) + def apply(vendor: Option[String], version: String): JavaVersion = new JavaVersion(vendor, version) + def apply(vendor: String, version: String): JavaVersion = new JavaVersion(Option(vendor), version) +} diff --git a/main/src/main/contraband/main.contra b/main/src/main/contraband/main.contra index eb9e9f42c..10ec4a469 100644 --- a/main/src/main/contraband/main.contra +++ b/main/src/main/contraband/main.contra @@ -17,3 +17,9 @@ enum PluginTrigger { AllRequirements NoTrigger } + +type JavaVersion { + vendor: String + version: String! + #xcompanion def apply(version: String): JavaVersion = new JavaVersion(None, version) +} diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 4a8a34f1a..c4fa875ac 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -69,6 +69,7 @@ import sbt.librarymanagement.syntax._ import sbt.util.InterfaceUtil.{ toJavaFunction => f1 } import sbt.util._ import sbt.util.CacheImplicits._ +import scala.collection.immutable.ListMap import scala.concurrent.duration.FiniteDuration import scala.util.control.NonFatal import scala.xml.NodeSeq @@ -159,6 +160,9 @@ object Defaults extends BuildCommon { scalaHome :== None, apiURL := None, javaHome :== None, + discoveredJavaHomes := sbt.internal.CrossJava.discoverJavaHomes, + javaHomes :== ListMap.empty, + fullJavaHomes := discoveredJavaHomes.value ++ javaHomes.value, testForkedParallel :== false, javaOptions :== Nil, sbtPlugin :== false, diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 82191f556..751540de7 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -271,6 +271,10 @@ object Keys { val outputStrategy = settingKey[Option[sbt.OutputStrategy]]("Selects how to log output when running a main class.").withRank(DSetting) val connectInput = settingKey[Boolean]("If true, connects standard input when running a main class forked.").withRank(CSetting) val javaHome = settingKey[Option[File]]("Selects the Java installation used for compiling and forking. If None, uses the Java installation running the build.").withRank(ASetting) + val discoveredJavaHomes = settingKey[Map[JavaVersion, File]]("Discovered Java home directories") + val javaHomes = settingKey[Map[JavaVersion, File]]("The user-defined additional Java home directories") + val fullJavaHomes = taskKey[Map[JavaVersion, File]]("Combines discoveredJavaHomes and custom javaHomes.").withRank(CTask) + val javaOptions = taskKey[Seq[String]]("Options passed to a new JVM when forking.").withRank(BPlusTask) val envVars = taskKey[Map[String, String]]("Environment variables used when forking a new JVM").withRank(BTask) diff --git a/main/src/main/scala/sbt/internal/CrossJava.scala b/main/src/main/scala/sbt/internal/CrossJava.scala new file mode 100644 index 000000000..c93d44257 --- /dev/null +++ b/main/src/main/scala/sbt/internal/CrossJava.scala @@ -0,0 +1,50 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt +package internal + +import java.io.File +import scala.collection.immutable.ListMap +import sbt.io.IO +import sbt.io.syntax._ + +private[sbt] object CrossJava { + def discoverJavaHomes: ListMap[JavaVersion, File] = { + val configs = Vector(JavaDiscoverConfig.linux, JavaDiscoverConfig.macOS) + ListMap(configs flatMap { _.javaHomes }: _*) + } + + sealed trait JavaDiscoverConf { + def javaHomes: Vector[(JavaVersion, File)] + } + + object JavaDiscoverConfig { + val linux = new JavaDiscoverConf { + val base: File = file("/usr") / "lib" / "jvm" + val JavaHomeDir = """java-([0-9]+)-.*""".r + def javaHomes: Vector[(JavaVersion, File)] = + wrapNull(base.list()).collect { + case dir @ JavaHomeDir(ver) => JavaVersion(ver) -> (base / dir) + } + } + + val macOS = new JavaDiscoverConf { + val base: File = file("/Library") / "Java" / "JavaVirtualMachines" + val JavaHomeDir = """jdk-?(1\.)?([0-9]+).*""".r + def javaHomes: Vector[(JavaVersion, File)] = + wrapNull(base.list()).collect { + case dir @ JavaHomeDir(m, n) => + JavaVersion(n) -> (base / dir / "Contents" / "Home") + } + } + } + + def wrapNull(a: Array[String]): Vector[String] = + if (a eq null) Vector() + else a.toVector +} diff --git a/sbt/src/sbt-test/java/home-discovery/build.sbt b/sbt/src/sbt-test/java/home-discovery/build.sbt new file mode 100644 index 000000000..b5d79caeb --- /dev/null +++ b/sbt/src/sbt-test/java/home-discovery/build.sbt @@ -0,0 +1,5 @@ +Global / javaHomes += JavaVersion("6") -> file("/good/old/times/java-6") + +TaskKey[Unit]("check") := { + assert(fullJavaHomes.value(JavaVersion("6")).getAbsolutePath.contains("java-6")) +} diff --git a/sbt/src/sbt-test/java/home-discovery/test b/sbt/src/sbt-test/java/home-discovery/test new file mode 100644 index 000000000..15675b169 --- /dev/null +++ b/sbt/src/sbt-test/java/home-discovery/test @@ -0,0 +1 @@ +> check From aff9e0110cd7f4b942e194e1eb979f89fbf64f1f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 3 May 2018 00:59:27 -0400 Subject: [PATCH 317/356] Accept both 1.x and x for 1.1 to 1.8 --- main/src/main/scala/sbt/Defaults.scala | 4 ++-- main/src/main/scala/sbt/internal/CrossJava.scala | 12 +++++++++++- sbt/src/sbt-test/java/home-discovery/build.sbt | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index c4fa875ac..f3e550205 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -160,9 +160,9 @@ object Defaults extends BuildCommon { scalaHome :== None, apiURL := None, javaHome :== None, - discoveredJavaHomes := sbt.internal.CrossJava.discoverJavaHomes, + discoveredJavaHomes := CrossJava.discoverJavaHomes, javaHomes :== ListMap.empty, - fullJavaHomes := discoveredJavaHomes.value ++ javaHomes.value, + fullJavaHomes := CrossJava.expandJavaHomes(discoveredJavaHomes.value ++ javaHomes.value), testForkedParallel :== false, javaOptions :== Nil, sbtPlugin :== false, diff --git a/main/src/main/scala/sbt/internal/CrossJava.scala b/main/src/main/scala/sbt/internal/CrossJava.scala index c93d44257..1c035a4d8 100644 --- a/main/src/main/scala/sbt/internal/CrossJava.scala +++ b/main/src/main/scala/sbt/internal/CrossJava.scala @@ -10,7 +10,6 @@ package internal import java.io.File import scala.collection.immutable.ListMap -import sbt.io.IO import sbt.io.syntax._ private[sbt] object CrossJava { @@ -44,6 +43,17 @@ private[sbt] object CrossJava { } } + // expand Java versions to 1-8 to 1.x, and vice versa to accept both "1.8" and "8" + private val oneDot = Map((1 to 8).toVector flatMap { i => + Vector(s"$i" -> s"1.$i", s"1.$i" -> s"$i") + }: _*) + def expandJavaHomes(hs: Map[JavaVersion, File]): Map[JavaVersion, File] = + hs flatMap { + case (k, v) => + if (oneDot.contains(k.version)) Vector(k -> v, k.withVersion(oneDot(k.version)) -> v) + else Vector(k -> v) + } + def wrapNull(a: Array[String]): Vector[String] = if (a eq null) Vector() else a.toVector diff --git a/sbt/src/sbt-test/java/home-discovery/build.sbt b/sbt/src/sbt-test/java/home-discovery/build.sbt index b5d79caeb..7dd72c1d1 100644 --- a/sbt/src/sbt-test/java/home-discovery/build.sbt +++ b/sbt/src/sbt-test/java/home-discovery/build.sbt @@ -1,5 +1,5 @@ Global / javaHomes += JavaVersion("6") -> file("/good/old/times/java-6") TaskKey[Unit]("check") := { - assert(fullJavaHomes.value(JavaVersion("6")).getAbsolutePath.contains("java-6")) + assert(fullJavaHomes.value(JavaVersion("1.6")).getAbsolutePath.contains("java-6")) } From 2da1aa61eb499c01d14ca64491b1a46f3599269a Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 3 May 2018 03:39:55 -0400 Subject: [PATCH 318/356] implement cross JDK forking ``` sbt:helloworld> java++ 10 [info] Reapplying settings... sbt:helloworld> run [info] Running (fork) Hello [info] 10.0.1 sbt:helloworld> java++ 8 [info] Reapplying settings... sbt:helloworld> run [info] Running (fork) Hello [info] 1.8.0_171 ``` --- .travis.yml | 12 +- .../contraband-scala/sbt/JavaVersion.scala | 28 +- main/src/main/contraband/main.contra | 8 +- main/src/main/scala/sbt/Cross.scala | 24 +- main/src/main/scala/sbt/Keys.scala | 3 +- main/src/main/scala/sbt/Main.scala | 3 + .../scala/sbt/internal/CommandStrings.scala | 25 ++ .../main/scala/sbt/internal/CrossJava.scala | 295 +++++++++++++++++- sbt/src/sbt-test/java/cross/A.scala | 16 + sbt/src/sbt-test/java/cross/build.sbt | 25 ++ sbt/src/sbt-test/java/cross/test | 6 + 11 files changed, 411 insertions(+), 34 deletions(-) create mode 100644 sbt/src/sbt-test/java/cross/A.scala create mode 100644 sbt/src/sbt-test/java/cross/build.sbt create mode 100644 sbt/src/sbt-test/java/cross/test diff --git a/.travis.yml b/.travis.yml index c6e2d000e..7f257e9ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ cache: directories: - $HOME/.ivy2/cache - $HOME/.sbt/boot + - $HOME/.jabba language: scala @@ -15,6 +16,15 @@ jdk: matrix: fast_finish: true +matrix: + include: + - env: SBT_CMD="scripted java/*" + sudo: true + before_install: + - curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.9.4/install.sh | bash && . ~/.jabba/jabba.sh + install: + - sudo /home/travis/.jabba/bin/jabba install openjdk@1.10 + env: global: - secure: d3bu2KNwsVHwfhbGgO+gmRfDKBJhfICdCJFGWKf2w3Gv86AJZX9nuTYRxz0KtdvEHO5Xw8WTBZLPb2thSJqhw9OCm4J8TBAVqCP0ruUj4+aqBUFy4bVexQ6WKE6nWHs4JPzPk8c6uC1LG3hMuzlC8RGETXtL/n81Ef1u7NjyXjs= @@ -26,7 +36,7 @@ env: - SBT_CMD="scripted dependency-management/*2of4" - SBT_CMD="scripted dependency-management/*3of4" - SBT_CMD="scripted dependency-management/*4of4" - - SBT_CMD="scripted java/* package/* reporter/* run/* project-load/*" + - SBT_CMD="scripted package/* reporter/* run/* project-load/*" - SBT_CMD="scripted project/*1of2" - SBT_CMD="scripted project/*2of2" - SBT_CMD="scripted source-dependencies/*1of3" diff --git a/main/src/main/contraband-scala/sbt/JavaVersion.scala b/main/src/main/contraband-scala/sbt/JavaVersion.scala index 68d3941f1..4c630e3cd 100644 --- a/main/src/main/contraband-scala/sbt/JavaVersion.scala +++ b/main/src/main/contraband-scala/sbt/JavaVersion.scala @@ -5,23 +5,26 @@ // DO NOT EDIT MANUALLY package sbt final class JavaVersion private ( - val vendor: Option[String], - val version: String) extends Serializable { - + val numbers: Vector[Long], + val vendor: Option[String]) extends Serializable { + def numberStr: String = numbers.mkString(".") override def equals(o: Any): Boolean = o match { - case x: JavaVersion => (this.vendor == x.vendor) && (this.version == x.version) + case x: JavaVersion => (this.numbers == x.numbers) && (this.vendor == x.vendor) case _ => false } override def hashCode: Int = { - 37 * (37 * (37 * (17 + "sbt.JavaVersion".##) + vendor.##) + version.##) + 37 * (37 * (37 * (17 + "sbt.JavaVersion".##) + numbers.##) + vendor.##) } override def toString: String = { - "JavaVersion(" + vendor + ", " + version + ")" + vendor.map(_ + "@").getOrElse("") + numberStr } - private[this] def copy(vendor: Option[String] = vendor, version: String = version): JavaVersion = { - new JavaVersion(vendor, version) + private[this] def copy(numbers: Vector[Long] = numbers, vendor: Option[String] = vendor): JavaVersion = { + new JavaVersion(numbers, vendor) + } + def withNumbers(numbers: Vector[Long]): JavaVersion = { + copy(numbers = numbers) } def withVendor(vendor: Option[String]): JavaVersion = { copy(vendor = vendor) @@ -29,12 +32,9 @@ final class JavaVersion private ( def withVendor(vendor: String): JavaVersion = { copy(vendor = Option(vendor)) } - def withVersion(version: String): JavaVersion = { - copy(version = version) - } } object JavaVersion { - def apply(version: String): JavaVersion = new JavaVersion(None, version) - def apply(vendor: Option[String], version: String): JavaVersion = new JavaVersion(vendor, version) - def apply(vendor: String, version: String): JavaVersion = new JavaVersion(Option(vendor), version) + def apply(version: String): JavaVersion = sbt.internal.CrossJava.parseJavaVersion(version) + def apply(numbers: Vector[Long], vendor: Option[String]): JavaVersion = new JavaVersion(numbers, vendor) + def apply(numbers: Vector[Long], vendor: String): JavaVersion = new JavaVersion(numbers, Option(vendor)) } diff --git a/main/src/main/contraband/main.contra b/main/src/main/contraband/main.contra index 10ec4a469..5cabb0cd4 100644 --- a/main/src/main/contraband/main.contra +++ b/main/src/main/contraband/main.contra @@ -19,7 +19,11 @@ enum PluginTrigger { } type JavaVersion { + numbers: [Long] vendor: String - version: String! - #xcompanion def apply(version: String): JavaVersion = new JavaVersion(None, version) + + #x def numberStr: String = numbers.mkString(".") + #xtostring vendor.map(_ + "@").getOrElse("") + numberStr + + #xcompanion def apply(version: String): JavaVersion = sbt.internal.CrossJava.parseJavaVersion(version) } diff --git a/main/src/main/scala/sbt/Cross.scala b/main/src/main/scala/sbt/Cross.scala index c1257db5a..e68f2f20f 100644 --- a/main/src/main/scala/sbt/Cross.scala +++ b/main/src/main/scala/sbt/Cross.scala @@ -99,14 +99,23 @@ object Cross { } /** - * Parse the given command into either an aggregate command or a command for a project + * Parse the given command into a list of aggregate projects and command to issue. */ - private def parseCommand(command: String): Either[String, (String, String)] = { + private[sbt] def parseSlashCommand(extracted: Extracted)( + command: String): (Seq[ProjectRef], String) = { + import extracted._ import DefaultParsers._ val parser = (OpOrID <~ charClass(_ == '/', "/")) ~ any.* map { - case project ~ cmd => (project, cmd.mkString) + case seg1 ~ cmd => (seg1, cmd.mkString) + } + Parser.parse(command, parser) match { + case Right((seg1, cmd)) => + structure.allProjectRefs.find(_.project == seg1) match { + case Some(proj) => (Seq(proj), cmd) + case _ => (resolveAggregates(extracted), command) + } + case _ => (resolveAggregates(extracted), command) } - Parser.parse(command, parser).left.map(_ => command) } def crossBuild: Command = @@ -115,12 +124,7 @@ object Cross { private def crossBuildCommandImpl(state: State, args: CrossArgs): State = { val x = Project.extract(state) import x._ - - val (aggs, aggCommand) = parseCommand(args.command) match { - case Right((project, cmd)) => - (structure.allProjectRefs.filter(_.project == project), cmd) - case Left(cmd) => (resolveAggregates(x), cmd) - } + val (aggs, aggCommand) = parseSlashCommand(x)(args.command) val projCrossVersions = aggs map { proj => proj -> crossVersions(x, proj) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 751540de7..f9e57bc1a 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -222,6 +222,7 @@ object Keys { val scalaCompilerBridgeSource = settingKey[ModuleID]("Configures the module ID of the sources of the compiler bridge.").withRank(CSetting) val scalaArtifacts = settingKey[Seq[String]]("Configures the list of artifacts which should match the Scala binary version").withRank(CSetting) val enableBinaryCompileAnalysis = settingKey[Boolean]("Writes the analysis file in binary format") + val crossJavaVersions = settingKey[Seq[JavaVersion]]("The java versions used during JDK cross testing").withRank(BPlusSetting) val clean = taskKey[Unit]("Deletes files produced by the build, such as generated sources, compiled classes, and task caches.").withRank(APlusTask) val console = taskKey[Unit]("Starts the Scala interpreter with the project classes on the classpath.").withRank(APlusTask) @@ -273,7 +274,7 @@ object Keys { val javaHome = settingKey[Option[File]]("Selects the Java installation used for compiling and forking. If None, uses the Java installation running the build.").withRank(ASetting) val discoveredJavaHomes = settingKey[Map[JavaVersion, File]]("Discovered Java home directories") val javaHomes = settingKey[Map[JavaVersion, File]]("The user-defined additional Java home directories") - val fullJavaHomes = taskKey[Map[JavaVersion, File]]("Combines discoveredJavaHomes and custom javaHomes.").withRank(CTask) + val fullJavaHomes = settingKey[Map[JavaVersion, File]]("Combines discoveredJavaHomes and custom javaHomes.").withRank(CTask) val javaOptions = taskKey[Seq[String]]("Options passed to a new JVM when forking.").withRank(BPlusTask) val envVars = taskKey[Map[String, String]]("Environment variables used when forking a new JVM").withRank(BTask) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index b29cca948..d6185d41e 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -14,6 +14,7 @@ import sbt.internal.{ BuildUnit, CommandExchange, CommandStrings, + CrossJava, DefaultBackgroundJobService, EvaluateConfigurations, Inspect, @@ -190,6 +191,8 @@ object BuiltinCommands { oldLoadFailed, Cross.crossBuild, Cross.switchVersion, + CrossJava.switchJavaHome, + CrossJava.crossJavaHome, PluginCross.pluginCross, PluginCross.pluginSwitch, Cross.crossRestoreSession, diff --git a/main/src/main/scala/sbt/internal/CommandStrings.scala b/main/src/main/scala/sbt/internal/CommandStrings.scala index 99ec4637e..d30a31f29 100644 --- a/main/src/main/scala/sbt/internal/CommandStrings.scala +++ b/main/src/main/scala/sbt/internal/CommandStrings.scala @@ -415,4 +415,29 @@ $SwitchCommand [=][!] [-v] [] See also `help $CrossCommand` """ + + val JavaCrossCommand = "java+" + val JavaSwitchCommand = "java++" + + def javaCrossHelp: Help = Help.more(JavaCrossCommand, JavaCrossDetailed) + def javaSwitchHelp: Help = Help.more(JavaSwitchCommand, JavaSwitchDetailed) + + def JavaCrossDetailed = + s"""$JavaCrossCommand + Runs for each JDK version specified for cross-JDK testing. + For each string in `crossJavaVersions` in the current project, this command sets the + `javaHome` of all projects to the corresponding Java home, reloads the build, + and executes . When finished, it reloads the build with the original + `javaHome`. + Note that `Test / fork := true` is needed for `javaHome` to be effective. + See also `help $JavaSwitchCommand` +""" + + def JavaSwitchDetailed = + s"""$JavaSwitchCommand + Changes the JDK version and runs a command. + Sets the `javaHome` of all projects to and + reloads the build. If is provided, it is then executed. + See also `help $JavaSwitchCommand` +""" } diff --git a/main/src/main/scala/sbt/internal/CrossJava.scala b/main/src/main/scala/sbt/internal/CrossJava.scala index 1c035a4d8..0f8a4730b 100644 --- a/main/src/main/scala/sbt/internal/CrossJava.scala +++ b/main/src/main/scala/sbt/internal/CrossJava.scala @@ -10,11 +10,277 @@ package internal import java.io.File import scala.collection.immutable.ListMap +import sbt.io.Path import sbt.io.syntax._ +import sbt.Cross._ +import sbt.Def.{ ScopedKey, Setting } +import sbt.internal.util.complete.DefaultParsers._ +import sbt.internal.util.AttributeKey +import sbt.internal.util.complete.{ DefaultParsers, Parser } +import sbt.internal.CommandStrings.{ + JavaCrossCommand, + JavaSwitchCommand, + javaCrossHelp, + javaSwitchHelp +} private[sbt] object CrossJava { + // parses jabaa style version number adopt@1.8 + def parseJavaVersion(version: String): JavaVersion = { + def splitDot(s: String): Vector[Long] = + Option(s) match { + case Some(x) => x.split('.').toVector.filterNot(_ == "").map(_.toLong) + case _ => Vector() + } + def splitAt(s: String): Vector[String] = + Option(s) match { + case Some(x) => x.split('@').toVector + case _ => Vector() + } + splitAt(version) match { + case Vector(vendor, rest) => JavaVersion(splitDot(rest), Option(vendor)) + case Vector(rest) => JavaVersion(splitDot(rest), None) + case _ => sys.error(s"Invalid JavaVersion: $version") + } + } + + def lookupJavaHome(jv: JavaVersion, mappings: Map[JavaVersion, File]): File = { + mappings.get(jv) match { + case Some(dir) => dir + + // when looking for "10" it should match "openjdk@10" + case None if jv.vendor.isEmpty => + val noVendors: Map[JavaVersion, File] = mappings map { + case (k, v) => k.withVendor(None) -> v + } + noVendors.get(jv).getOrElse(javaHomeNotFound(jv, mappings)) + case _ => javaHomeNotFound(jv, mappings) + } + } + + private def javaHomeNotFound(version: JavaVersion, mappings: Map[JavaVersion, File]): Nothing = { + sys.error(s"""Java home for $version was not found in $mappings + | + |use Global / javaHomes += JavaVersion("$version") -> file(...)""".stripMargin) + } + + private case class SwitchTarget(version: Option[JavaVersion], home: Option[File], force: Boolean) + private case class SwitchJavaHome(target: SwitchTarget, verbose: Boolean, command: Option[String]) + + private def switchParser(state: State): Parser[SwitchJavaHome] = { + import DefaultParsers._ + def versionAndCommand(spacePresent: Boolean) = { + val x = Project.extract(state) + import x._ + val javaHomes = getJavaHomes(x, currentRef) + val knownVersions = javaHomes.keysIterator.map(_.numberStr).toVector + val version: Parser[SwitchTarget] = + (token( + (StringBasic <~ "@").? ~ ((NatBasic) ~ ("." ~> NatBasic).*) + .examples(knownVersions: _*) ~ "!".?) || token(StringBasic)) + .map { + case Left(((vendor, (v1, vs)), bang)) => + val force = bang.isDefined + val versionArg = (Vector(v1) ++ vs) map { _.toLong } + SwitchTarget(Option(JavaVersion(versionArg, vendor)), None, force) + case Right(home) => + SwitchTarget(None, Option(new File(home)), true) + } + val spacedVersion = + if (spacePresent) version + else version & spacedFirst(JavaSwitchCommand) + val verbose = Parser.opt(token(Space ~> "-v")) + val optionalCommand = Parser.opt(token(Space ~> matched(state.combinedParser))) + (spacedVersion ~ verbose ~ optionalCommand).map { + case v ~ verbose ~ command => + SwitchJavaHome(v, verbose.isDefined, command) + } + } + token(JavaSwitchCommand ~> OptSpace) flatMap { sp => + versionAndCommand(sp.nonEmpty) + } + } + + private def getJavaHomes(extracted: Extracted, + proj: ResolvedReference): Map[JavaVersion, File] = { + import extracted._ + (Keys.fullJavaHomes in proj get structure.data).get + } + + private def getCrossJavaVersions(extracted: Extracted, + proj: ResolvedReference): Seq[JavaVersion] = { + import extracted._ + import Keys._ + (crossJavaVersions in proj get structure.data).getOrElse(Nil) + } + + private def getCrossJavaHomes(extracted: Extracted, proj: ResolvedReference): Seq[File] = { + import extracted._ + import Keys._ + val fjh = (fullJavaHomes in proj get structure.data).get + (crossJavaVersions in proj get structure.data) map { jvs => + jvs map { jv => + lookupJavaHome(jv, fjh) + } + } getOrElse Vector() + } + + private def switchCommandImpl(state: State, switch: SwitchJavaHome): State = { + val extracted = Project.extract(state) + import extracted._ + import Keys.javaHome + + // filter out subprojects based on switch target e.g. "10" vs what's in crossJavaVersions + // for the subproject. Only if crossJavaVersions is non-empty, and does NOT include "10" + // it will skip the subproject. + val projects: Seq[(ResolvedReference, Seq[JavaVersion])] = { + val projectJavaVersions = + structure.allProjectRefs.map(proj => proj -> getCrossJavaVersions(extracted, proj)) + if (switch.target.force) projectJavaVersions + else + switch.target.version match { + case None => projectJavaVersions + case Some(v) => + projectJavaVersions flatMap { + case (proj, versions) => + if (versions.isEmpty || versions.contains(v)) Vector(proj -> versions) + else Vector() + } + } + } + + def setJavaHomeForProjects: State = { + val newSettings = projects.flatMap { + case (proj, javaVersions) => + val fjh = getJavaHomes(extracted, proj) + val home = switch.target match { + case SwitchTarget(Some(v), _, _) => lookupJavaHome(v, fjh) + case SwitchTarget(_, Some(h), _) => h + case _ => sys.error(s"unexpected ${switch.target}") + } + val scope = Scope(Select(proj), Zero, Zero, Zero) + Seq( + javaHome in scope := Some(home) + ) + } + + val filterKeys: Set[AttributeKey[_]] = Set(javaHome).map(_.key) + + val projectsContains: Reference => Boolean = projects.map(_._1).toSet.contains + + // Filter out any old javaHome version settings that were added, this is just for hygiene. + val filteredRawAppend = session.rawAppend.filter(_.key match { + case ScopedKey(Scope(Select(ref), Zero, Zero, Zero), key) + if filterKeys.contains(key) && projectsContains(ref) => + false + case _ => true + }) + + val newSession = session.copy(rawAppend = filteredRawAppend ++ newSettings) + + BuiltinCommands.reapply(newSession, structure, state) + } + + setJavaHomeForProjects + } + + def switchJavaHome: Command = + Command.arb(requireSession(switchParser), javaSwitchHelp)(switchCommandImpl) + + def crossJavaHome: Command = + Command.arb(requireSession(crossParser), javaCrossHelp)(crossJavaHomeCommandImpl) + + private case class CrossArgs(command: String, verbose: Boolean) + + /** + * Parse the given command into either an aggregate command or a command for a project + */ + private def crossParser(state: State): Parser[CrossArgs] = + token(JavaCrossCommand <~ OptSpace) flatMap { _ => + (token(Parser.opt("-v" <~ Space)) ~ token(matched(state.combinedParser))).map { + case (verbose, command) => CrossArgs(command, verbose.isDefined) + } & spacedFirst(JavaCrossCommand) + } + + private def crossJavaHomeCommandImpl(state: State, args: CrossArgs): State = { + val x = Project.extract(state) + import x._ + val (aggs, aggCommand) = Cross.parseSlashCommand(x)(args.command) + val projCrossVersions = aggs map { proj => + proj -> getCrossJavaHomes(x, proj) + } + // if we support javaHome, projVersions should be cached somewhere since + // running ++2.11.1 is at the root level is going to mess with the scalaVersion for the aggregated subproj + val projVersions = (projCrossVersions flatMap { + case (proj, versions) => versions map { proj.project -> _ } + }).toList + + val verbose = "" + // println(s"projVersions $projVersions") + + if (projVersions.isEmpty) { + state + } else { + // Detect whether a task or command has been issued + val allCommands = Parser.parse(aggCommand, Act.aggregatedKeyParser(x)) match { + case Left(_) => + // It's definitely not a task, check if it's a valid command, because we don't want to emit the warning + // message below for typos. + val validCommand = Parser.parse(aggCommand, state.combinedParser).isRight + + val distinctCrossConfigs = projCrossVersions.map(_._2.toSet).distinct + if (validCommand && distinctCrossConfigs.size > 1) { + state.log.warn( + "Issuing a Java cross building command, but not all sub projects have the same cross build " + + "configuration. This could result in subprojects cross building against Java versions that they are " + + "not compatible with. Try issuing cross building command with tasks instead, since sbt will be able " + + "to ensure that cross building is only done using configured project and Java version combinations " + + "that are configured.") + state.log.debug("Java versions configuration is:") + projCrossVersions.foreach { + case (project, versions) => state.log.debug(s"$project: $versions") + } + } + + // Execute using a blanket switch + projCrossVersions.toMap.apply(currentRef).flatMap { version => + // Force scala version + Seq(s"$JavaSwitchCommand $verbose $version!", aggCommand) + } + + case Right(_) => + // We have a key, we're likely to be able to cross build this using the per project behaviour. + + // Group all the projects by scala version + projVersions.groupBy(_._2).mapValues(_.map(_._1)).toSeq.flatMap { + case (version, Seq(project)) => + // If only one project for a version, issue it directly + Seq(s"$JavaSwitchCommand $verbose $version", s"$project/$aggCommand") + case (version, projects) if aggCommand.contains(" ") => + // If the command contains a space, then the `all` command won't work because it doesn't support issuing + // commands with spaces, so revert to running the command on each project one at a time + s"$JavaSwitchCommand $verbose $version" :: projects.map(project => + s"$project/$aggCommand") + case (version, projects) => + // First switch scala version, then use the all command to run the command on each project concurrently + Seq(s"$JavaSwitchCommand $verbose $version", + projects.map(_ + "/" + aggCommand).mkString("all ", " ", "")) + } + } + + allCommands.toList ::: captureCurrentSession(state, x) + } + } + + private val JavaCapturedSession = AttributeKey[Seq[Setting[_]]]("javaCrossCapturedSession") + + private def captureCurrentSession(state: State, extracted: Extracted): State = { + state.put(JavaCapturedSession, extracted.session.rawAppend) + } + def discoverJavaHomes: ListMap[JavaVersion, File] = { - val configs = Vector(JavaDiscoverConfig.linux, JavaDiscoverConfig.macOS) + import JavaDiscoverConfig._ + val configs = Vector(jabba, linux, macOS) ListMap(configs flatMap { _.javaHomes }: _*) } @@ -38,19 +304,36 @@ private[sbt] object CrossJava { def javaHomes: Vector[(JavaVersion, File)] = wrapNull(base.list()).collect { case dir @ JavaHomeDir(m, n) => - JavaVersion(n) -> (base / dir / "Contents" / "Home") + JavaVersion(nullBlank(m) + n) -> (base / dir / "Contents" / "Home") + } + } + + // See https://github.com/shyiko/jabba + val jabba = new JavaDiscoverConf { + val base: File = Path.userHome / ".jabba" / "jdk" + val JavaHomeDir = """([\w\-]+)\@(1\.)?([0-9]+).*""".r + def javaHomes: Vector[(JavaVersion, File)] = + wrapNull(base.list()).collect { + case dir @ JavaHomeDir(vendor, m, n) => + val v = JavaVersion(nullBlank(m) + n).withVendor(vendor) + if ((base / dir / "Contents" / "Home").exists) v -> (base / dir / "Contents" / "Home") + else v -> (base / dir) } } } - // expand Java versions to 1-8 to 1.x, and vice versa to accept both "1.8" and "8" - private val oneDot = Map((1 to 8).toVector flatMap { i => - Vector(s"$i" -> s"1.$i", s"1.$i" -> s"$i") + def nullBlank(s: String): String = + if (s eq null) "" + else s + + // expand Java versions to 1-20 to 1.x, and vice versa to accept both "1.8" and "8" + private val oneDot = Map((1L to 20L).toVector flatMap { i => + Vector(Vector(i) -> Vector(1L, i), Vector(1L, i) -> Vector(i)) }: _*) def expandJavaHomes(hs: Map[JavaVersion, File]): Map[JavaVersion, File] = hs flatMap { case (k, v) => - if (oneDot.contains(k.version)) Vector(k -> v, k.withVersion(oneDot(k.version)) -> v) + if (oneDot.contains(k.numbers)) Vector(k -> v, k.withNumbers(oneDot(k.numbers)) -> v) else Vector(k -> v) } diff --git a/sbt/src/sbt-test/java/cross/A.scala b/sbt/src/sbt-test/java/cross/A.scala new file mode 100644 index 000000000..e9ff1f72a --- /dev/null +++ b/sbt/src/sbt-test/java/cross/A.scala @@ -0,0 +1,16 @@ +package pkg + +import java.nio.file.{ Paths, Files } +import java.nio.charset.Charset + +object A extends App { + val out = Paths.get("out.txt") + val content = sys.props("java.version") + val w = Files.newBufferedWriter(out, Charset.forName("UTF-8")) + try { + w.write(content) + w.flush() + } finally { + w.close + } +} diff --git a/sbt/src/sbt-test/java/cross/build.sbt b/sbt/src/sbt-test/java/cross/build.sbt new file mode 100644 index 000000000..dad1dc347 --- /dev/null +++ b/sbt/src/sbt-test/java/cross/build.sbt @@ -0,0 +1,25 @@ +import complete.DefaultParsers._ + +val check = inputKey[Unit]("Runs the check") + +lazy val root = (project in file(".")) + .settings( + ThisBuild / scalaVersion := "2.12.6", + crossJavaVersions := List(JavaVersion("1.8")), + + // read out.txt and see if it starts with the passed in number + check := { + val arg1: Int = (Space ~> NatBasic).parsed + file("out.txt") match { + case out if out.exists => + IO.readLines(out).headOption match { + case Some(v) if v startsWith arg1.toString => () + case Some(v) if v startsWith s"1.$arg1" => () + case x => sys.error(s"unexpected value: $x") + } + case out => sys.error(s"$out doesn't exist") + } + }, + + Compile / run / fork := true, + ) diff --git a/sbt/src/sbt-test/java/cross/test b/sbt/src/sbt-test/java/cross/test new file mode 100644 index 000000000..ff76c51bf --- /dev/null +++ b/sbt/src/sbt-test/java/cross/test @@ -0,0 +1,6 @@ +> java+ run +> check 8 + +> java++ 10! +> run +> check 10 From 951eaa646f2cda69123ef8da8a2b5fd370106aa9 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 4 May 2018 19:09:25 -0400 Subject: [PATCH 319/356] jabba 0.9.6 (no sudo) Ref https://github.com/shyiko/jabba/issues/190 Bumping to jabba 0.9.6 fixes sporaditc permission issues. --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7f257e9ed..f75525898 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,11 +19,10 @@ matrix: matrix: include: - env: SBT_CMD="scripted java/*" - sudo: true before_install: - - curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.9.4/install.sh | bash && . ~/.jabba/jabba.sh + - curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.9.6/install.sh | bash && . ~/.jabba/jabba.sh install: - - sudo /home/travis/.jabba/bin/jabba install openjdk@1.10 + - /home/travis/.jabba/bin/jabba install openjdk@1.10 env: global: From 35e98f51fd386b15c27c678392e90acd44787cd1 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 5 May 2018 15:50:40 -0400 Subject: [PATCH 320/356] Adjust to upstream change --- main/src/main/scala/sbt/Cross.scala | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/main/src/main/scala/sbt/Cross.scala b/main/src/main/scala/sbt/Cross.scala index e68f2f20f..420432707 100644 --- a/main/src/main/scala/sbt/Cross.scala +++ b/main/src/main/scala/sbt/Cross.scala @@ -233,14 +233,11 @@ object Cross { args.command } else { args.command.map { rawCmd => - parseCommand(rawCmd) match { - case Right(_) => rawCmd // A project is specified, run as is - case Left(cmd) => - resolveAggregates(x) - .intersect(affectedRefs) - .collect { case ProjectRef(_, proj) => s"$proj/$cmd" } - .mkString("all ", " ", "") - } + val (aggs, aggCommand) = parseSlashCommand(x)(rawCmd) + aggs + .intersect(affectedRefs) + .map({ case ProjectRef(_, proj) => s"$proj/$aggCommand" }) + .mkString("all ", " ", "") } } From a7d85c87243b06ecbb5186902367a85197aa7c71 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 5 May 2018 15:51:29 -0400 Subject: [PATCH 321/356] Formatting --- main/src/main/scala/sbt/Cross.scala | 5 ++-- .../main/scala/sbt/internal/CrossJava.scala | 28 ++++++++++++------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/main/src/main/scala/sbt/Cross.scala b/main/src/main/scala/sbt/Cross.scala index 420432707..78571d78b 100644 --- a/main/src/main/scala/sbt/Cross.scala +++ b/main/src/main/scala/sbt/Cross.scala @@ -101,8 +101,9 @@ object Cross { /** * Parse the given command into a list of aggregate projects and command to issue. */ - private[sbt] def parseSlashCommand(extracted: Extracted)( - command: String): (Seq[ProjectRef], String) = { + private[sbt] def parseSlashCommand( + extracted: Extracted + )(command: String): (Seq[ProjectRef], String) = { import extracted._ import DefaultParsers._ val parser = (OpOrID <~ charClass(_ == '/', "/")) ~ any.* map { diff --git a/main/src/main/scala/sbt/internal/CrossJava.scala b/main/src/main/scala/sbt/internal/CrossJava.scala index 0f8a4730b..5836b2c17 100644 --- a/main/src/main/scala/sbt/internal/CrossJava.scala +++ b/main/src/main/scala/sbt/internal/CrossJava.scala @@ -77,7 +77,8 @@ private[sbt] object CrossJava { val version: Parser[SwitchTarget] = (token( (StringBasic <~ "@").? ~ ((NatBasic) ~ ("." ~> NatBasic).*) - .examples(knownVersions: _*) ~ "!".?) || token(StringBasic)) + .examples(knownVersions: _*) ~ "!".? + ) || token(StringBasic)) .map { case Left(((vendor, (v1, vs)), bang)) => val force = bang.isDefined @@ -101,14 +102,18 @@ private[sbt] object CrossJava { } } - private def getJavaHomes(extracted: Extracted, - proj: ResolvedReference): Map[JavaVersion, File] = { + private def getJavaHomes( + extracted: Extracted, + proj: ResolvedReference + ): Map[JavaVersion, File] = { import extracted._ (Keys.fullJavaHomes in proj get structure.data).get } - private def getCrossJavaVersions(extracted: Extracted, - proj: ResolvedReference): Seq[JavaVersion] = { + private def getCrossJavaVersions( + extracted: Extracted, + proj: ResolvedReference + ): Seq[JavaVersion] = { import extracted._ import Keys._ (crossJavaVersions in proj get structure.data).getOrElse(Nil) @@ -235,7 +240,8 @@ private[sbt] object CrossJava { "configuration. This could result in subprojects cross building against Java versions that they are " + "not compatible with. Try issuing cross building command with tasks instead, since sbt will be able " + "to ensure that cross building is only done using configured project and Java version combinations " + - "that are configured.") + "that are configured." + ) state.log.debug("Java versions configuration is:") projCrossVersions.foreach { case (project, versions) => state.log.debug(s"$project: $versions") @@ -259,12 +265,14 @@ private[sbt] object CrossJava { case (version, projects) if aggCommand.contains(" ") => // If the command contains a space, then the `all` command won't work because it doesn't support issuing // commands with spaces, so revert to running the command on each project one at a time - s"$JavaSwitchCommand $verbose $version" :: projects.map(project => - s"$project/$aggCommand") + s"$JavaSwitchCommand $verbose $version" :: projects + .map(project => s"$project/$aggCommand") case (version, projects) => // First switch scala version, then use the all command to run the command on each project concurrently - Seq(s"$JavaSwitchCommand $verbose $version", - projects.map(_ + "/" + aggCommand).mkString("all ", " ", "")) + Seq( + s"$JavaSwitchCommand $verbose $version", + projects.map(_ + "/" + aggCommand).mkString("all ", " ", "") + ) } } From 72ebdeb1990667add76c40f3c28c29281a32d310 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 7 May 2018 02:15:42 -0400 Subject: [PATCH 322/356] jabba 0.10.1 https://github.com/shyiko/jabba/blob/master/CHANGELOG.md#0101---2018-05-07 Ref https://github.com/shyiko/jabba/issues/190 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f75525898..08a61721f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ matrix: include: - env: SBT_CMD="scripted java/*" before_install: - - curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.9.6/install.sh | bash && . ~/.jabba/jabba.sh + - curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.10.1/install.sh | bash && . ~/.jabba/jabba.sh install: - /home/travis/.jabba/bin/jabba install openjdk@1.10 From 9b7c224f93ef9ca2a81d43cdd1a406f15531376b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 30 May 2018 00:59:12 -0400 Subject: [PATCH 323/356] use stringly-typed key so we can define it machine-wide --- main/src/main/scala/sbt/Keys.scala | 8 ++-- .../main/scala/sbt/internal/CrossJava.scala | 44 ++++++++++++------- sbt/src/sbt-test/java/cross/build.sbt | 2 +- .../sbt-test/java/home-discovery/build.sbt | 4 +- .../sbt/scriptedtest/ScriptedTests.scala | 1 + 5 files changed, 37 insertions(+), 22 deletions(-) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index f9e57bc1a..bf8c9549f 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -222,7 +222,7 @@ object Keys { val scalaCompilerBridgeSource = settingKey[ModuleID]("Configures the module ID of the sources of the compiler bridge.").withRank(CSetting) val scalaArtifacts = settingKey[Seq[String]]("Configures the list of artifacts which should match the Scala binary version").withRank(CSetting) val enableBinaryCompileAnalysis = settingKey[Boolean]("Writes the analysis file in binary format") - val crossJavaVersions = settingKey[Seq[JavaVersion]]("The java versions used during JDK cross testing").withRank(BPlusSetting) + val crossJavaVersions = settingKey[Seq[String]]("The java versions used during JDK cross testing").withRank(BPlusSetting) val clean = taskKey[Unit]("Deletes files produced by the build, such as generated sources, compiled classes, and task caches.").withRank(APlusTask) val console = taskKey[Unit]("Starts the Scala interpreter with the project classes on the classpath.").withRank(APlusTask) @@ -272,9 +272,9 @@ object Keys { val outputStrategy = settingKey[Option[sbt.OutputStrategy]]("Selects how to log output when running a main class.").withRank(DSetting) val connectInput = settingKey[Boolean]("If true, connects standard input when running a main class forked.").withRank(CSetting) val javaHome = settingKey[Option[File]]("Selects the Java installation used for compiling and forking. If None, uses the Java installation running the build.").withRank(ASetting) - val discoveredJavaHomes = settingKey[Map[JavaVersion, File]]("Discovered Java home directories") - val javaHomes = settingKey[Map[JavaVersion, File]]("The user-defined additional Java home directories") - val fullJavaHomes = settingKey[Map[JavaVersion, File]]("Combines discoveredJavaHomes and custom javaHomes.").withRank(CTask) + val discoveredJavaHomes = settingKey[Map[String, File]]("Discovered Java home directories") + val javaHomes = settingKey[Map[String, File]]("The user-defined additional Java home directories") + val fullJavaHomes = settingKey[Map[String, File]]("Combines discoveredJavaHomes and custom javaHomes.").withRank(CTask) val javaOptions = taskKey[Seq[String]]("Options passed to a new JVM when forking.").withRank(BPlusTask) val envVars = taskKey[Map[String, String]]("Environment variables used when forking a new JVM").withRank(BTask) diff --git a/main/src/main/scala/sbt/internal/CrossJava.scala b/main/src/main/scala/sbt/internal/CrossJava.scala index 5836b2c17..9ee668042 100644 --- a/main/src/main/scala/sbt/internal/CrossJava.scala +++ b/main/src/main/scala/sbt/internal/CrossJava.scala @@ -44,6 +44,11 @@ private[sbt] object CrossJava { } } + def lookupJavaHome(jv: String, mappings: Map[String, File]): File = { + val ms = mappings map { case (k, v) => (JavaVersion(k), v) } + lookupJavaHome(JavaVersion(jv), ms) + } + def lookupJavaHome(jv: JavaVersion, mappings: Map[JavaVersion, File]): File = { mappings.get(jv) match { case Some(dir) => dir @@ -72,7 +77,7 @@ private[sbt] object CrossJava { def versionAndCommand(spacePresent: Boolean) = { val x = Project.extract(state) import x._ - val javaHomes = getJavaHomes(x, currentRef) + val javaHomes = getJavaHomesTyped(x, currentRef) val knownVersions = javaHomes.keysIterator.map(_.numberStr).toVector val version: Parser[SwitchTarget] = (token( @@ -105,15 +110,22 @@ private[sbt] object CrossJava { private def getJavaHomes( extracted: Extracted, proj: ResolvedReference - ): Map[JavaVersion, File] = { + ): Map[String, File] = { import extracted._ (Keys.fullJavaHomes in proj get structure.data).get } + private def getJavaHomesTyped( + extracted: Extracted, + proj: ResolvedReference + ): Map[JavaVersion, File] = { + getJavaHomes(extracted, proj) map { case (k, v) => (JavaVersion(k), v) } + } + private def getCrossJavaVersions( extracted: Extracted, proj: ResolvedReference - ): Seq[JavaVersion] = { + ): Seq[String] = { import extracted._ import Keys._ (crossJavaVersions in proj get structure.data).getOrElse(Nil) @@ -138,7 +150,7 @@ private[sbt] object CrossJava { // filter out subprojects based on switch target e.g. "10" vs what's in crossJavaVersions // for the subproject. Only if crossJavaVersions is non-empty, and does NOT include "10" // it will skip the subproject. - val projects: Seq[(ResolvedReference, Seq[JavaVersion])] = { + val projects: Seq[(ResolvedReference, Seq[String])] = { val projectJavaVersions = structure.allProjectRefs.map(proj => proj -> getCrossJavaVersions(extracted, proj)) if (switch.target.force) projectJavaVersions @@ -157,7 +169,7 @@ private[sbt] object CrossJava { def setJavaHomeForProjects: State = { val newSettings = projects.flatMap { case (proj, javaVersions) => - val fjh = getJavaHomes(extracted, proj) + val fjh = getJavaHomesTyped(extracted, proj) val home = switch.target match { case SwitchTarget(Some(v), _, _) => lookupJavaHome(v, fjh) case SwitchTarget(_, Some(h), _) => h @@ -286,33 +298,33 @@ private[sbt] object CrossJava { state.put(JavaCapturedSession, extracted.session.rawAppend) } - def discoverJavaHomes: ListMap[JavaVersion, File] = { + def discoverJavaHomes: ListMap[String, File] = { import JavaDiscoverConfig._ val configs = Vector(jabba, linux, macOS) ListMap(configs flatMap { _.javaHomes }: _*) } sealed trait JavaDiscoverConf { - def javaHomes: Vector[(JavaVersion, File)] + def javaHomes: Vector[(String, File)] } object JavaDiscoverConfig { val linux = new JavaDiscoverConf { val base: File = file("/usr") / "lib" / "jvm" val JavaHomeDir = """java-([0-9]+)-.*""".r - def javaHomes: Vector[(JavaVersion, File)] = + def javaHomes: Vector[(String, File)] = wrapNull(base.list()).collect { - case dir @ JavaHomeDir(ver) => JavaVersion(ver) -> (base / dir) + case dir @ JavaHomeDir(ver) => JavaVersion(ver).toString -> (base / dir) } } val macOS = new JavaDiscoverConf { val base: File = file("/Library") / "Java" / "JavaVirtualMachines" val JavaHomeDir = """jdk-?(1\.)?([0-9]+).*""".r - def javaHomes: Vector[(JavaVersion, File)] = + def javaHomes: Vector[(String, File)] = wrapNull(base.list()).collect { case dir @ JavaHomeDir(m, n) => - JavaVersion(nullBlank(m) + n) -> (base / dir / "Contents" / "Home") + JavaVersion(nullBlank(m) + n).toString -> (base / dir / "Contents" / "Home") } } @@ -320,10 +332,10 @@ private[sbt] object CrossJava { val jabba = new JavaDiscoverConf { val base: File = Path.userHome / ".jabba" / "jdk" val JavaHomeDir = """([\w\-]+)\@(1\.)?([0-9]+).*""".r - def javaHomes: Vector[(JavaVersion, File)] = + def javaHomes: Vector[(String, File)] = wrapNull(base.list()).collect { case dir @ JavaHomeDir(vendor, m, n) => - val v = JavaVersion(nullBlank(m) + n).withVendor(vendor) + val v = JavaVersion(nullBlank(m) + n).withVendor(vendor).toString if ((base / dir / "Contents" / "Home").exists) v -> (base / dir / "Contents" / "Home") else v -> (base / dir) } @@ -338,10 +350,12 @@ private[sbt] object CrossJava { private val oneDot = Map((1L to 20L).toVector flatMap { i => Vector(Vector(i) -> Vector(1L, i), Vector(1L, i) -> Vector(i)) }: _*) - def expandJavaHomes(hs: Map[JavaVersion, File]): Map[JavaVersion, File] = + def expandJavaHomes(hs: Map[String, File]): Map[String, File] = hs flatMap { case (k, v) => - if (oneDot.contains(k.numbers)) Vector(k -> v, k.withNumbers(oneDot(k.numbers)) -> v) + val jv = JavaVersion(k) + if (oneDot.contains(jv.numbers)) + Vector(k -> v, jv.withNumbers(oneDot(jv.numbers)).toString -> v) else Vector(k -> v) } diff --git a/sbt/src/sbt-test/java/cross/build.sbt b/sbt/src/sbt-test/java/cross/build.sbt index dad1dc347..c71bd179e 100644 --- a/sbt/src/sbt-test/java/cross/build.sbt +++ b/sbt/src/sbt-test/java/cross/build.sbt @@ -5,7 +5,7 @@ val check = inputKey[Unit]("Runs the check") lazy val root = (project in file(".")) .settings( ThisBuild / scalaVersion := "2.12.6", - crossJavaVersions := List(JavaVersion("1.8")), + crossJavaVersions := List("1.8"), // read out.txt and see if it starts with the passed in number check := { diff --git a/sbt/src/sbt-test/java/home-discovery/build.sbt b/sbt/src/sbt-test/java/home-discovery/build.sbt index 7dd72c1d1..7177ca692 100644 --- a/sbt/src/sbt-test/java/home-discovery/build.sbt +++ b/sbt/src/sbt-test/java/home-discovery/build.sbt @@ -1,5 +1,5 @@ -Global / javaHomes += JavaVersion("6") -> file("/good/old/times/java-6") +Global / javaHomes += "6" -> file("/good/old/times/java-6") TaskKey[Unit]("check") := { - assert(fullJavaHomes.value(JavaVersion("1.6")).getAbsolutePath.contains("java-6")) + assert(fullJavaHomes.value("1.6").getAbsolutePath.contains("java-6")) } diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index dabaeeca1..77e3b7467 100644 --- a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -206,6 +206,7 @@ final class ScriptedTests( case "dependency-management/update-sbt-classifiers" => LauncherBased // tbd case "dependency-management/url" => LauncherBased // tbd case "java/argfile" => LauncherBased // sbt/Package$ + case "java/cross" => LauncherBased // sbt/Package$ case "java/basic" => LauncherBased // sbt/Package$ case "java/varargs-main" => LauncherBased // sbt/Package$ case "package/lazy-name" => LauncherBased // sbt/Package$ From 27e93601b57e037f2ba71de5683530591e249bf7 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Fri, 24 Nov 2017 12:43:50 -0800 Subject: [PATCH 324/356] Add warning for slow hostname lookups on OS X I spent a lot of time debugging why it took 5 seconds to run tests each time. It turns out that if the hostname is not set explicitly on os x, then getaddrinfo takes 5 seconds to try (and fail) to resolve the dns entry for the localhostname. This is easily fixed by setting the hostname, but it is not at all easy to figure out that a slow hostname lookup is the reason why tests are slow to start. I don't know if this is a common issue on other platforms, so only issue the warning on OS X. --- main/src/main/scala/sbt/internal/Load.scala | 4 +++- .../sbt/plugins/JUnitXmlReportPlugin.scala | 2 +- .../scala/sbt/JUnitXmlTestsListener.scala | 22 ++++++++++++++++--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 8a316bbc1..c496a7f32 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -1039,7 +1039,9 @@ private[sbt] object Load { // Grab all the settings we already loaded from sbt files def settings(files: Seq[File]): Seq[Setting[_]] = { if (files.nonEmpty) - log.info(s"${files.map(_.getName).mkString(s"Loading settings for project ${p.id} from ", ",", " ...")}") + log.info( + s"${files.map(_.getName).mkString(s"Loading settings for project ${p.id} from ", ",", " ...")}" + ) for { file <- files config <- (memoSettings get file).toSeq diff --git a/main/src/main/scala/sbt/plugins/JUnitXmlReportPlugin.scala b/main/src/main/scala/sbt/plugins/JUnitXmlReportPlugin.scala index b65324be7..232265d68 100644 --- a/main/src/main/scala/sbt/plugins/JUnitXmlReportPlugin.scala +++ b/main/src/main/scala/sbt/plugins/JUnitXmlReportPlugin.scala @@ -30,6 +30,6 @@ object JUnitXmlReportPlugin extends AutoPlugin { // It might be a good idea to derive this setting into specific test scopes. override lazy val projectSettings: Seq[Setting[_]] = Seq( - testListeners += new JUnitXmlTestsListener(target.value.getAbsolutePath) + testListeners += new JUnitXmlTestsListener(target.value.getAbsolutePath, streams.value.log) ) } diff --git a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala index 9344305f5..a59e44ee7 100644 --- a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala +++ b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala @@ -13,8 +13,10 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit import java.util.Hashtable +import java.util.concurrent.TimeUnit.NANOSECONDS import scala.collection.mutable.ListBuffer +import scala.util.Properties import scala.xml.{ Elem, Node => XNode, XML } import testing.{ Event => TEvent, @@ -23,6 +25,7 @@ import testing.{ OptionalThrowable, TestSelector } +import util.Logger import sbt.protocol.testing.TestResult /** @@ -30,14 +33,27 @@ import sbt.protocol.testing.TestResult * report format. * @param outputDir path to the dir in which a folder with results is generated */ -class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { +class JUnitXmlTestsListener(val outputDir: String, logger: Logger) extends TestsListener { + // This constructor is for binary compatibility with older versions of sbt. + def this(outputDir: String) = this(outputDir, null) /**Current hostname so we know which machine executed the tests*/ - val hostname = - try InetAddress.getLocalHost.getHostName + val hostname = { + val start = System.nanoTime + val name = try InetAddress.getLocalHost.getHostName catch { case _: IOException => "localhost" } + val elapsed = System.nanoTime - start + if ((NANOSECONDS.toSeconds(elapsed) >= 4) && Properties.isMac && logger != null) { + logger.warn( + s"Getting the hostname $name was slow (${elapsed / 1.0e6} ms). " + + "This is likely because the computer's hostname is not set. You can set the " + + "hostname with the command: scutil --set HostName $(scutil --get LocalHostName)." + ) + } + name + } /**The dir in which we put all result files. Is equal to the given dir + "/test-reports"*/ val targetDir = new File(outputDir + "/test-reports/") From 5fd774693c8ffb5f2fa37c90c3eb80ef75894cf9 Mon Sep 17 00:00:00 2001 From: Mark Canlas Date: Fri, 8 Jun 2018 18:52:34 -0400 Subject: [PATCH 325/356] sort and indent about plugins output --- main/src/main/scala/sbt/Main.scala | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 65b436be0..74781030f 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -238,9 +238,22 @@ object BuiltinCommands { } else "No project is currently loaded" def aboutPlugins(e: Extracted): String = { - def list(b: BuildUnit) = b.plugins.detected.autoPlugins.map(_.value.label) - val allPluginNames = e.structure.units.values.flatMap(u => list(u.unit)).toSeq.distinct - if (allPluginNames.isEmpty) "" else allPluginNames.mkString("Available Plugins: ", ", ", "") + def plugins(lbu: LoadedBuildUnit) = + lbu.unit.plugins.detected.autoPlugins + .map(_.value.label) + + val allPluginNames = + e.structure.units.values + .flatMap(plugins) + .toList + .distinct + .sorted + + if (allPluginNames.isEmpty) + "" + else + ("Available Plugins" +: allPluginNames) + .mkString("\n - ") } def aboutScala(s: State, e: Extracted): String = { From e465aee36abc3f6c52964e8a4fdb0232dd25e996 Mon Sep 17 00:00:00 2001 From: veera venky Date: Wed, 13 Jun 2018 03:06:30 +0530 Subject: [PATCH 326/356] Fix for #4191 (active.json should be removed on JVM shutdown) Added a shutdown hook to clean up active.json file --- main/src/main/scala/sbt/Main.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 65b436be0..67bc59857 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -105,11 +105,18 @@ object StandardMain { import scalacache.caffeine._ private[sbt] lazy val cache: Cache[Any] = CaffeineCache[Any] + private[sbt] val shutdownHook = new Thread(new Runnable { + def run(): Unit = { + exchange.shutdown + } + }) + def runManaged(s: State): xsbti.MainResult = { val previous = TrapExit.installManager() try { try { try { + Runtime.getRuntime.addShutdownHook(shutdownHook) MainLoop.runLogged(s) } finally exchange.shutdown } finally DefaultBackgroundJobService.backgroundJobService.shutdown() From 41fc25dba79e6728bc58a9b5745e279ee3a65eb6 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 13 Jun 2018 02:01:17 -0400 Subject: [PATCH 327/356] Fix scripted test --- scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index 77e3b7467..50883790e 100644 --- a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -167,6 +167,7 @@ final class ScriptedTests( case "actions/external-doc" => LauncherBased // sbt/Package$ case "actions/input-task" => LauncherBased // sbt/Package$ case "actions/input-task-dyn" => LauncherBased // sbt/Package$ + case "compiler-project/dotty-compiler-plugin" => LauncherBased // sbt/Package$ case "compiler-project/run-test" => LauncherBased // sbt/Package$ case "compiler-project/src-dep-plugin" => LauncherBased // sbt/Package$ case "dependency-management/artifact" => LauncherBased // tbd From 7b6ae46116436ec8a6de91575044b5c1f5bf9037 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 13 Jun 2018 03:52:21 -0400 Subject: [PATCH 328/356] -Dsbt.offline sets offline setting Fixes #771 --- main/src/main/scala/sbt/Defaults.scala | 2 +- notes/1.2.0/offline.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 notes/1.2.0/offline.md diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index d8c072cae..d1db1af98 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1861,7 +1861,7 @@ object Classpaths { licenses :== Nil, developers :== Nil, scmInfo :== None, - offline :== false, + offline :== java.lang.Boolean.getBoolean("sbt.offline"), defaultConfiguration :== Some(Configurations.Compile), dependencyOverrides :== Vector.empty, libraryDependencies :== Nil, diff --git a/notes/1.2.0/offline.md b/notes/1.2.0/offline.md new file mode 100644 index 000000000..6a0a9e68b --- /dev/null +++ b/notes/1.2.0/offline.md @@ -0,0 +1,2 @@ +- `-Dsbt.offline=true` now can set offline mode. + From 8ec0e0193e60ee1eb8c0c807352305a745e294b9 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 14 Jun 2018 02:51:09 -0400 Subject: [PATCH 329/356] sbt-houserules 0.3.7 --- project/build.properties | 2 +- project/plugins.sbt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/project/build.properties b/project/build.properties index 64cf32f7f..d6e35076c 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.4 +sbt.version=1.1.6 diff --git a/project/plugins.sbt b/project/plugins.sbt index 46054b0a0..5ded00d20 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,8 +1,7 @@ scalaVersion := "2.12.6" scalacOptions ++= Seq("-feature", "-language:postfixOps") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.2.0") -addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.6") +addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.7") addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.4.0") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.8.0") From e3c9eb0cd9dded109deaa08df4da4ad1eb17a265 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 14 Jun 2018 05:09:00 -0400 Subject: [PATCH 330/356] Remove the shutdown hook when it's done --- main/src/main/scala/sbt/Main.scala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 67bc59857..920a0c302 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -115,10 +115,20 @@ object StandardMain { val previous = TrapExit.installManager() try { try { - try { + val hooked = try { Runtime.getRuntime.addShutdownHook(shutdownHook) + true + } catch { + case _: IllegalArgumentException => false + } + try { MainLoop.runLogged(s) - } finally exchange.shutdown + } finally { + exchange.shutdown + if (hooked) { + Runtime.getRuntime.removeShutdownHook(shutdownHook) + } + } } finally DefaultBackgroundJobService.backgroundJobService.shutdown() } finally TrapExit.uninstallManager(previous) } From b3bcb3f47d2769013d3d3791a8c9679b285fc4ad Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 18 Jun 2018 00:36:43 -0400 Subject: [PATCH 331/356] Open up setPlugins to private[sbt] This would make it easier for projectMatrix to be a plugin. --- main/src/main/scala/sbt/Project.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index e78a47f14..fcf5654dc 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -270,7 +270,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference] with CompositeP def disablePlugins(ps: AutoPlugin*): Project = setPlugins(Plugins.and(plugins, Plugins.And(ps.map(p => Plugins.Exclude(p)).toList))) - private[this] def setPlugins(ns: Plugins): Project = copy2(plugins = ns) + private[sbt] def setPlugins(ns: Plugins): Project = copy2(plugins = ns) /** Definitively set the [[AutoPlugin]]s for this project. */ private[sbt] def setAutoPlugins(autos: Seq[AutoPlugin]): Project = copy2(autoPlugins = autos) From 932f911483410cee02d9bdffeb86d367b84288bc Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 18 Jun 2018 00:27:57 -0400 Subject: [PATCH 332/356] addPluginSbtFile command Fixes https://github.com/sbt/sbt/issues/1502 This adds `--addPluginSbtFile=` command, which adds the given .sbt file to the plugin build. Using this mechanism editors or IDEs can start a build with required plugin. ``` $ cat /tmp/extra.sbt addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.7") $ sbt --addPluginSbtFile=/tmp/extra.sbt ... sbt:helloworld> plugins In file:/xxxx/hellotest/ ... sbtassembly.AssemblyPlugin: enabled in root ``` --- .../main/scala/sbt/BasicCommandStrings.scala | 11 ++++- .../src/main/scala/sbt/BasicCommands.scala | 31 +++++++++++++- .../src/main/scala/sbt/BasicKeys.scala | 7 ++++ main/src/main/scala/sbt/Main.scala | 1 + main/src/main/scala/sbt/internal/Load.scala | 42 +++++++++++++++---- .../internal/server/SettingQueryTest.scala | 1 + notes/1.2.0/add-plugin.md | 14 +++++++ 7 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 notes/1.2.0/add-plugin.md diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index 482612f6f..d7da62d90 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -83,7 +83,8 @@ $HelpCommand List("-" + elem, "--" + elem) } (s.startsWith(EarlyCommand + "(") && s.endsWith(")")) || - (levelOptions contains s) + (levelOptions contains s) || + (s.startsWith("-" + AddPluginSbtFileCommand) || s.startsWith("--" + AddPluginSbtFileCommand)) } val EarlyCommand = "early" @@ -96,6 +97,14 @@ $HelpCommand The order is preserved between all early commands, so `sbt "early(a)" "early(b)"` executes `a` and `b` in order. """ + def addPluginSbtFileHelp = { + val brief = + (s"--$AddPluginSbtFileCommand=", "Adds the given *.sbt file to the plugin build.") + Help(brief) + } + + val AddPluginSbtFileCommand = "addPluginSbtFile" + def ReadCommand = "<" def ReadFiles = " file1 file2 ..." def ReadDetailed = diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index eee6311ac..896cd9fe1 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -66,15 +66,42 @@ object BasicCommands { private[this] def levelParser: Parser[String] = Iterator(Level.Debug, Level.Info, Level.Warn, Level.Error) map (l => token(l.toString)) reduce (_ | _) + private[this] def addPluginSbtFileParser: Parser[File] = { + token(AddPluginSbtFileCommand) ~> (":" | "=" | Space) ~> (StringBasic).examples( + "/some/extra.sbt" + ) map { + new File(_) + } + } + + private[this] def addPluginSbtFileStringParser: Parser[String] = { + token( + token(AddPluginSbtFileCommand) ~ (":" | "=" | Space) ~ (StringBasic) + .examples("/some/extra.sbt") map { + case s1 ~ s2 ~ s3 => s1 + s2 + s3 + } + ) + } + private[this] def earlyParser: State => Parser[String] = (s: State) => { val p1 = token(EarlyCommand + "(") flatMap (_ => otherCommandParser(s) <~ token(")")) - val p2 = token("-") flatMap (_ => levelParser) - val p3 = token("--") flatMap (_ => levelParser) + val p2 = (token("-") | token("--")) flatMap (_ => levelParser) + val p3 = (token("-") | token("--")) flatMap (_ => addPluginSbtFileStringParser) p1 | p2 | p3 } private[this] def earlyHelp = Help(EarlyCommand, EarlyCommandBrief, EarlyCommandDetailed) + /** + * Adds additional *.sbt to the plugin build. + * This must be combined with early command as: --addPluginSbtFile=/tmp/extra.sbt + */ + def addPluginSbtFile: Command = Command.arb(_ => addPluginSbtFileParser, addPluginSbtFileHelp) { + (s, extraSbtFile) => + val extraFiles = s.get(BasicKeys.extraMetaSbtFiles).toList.flatten + s.put(BasicKeys.extraMetaSbtFiles, extraFiles :+ extraSbtFile) + } + def help: Command = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser) def helpParser(s: State): Parser[() => State] = { diff --git a/main-command/src/main/scala/sbt/BasicKeys.scala b/main-command/src/main/scala/sbt/BasicKeys.scala index d1fc63f7a..9d34c27db 100644 --- a/main-command/src/main/scala/sbt/BasicKeys.scala +++ b/main-command/src/main/scala/sbt/BasicKeys.scala @@ -20,6 +20,13 @@ object BasicKeys { "The location where command line history is persisted.", 40 ) + + val extraMetaSbtFiles = AttributeKey[Seq[File]]( + "extraMetaSbtFile", + "Additional plugin.sbt files.", + 10000 + ) + val shellPrompt = AttributeKey[State => String]( "shell-prompt", "The function that constructs the command prompt from the current build state.", diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index d7dd60054..0e592984d 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -216,6 +216,7 @@ object BuiltinCommands { setLogLevel, plugin, plugins, + addPluginSbtFile, writeSbtVersion, notifyUsersAboutShell, shell, diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index c496a7f32..b0d8b53dc 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -714,7 +714,8 @@ private[sbt] object Load { createRoot, uri, config.pluginManagement.context, - Nil + Nil, + s.get(BasicKeys.extraMetaSbtFiles).getOrElse(Nil) ) val loadedProjectsRaw = timed("Load.loadUnit: loadedProjectsRaw", log) { loadProjects(initialProjects, !hasRootAlreadyDefined) @@ -844,7 +845,8 @@ private[sbt] object Load { makeOrDiscoverRoot: Boolean, buildUri: URI, context: PluginManagement.Context, - generatedConfigClassFiles: Seq[File] + generatedConfigClassFiles: Seq[File], + extraSbtFiles: Seq[File] ): LoadedProjects = /*timed(s"Load.loadTransitive(${ newProjects.map(_.id) })", log)*/ { // load all relevant configuration files (.sbt, as .scala already exists at this point) @@ -869,7 +871,11 @@ private[sbt] object Load { val autoPlugins: Seq[AutoPlugin] = try plugins.detected.deducePluginsFromProject(p1, log) catch { case e: AutoPluginException => throw translateAutoPluginException(e, p) } - val p2 = this.resolveProject(p1, autoPlugins, plugins, injectSettings, memoSettings, log) + val extra = + if (context.globalPluginProject) extraSbtFiles + else Nil + val p2 = + this.resolveProject(p1, autoPlugins, plugins, injectSettings, memoSettings, extra, log) val projectLevelExtra = if (expand) autoPlugins flatMap { _.derivedProjects(p2) map { _.setProjectOrigin(ProjectOrigin.DerivedProject) } @@ -888,7 +894,8 @@ private[sbt] object Load { (root, rest, files, generated) case DiscoveredProjects(None, rest, files, generated) => (p, rest, files, generated) } - val (finalRoot, projectLevelExtra) = finalizeProject(root, files, true) + val (finalRoot, projectLevelExtra) = + finalizeProject(root, files, true) (finalRoot, discovered ++ projectLevelExtra, generated) } @@ -910,11 +917,16 @@ private[sbt] object Load { false, buildUri, context, - generated ++ generatedConfigClassFiles + generated ++ generatedConfigClassFiles, + Nil ) case Nil if makeOrDiscoverRoot => log.debug(s"[Loading] Scanning directory ${buildBase}") - discover(AddSettings.defaultSbtFiles, buildBase) match { + val auto = + if (context.globalPluginProject) + AddSettings.seq(AddSettings.defaultSbtFiles, AddSettings.sbtFiles(extraSbtFiles: _*)) + else AddSettings.defaultSbtFiles + discover(auto, buildBase) match { case DiscoveredProjects(Some(root), discovered, files, generated) => log.debug( s"[Loading] Found root project ${root.id} w/ remaining ${discovered.map(_.id).mkString(",")}" @@ -935,7 +947,8 @@ private[sbt] object Load { false, buildUri, context, - generated ++ generatedConfigClassFiles + generated ++ generatedConfigClassFiles, + Nil ) // Here we need to create a root project... case DiscoveredProjects(None, discovered, files, generated) => @@ -953,6 +966,7 @@ private[sbt] object Load { false, buildUri, context, + Nil, Nil ) val otherGenerated = otherProjects.generatedConfigClassFiles @@ -1014,6 +1028,7 @@ private[sbt] object Load { * @param globalUserSettings All the settings contributed from the ~/.sbt/ directory * @param memoSettings A recording of all loaded files (our files should reside in there). We should need not load any * sbt file to resolve a project. + * @param extraSbtFiles Extra *.sbt files. * @param log A logger to report auto-plugin issues to. */ private[sbt] def resolveProject( @@ -1022,6 +1037,7 @@ private[sbt] object Load { loadedPlugins: LoadedPlugins, globalUserSettings: InjectSettings, memoSettings: mutable.Map[File, LoadedSbtFile], + extraSbtFiles: Seq[File], log: Logger ): Project = timed(s"Load.resolveProject(${p.id})", log) { @@ -1060,7 +1076,17 @@ private[sbt] object Load { b ++ expandSettings(add) } } - expandSettings(AddSettings.allDefaults) + val auto = + if (extraSbtFiles.nonEmpty) + AddSettings.seq( + AddSettings.autoPlugins, + AddSettings.buildScalaFiles, + AddSettings.userSettings, + AddSettings.defaultSbtFiles, + AddSettings.sbtFiles(extraSbtFiles: _*), + ) + else AddSettings.allDefaults + expandSettings(auto) } // Finally, a project we can use in buildStructure. p.copy(settings = allSettings) diff --git a/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala b/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala index b60121458..a971c818a 100644 --- a/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala +++ b/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala @@ -142,6 +142,7 @@ object SettingQueryTest extends org.specs2.mutable.Specification { loadedPlugins, injectSettings, fileToLoadedSbtFileMap, + Nil, state.log ) } diff --git a/notes/1.2.0/add-plugin.md b/notes/1.2.0/add-plugin.md new file mode 100644 index 000000000..913b3309b --- /dev/null +++ b/notes/1.2.0/add-plugin.md @@ -0,0 +1,14 @@ +Adds `--addPluginSbtFile=` command, which adds the given .sbt file to the plugin build. +Using this mechanism editors or IDEs can start a build with required plugin. + +``` +$ cat /tmp/extra.sbt +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.7") + +$ sbt --addPluginSbtFile=/tmp/extra.sbt +... +sbt:helloworld> plugins +In file:/xxxx/hellotest/ + ... + sbtassembly.AssemblyPlugin: enabled in root +``` From dc73fcfece0e7b3e1a82a3409e7c35ebb13799df Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 18 Jun 2018 04:20:50 -0400 Subject: [PATCH 333/356] Split server unit test on its own Ref https://github.com/sbt/sbt/issues/4186 This is an attempt to fix the flaky server test issue. --- .travis.yml | 2 +- build.sbt | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 08a61721f..91a3b818a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,7 +50,7 @@ notifications: # Undo _JAVA_OPTIONS environment variable before_script: - - _JAVA_OPTIONS= + - unset _JAVA_OPTIONS script: # It doesn't need that much memory because compile and run are forked diff --git a/build.sbt b/build.sbt index 7ee4bef71..93237deed 100644 --- a/build.sbt +++ b/build.sbt @@ -726,20 +726,12 @@ lazy val docProjects: ScopeFilter = ScopeFilter( ) lazy val safeUnitTests = taskKey[Unit]("Known working tests (for both 2.10 and 2.11)") lazy val safeProjects: ScopeFilter = ScopeFilter( - inProjects(mainSettingsProj, mainProj, actionsProj, runProj, stdTaskProj), + inAnyProject -- inProjects(sbtRoot, sbtProj), inConfigurations(Test) ) lazy val otherUnitTests = taskKey[Unit]("Unit test other projects") lazy val otherProjects: ScopeFilter = ScopeFilter( inProjects( - testingProj, - testAgentProj, - taskProj, - scriptedSbtProj, - scriptedPluginProj, - commandProj, - mainSettingsProj, - mainProj, sbtProj ), inConfigurations(Test) From ddf0f4f43db9e48a287a867f82c78607658598c1 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 18 Jun 2018 22:39:40 -0400 Subject: [PATCH 334/356] bump modules to 1.2.0-M1 --- project/Dependencies.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index bb2a81649..c528107dd 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,10 +8,10 @@ object Dependencies { val baseScalaVersion = scala212 // sbt modules - private val ioVersion = "1.1.10" - private val utilVersion = "1.1.3" - private val lmVersion = "1.1.5" - private val zincVersion = "1.1.7" + private val ioVersion = "1.2.0-M1" + private val utilVersion = "1.2.0-M1" + private val lmVersion = "1.2.0-M1" + private val zincVersion = "1.2.0-M1" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion From d07e432b4fddc848c9ea05deb925cacd748e0e56 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 19 Jun 2018 10:47:34 -0400 Subject: [PATCH 335/356] Use 1.x branch --- CONTRIBUTING.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed916ffd8..6192a8846 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -133,8 +133,6 @@ sbt uses two branches for development: - Development branch: `1.x` (this is also called "master") - Stable branch: `1.$MINOR.x`, where `$MINOR` is current minor version (e.g. `1.1.x` during 1.1.x series) -If you're working on a bug fix, it's a good idea to start with the `1.$MINOR.x` branch, since we can always safely merge from stable to `1.x`, but not other way around. - ### Instruction to build all modules from source 1. Install the current stable binary release of sbt (see [Setup]), which will be used to build sbt from source. @@ -144,7 +142,7 @@ If you're working on a bug fix, it's a good idea to start with the `1.$MINOR.x` $ mkdir sbt-modules $ cd sbt-modules $ for i in sbt io util librarymanagement zinc; do \ - git clone https://github.com/sbt/$i.git && (cd $i; git checkout -b 1.1.x origin/1.1.x) + git clone https://github.com/sbt/$i.git && (cd $i; git checkout -b 1.x origin/1.x) done $ cd sbt $ ./sbt-allsources.sh From d62a7465ae3b5da2179d0e682fc472203295f9d6 Mon Sep 17 00:00:00 2001 From: Anthony Garo Date: Tue, 19 Jun 2018 11:55:57 -0400 Subject: [PATCH 336/356] Addresses #4206 --- main/src/main/scala/sbt/Defaults.scala | 2 ++ main/src/main/scala/sbt/Keys.scala | 2 ++ 2 files changed, 4 insertions(+) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index d1db1af98..bd28131d9 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -559,6 +559,8 @@ object Defaults extends BuildCommon { mainClass := pickMainClassOrWarn(discoveredMainClasses.value, streams.value.log), runMain := foregroundRunMainTask.evaluated, run := foregroundRunTask.evaluated, + fgRun := runTask(fullClasspath, mainClass in run, runner in run).evaluated, + fgRunMain := runMainTask(fullClasspath, runner in run).evaluated, copyResources := copyResourcesTask.value, // note that we use the same runner and mainClass as plain run mainBgRunMainTaskForConfig(This), diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 45d9c2d96..10dfa7361 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -286,7 +286,9 @@ object Keys { val bgStop = inputKey[Unit]("Stop a background job by providing its ID.") val bgWaitFor = inputKey[Unit]("Wait for a background job to finish by providing its ID.") val bgRun = inputKey[JobHandle]("Start an application's default main class as a background job") + val fgRun = inputKey[Unit]("Start an application's default main class as a foreground job") val bgRunMain = inputKey[JobHandle]("Start a provided main class as a background job") + val fgRunMain = inputKey[Unit]("Start a provided main class as a foreground job") val bgCopyClasspath = settingKey[Boolean]("Copies classpath on bgRun to prevent conflict.") // Test Keys From 68d6cd5338bb2f5b301862af18973bb03c853ee3 Mon Sep 17 00:00:00 2001 From: Filipe Regadas Date: Tue, 19 Jun 2018 13:27:50 -0400 Subject: [PATCH 337/356] Add doc task to travis-ci --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 91a3b818a..d17c5e546 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ env: global: - secure: d3bu2KNwsVHwfhbGgO+gmRfDKBJhfICdCJFGWKf2w3Gv86AJZX9nuTYRxz0KtdvEHO5Xw8WTBZLPb2thSJqhw9OCm4J8TBAVqCP0ruUj4+aqBUFy4bVexQ6WKE6nWHs4JPzPk8c6uC1LG3hMuzlC8RGETXtL/n81Ef1u7NjyXjs= matrix: - - SBT_CMD=";mimaReportBinaryIssues ;scalafmt::test ;test:scalafmt::test ;sbt:scalafmt::test ;headerCheck ;test:headerCheck ;whitesourceCheckPolicies ;test:compile ;mainSettingsProj/test ;safeUnitTests ;otherUnitTests" + - SBT_CMD=";mimaReportBinaryIssues ;scalafmt::test ;test:scalafmt::test ;sbt:scalafmt::test ;headerCheck ;test:headerCheck ;whitesourceCheckPolicies ;test:compile ;mainSettingsProj/test ;safeUnitTests ;otherUnitTests; doc" - SBT_CMD="scripted actions/*" - SBT_CMD="scripted apiinfo/* compiler-project/* ivy-deps-management/*" - SBT_CMD="scripted dependency-management/*1of4" From 0fb07a861b2c528459dfc3fdeacd875cf86be69d Mon Sep 17 00:00:00 2001 From: Filipe Regadas Date: Tue, 19 Jun 2018 14:59:04 -0400 Subject: [PATCH 338/356] Add alternative scripted filenames --- main/src/main/scala/sbt/ScriptedPlugin.scala | 3 ++- .../sbt-test/project/test-script-file/build.sbt | 1 + .../sbt-test/project/test-script-file/test.script | 1 + .../scala/sbt/scriptedtest/ScriptedTests.scala | 15 ++++++++++----- 4 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 sbt/src/sbt-test/project/test-script-file/build.sbt create mode 100644 sbt/src/sbt-test/project/test-script-file/test.script diff --git a/main/src/main/scala/sbt/ScriptedPlugin.scala b/main/src/main/scala/sbt/ScriptedPlugin.scala index f1dfd3434..b17886904 100644 --- a/main/src/main/scala/sbt/ScriptedPlugin.scala +++ b/main/src/main/scala/sbt/ScriptedPlugin.scala @@ -120,7 +120,8 @@ object ScriptedPlugin extends AutoPlugin { private[sbt] def scriptedParser(scriptedBase: File): Parser[Seq[String]] = { import DefaultParsers._ - val scriptedFiles: NameFilter = ("test": NameFilter) | "pending" + val scriptedFiles + : NameFilter = ("test": NameFilter) | "test.script" | "pending" | "pending.script" val pairs = (scriptedBase * AllPassFilter * AllPassFilter * scriptedFiles).get map { (f: File) => val p = f.getParentFile diff --git a/sbt/src/sbt-test/project/test-script-file/build.sbt b/sbt/src/sbt-test/project/test-script-file/build.sbt new file mode 100644 index 000000000..c128b140e --- /dev/null +++ b/sbt/src/sbt-test/project/test-script-file/build.sbt @@ -0,0 +1 @@ +lazy val root = (project in file(".")) diff --git a/sbt/src/sbt-test/project/test-script-file/test.script b/sbt/src/sbt-test/project/test-script-file/test.script new file mode 100644 index 000000000..dfffb838b --- /dev/null +++ b/sbt/src/sbt-test/project/test-script-file/test.script @@ -0,0 +1 @@ +> test diff --git a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index 50883790e..92039e8c5 100644 --- a/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted/sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -8,7 +8,7 @@ package sbt package scriptedtest -import java.io.File +import java.io.{ File, FileNotFoundException } import java.net.SocketException import java.util.Properties import java.util.concurrent.ForkJoinPool @@ -17,7 +17,6 @@ import scala.collection.GenSeq import scala.collection.mutable import scala.collection.parallel.ForkJoinTaskSupport import scala.util.control.NonFatal - import sbt.internal.scripted._ import sbt.internal.io.Resources import sbt.internal.util.{ BufferedLogger, ConsoleLogger, FullLogger } @@ -386,9 +385,15 @@ final class ScriptedTests( if (bufferLog) log.record() val (file, pending) = { - val normal = new File(testDirectory, ScriptFilename) - val pending = new File(testDirectory, PendingScriptFilename) - if (pending.isFile) (pending, true) else (normal, false) + val normal = (new File(testDirectory, ScriptFilename), false) + val normalScript = (new File(testDirectory, s"$ScriptFilename.script"), false) + val pending = (new File(testDirectory, PendingScriptFilename), true) + val pendingScript = (new File(testDirectory, s"$PendingScriptFilename.script"), true) + + List(pending, pendingScript, normal, normalScript).find(_._1.isFile) match { + case Some(script) => script + case None => throw new FileNotFoundException("no test scripts found") + } } val pendingMark = if (pending) PendingLabel else "" From aee10cf036029b69dc39b502b2e331d7558cbd1c Mon Sep 17 00:00:00 2001 From: Daniel Riquelme Date: Fri, 22 Jun 2018 05:04:30 -0400 Subject: [PATCH 339/356] Fix CTRL-C exception --- testing/agent/src/main/java/sbt/ForkMain.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/testing/agent/src/main/java/sbt/ForkMain.java b/testing/agent/src/main/java/sbt/ForkMain.java index a92d02506..f5549231e 100644 --- a/testing/agent/src/main/java/sbt/ForkMain.java +++ b/testing/agent/src/main/java/sbt/ForkMain.java @@ -258,9 +258,19 @@ final public class ForkMain { final Task[] tasks = runner.tasks(filteredTests.toArray(new TaskDef[filteredTests.size()])); logDebug(os, "Runner for " + framework.getClass().getName() + " produced " + tasks.length + " initial tasks for " + filteredTests.size() + " tests."); + Thread callDoneOnShutdown = new Thread() { + @Override + public void run() { + runner.done(); + } + }; + Runtime.getRuntime().addShutdownHook(callDoneOnShutdown); + runTestTasks(executor, tasks, loggers, os); runner.done(); + + Runtime.getRuntime().removeShutdownHook(callDoneOnShutdown); } write(os, ForkTags.Done); is.readObject(); From 59465d9e1f8ed884ffb79b9ab8454e61d677acb9 Mon Sep 17 00:00:00 2001 From: Guillaume Poirier Date: Tue, 19 Jun 2018 15:03:14 -0400 Subject: [PATCH 340/356] Adding minimal support for commands in inspect There's also a special case for aliases that will try to resolve the target of the alias to a task key if possible and display the output of that key if found. see https://github.com/sbt/sbt/issues/2881 --- main/src/main/scala/sbt/Main.scala | 4 +- .../scala/sbt/internal/CommandStrings.scala | 5 +- .../src/main/scala/sbt/internal/Inspect.scala | 48 +++++++++++++++---- notes/1.2.0/inspect-alias.md | 7 +++ sbt/src/sbt-test/project/unified/build.sbt | 4 +- 5 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 notes/1.2.0/inspect-alias.md diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 0e592984d..49094156b 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -505,8 +505,8 @@ object BuiltinCommands { SettingCompletions.setThis(extracted, settings, arg) def inspect: Command = Command(InspectCommand, inspectBrief, inspectDetailed)(Inspect.parser) { - case (s, (option, sk)) => - s.log.info(Inspect.output(s, option, sk)) + case (s, f) => + s.log.info(f()) s } diff --git a/main/src/main/scala/sbt/internal/CommandStrings.scala b/main/src/main/scala/sbt/internal/CommandStrings.scala index d30a31f29..534945f18 100644 --- a/main/src/main/scala/sbt/internal/CommandStrings.scala +++ b/main/src/main/scala/sbt/internal/CommandStrings.scala @@ -120,10 +120,11 @@ $LastCommand "Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies." ) val inspectDetailed = s""" - |$InspectCommand + |$InspectCommand [-] | | For a plain setting, the value bound to the key argument is displayed using its toString method. - | Otherwise, the type of task ("Task" or "Input task") is displayed. + | For an alias, the command bound to the alias is displayed. + | Otherwise, the type of the key ("Task" or "Input task") is displayed. | | "Dependencies" shows the settings that this setting depends on. | diff --git a/main/src/main/scala/sbt/internal/Inspect.scala b/main/src/main/scala/sbt/internal/Inspect.scala index 405da6c78..a69123340 100644 --- a/main/src/main/scala/sbt/internal/Inspect.scala +++ b/main/src/main/scala/sbt/internal/Inspect.scala @@ -27,19 +27,18 @@ object Inspect { val Uses: Mode = UsesMode val Definitions: Mode = DefinitionsMode - def parser: State => Parser[(Inspect.Mode, ScopedKey[_])] = + def parser: State => Parser[() => String] = (s: State) => - spacedModeParser(s) flatMap { - case opt @ (UsesMode | DefinitionsMode) => - allKeyParser(s).map(key => (opt, Def.ScopedKey(Global, key))) - case opt @ (DependencyTreeMode | Details(_)) => spacedKeyParser(s).map(key => (opt, key)) + spacedModeParser(s) flatMap { mode => + commandHandler(s, mode) | keyHandler(s)(mode) } - val spacedModeParser: (State => Parser[Mode]) = (s: State) => { + val spacedModeParser: State => Parser[Mode] = (_: State) => { + val default = "-" ^^^ Details(false) val actual = "actual" ^^^ Details(true) val tree = "tree" ^^^ DependencyTree val uses = "uses" ^^^ Uses val definitions = "definitions" ^^^ Definitions - token(Space ~> (tree | actual | uses | definitions)) ?? Details(false) + token(Space ~> (default | tree | actual | uses | definitions)) ?? Details(false) } def allKeyParser(s: State): Parser[AttributeKey[_]] = { @@ -51,7 +50,40 @@ object Inspect { val spacedKeyParser: State => Parser[ScopedKey[_]] = (s: State) => Act.requireSession(s, token(Space) ~> Act.scopedKeyParser(s)) - def output(s: State, option: Mode, sk: Def.ScopedKey[_]): String = { + def keyHandler(s: State): Mode => Parser[() => String] = { + case opt @ (UsesMode | DefinitionsMode) => + allKeyParser(s).map(key => () => keyOutput(s, opt, Def.ScopedKey(Global, key))) + case opt @ (DependencyTreeMode | Details(_)) => + spacedKeyParser(s).map(key => () => keyOutput(s, opt, key)) + } + + def commandHandler(s: State, mode: Mode): Parser[() => String] = { + Space ~> commandParser(s).flatMap { + case (name, cmd) => + cmd.tags.get(BasicCommands.CommandAliasKey) match { + case Some((_, aliasFor)) => + def header = s"Alias for: $aliasFor" + Parser + .parse(" " ++ aliasFor, keyHandler(s)(mode)) + .fold( + // If we can't find a task key for the alias target + // we don't display anymore information + _ => success(() => header), + success + ) + case None => + success(() => s"Command: $name") + } + } + } + + def commandParser: State => Parser[(String, Command)] = { s => + oneOf(s.definedCommands.map(cmd => cmd -> cmd.nameOption) collect { + case (cmd, Some(name)) => DefaultParsers.literal(name).map(_ -> cmd) + }) + } + + def keyOutput(s: State, option: Mode, sk: Def.ScopedKey[_]): String = { val extracted = Project.extract(s) import extracted._ option match { diff --git a/notes/1.2.0/inspect-alias.md b/notes/1.2.0/inspect-alias.md new file mode 100644 index 000000000..1840791f1 --- /dev/null +++ b/notes/1.2.0/inspect-alias.md @@ -0,0 +1,7 @@ +[@ruippeixotog]: https://github.com/gpoirier + +[#2881]: https://github.com/sbt/sbt/issues/2881 + +### Improvements + +- Adding minimal support for commands in inspect. There's also a special case for aliases. diff --git a/sbt/src/sbt-test/project/unified/build.sbt b/sbt/src/sbt-test/project/unified/build.sbt index 7f456b66c..966beab44 100644 --- a/sbt/src/sbt-test/project/unified/build.sbt +++ b/sbt/src/sbt-test/project/unified/build.sbt @@ -38,8 +38,8 @@ lazy val root = (project in file(".")) testFrameworks += new TestFramework("utest.runner.Framework"), commands += Command("inspectCheck", inspectBrief, inspectDetailed)(Inspect.parser) { - case (s, (option, sk)) => - val actual = Inspect.output(s, option, sk) + case (s, f) => + val actual = f() val expected = s"""Task: Unit Description: \tExecutes all tests. From 14b9bd6e7a3207d1c707c0c930a36efd59c05d99 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 25 Jun 2018 17:02:32 -0400 Subject: [PATCH 341/356] IO 1.2.0-M2 Fixes https://github.com/sbt/sbt/issues/4223 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index c528107dd..a7e6e69b1 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,7 +8,7 @@ object Dependencies { val baseScalaVersion = scala212 // sbt modules - private val ioVersion = "1.2.0-M1" + private val ioVersion = "1.2.0-M2" private val utilVersion = "1.2.0-M1" private val lmVersion = "1.2.0-M1" private val zincVersion = "1.2.0-M1" From 444f0dc8b0df063a2961e9b687b579344411e22b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 25 Jun 2018 18:42:06 -0400 Subject: [PATCH 342/356] Fixes scripted test --- .../dependency-management/ext-pom-classifier/build.sbt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sbt/src/sbt-test/dependency-management/ext-pom-classifier/build.sbt b/sbt/src/sbt-test/dependency-management/ext-pom-classifier/build.sbt index 494d0b654..cfbfbdb21 100644 --- a/sbt/src/sbt-test/dependency-management/ext-pom-classifier/build.sbt +++ b/sbt/src/sbt-test/dependency-management/ext-pom-classifier/build.sbt @@ -1,3 +1,5 @@ -scalaVersion := "2.10.6" - -externalPom() +lazy val root = (project in file(".")) + .settings( + scalaVersion := "2.12.6", + externalPom() + ) From f3b8ed21968d53d78b0bc2d797a75ac93b302d59 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 19 Jun 2018 11:59:57 -0400 Subject: [PATCH 343/356] Use .sbtopts to increase the RAM --- .sbtopts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .sbtopts diff --git a/.sbtopts b/.sbtopts new file mode 100644 index 000000000..92a026140 --- /dev/null +++ b/.sbtopts @@ -0,0 +1,3 @@ +-J-Xms2048M +-J-Xmx2048M +-J-Xss2M From 18c6b04b47c58836450842ef7f6e476446f5e3b0 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 24 Jun 2018 05:01:14 -0400 Subject: [PATCH 344/356] Fix thin client to use LSP Fixes https://github.com/sbt/sbt/issues/2798 --- .../sbt/internal/client/NetworkClient.scala | 179 ++++++++++++------ .../internal/client/ServerConnection.scala | 106 ++++++++--- .../scala/sbt/protocol/Serialization.scala | 49 ++++- 3 files changed, 241 insertions(+), 93 deletions(-) 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 d3cd2dc0e..758f8712d 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -10,14 +10,19 @@ package internal package client import java.io.IOException -import java.net.{ URI, Socket, InetAddress } import java.util.UUID import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } import scala.collection.mutable.ListBuffer import scala.util.control.NonFatal +import scala.util.{ Success, Failure } import sbt.protocol._ +import sbt.internal.protocol._ +import sbt.internal.langserver.{ LogMessageParams, MessageType, PublishDiagnosticsParams } import sbt.internal.util.{ JLine, StringEvent, ConsoleAppender } import sbt.util.Level +import sbt.io.syntax._ +import sbt.io.IO +import sjsonnew.support.scalajson.unsafe.Converter class NetworkClient(arguments: List[String]) { self => private val channelName = new AtomicReference("_") @@ -28,64 +33,121 @@ class NetworkClient(arguments: List[String]) { self => private val console = ConsoleAppender("thin1") - def usageError = sys.error("Expecting: sbt client 127.0.0.1:port") val connection = init() + start() + // Open server connection based on the portfile def init(): ServerConnection = { - val u = arguments match { - case List(x) => - if (x contains "://") new URI(x) - else new URI("tcp://" + x) - case _ => usageError - } - val host = Option(u.getHost) match { - case None => usageError - case Some(x) => x - } - val port = Option(u.getPort) match { - case None => usageError - case Some(x) if x == -1 => usageError - case Some(x) => x - } - println(s"client on port $port") - val socket = new Socket(InetAddress.getByName(host), port) - new ServerConnection(socket) { - override def onEvent(event: EventMessage): Unit = self.onEvent(event) - override def onLogEntry(event: StringEvent): Unit = self.onLogEntry(event) + val portfile = (file("project") / "target" / "active.json").getAbsoluteFile + if (!portfile.exists) sys.error("server does not seem to be running.") + val (sk, tkn) = ClientSocket.socket(portfile) + val conn = new ServerConnection(sk) { + override def onNotification(msg: JsonRpcNotificationMessage): Unit = self.onNotification(msg) + override def onRequest(msg: JsonRpcRequestMessage): Unit = self.onRequest(msg) + override def onResponse(msg: JsonRpcResponseMessage): Unit = self.onResponse(msg) override def onShutdown(): Unit = { running.set(false) } } + // initiate handshake + val execId = UUID.randomUUID.toString + val initCommand = InitCommand(tkn, Option(execId)) + conn.sendString(Serialization.serializeCommandAsJsonMessage(initCommand)) + conn } - def onLogEntry(event: StringEvent): Unit = { - val level = event.level match { - case "debug" => Level.Debug - case "info" => Level.Info - case "warn" => Level.Warn - case "error" => Level.Error - } - console.appendLog(level, event.message) - } - - def onEvent(event: EventMessage): Unit = - event match { - case e: ChannelAcceptedEvent => - channelName.set(e.channelName) - println(event) - case e: ExecStatusEvent => - status.set(e.status) - // println(event) - e.execId foreach { execId => - if (e.status == "Done" && (pendingExecIds contains execId)) { - lock.synchronized { - pendingExecIds -= execId - } + /** Called on the response for a returning message. */ + def onReturningReponse(msg: JsonRpcResponseMessage): Unit = { + def printResponse(): Unit = { + msg.result match { + case Some(result) => + // ignore result JSON + console.success("completed") + case _ => + msg.error match { + case Some(err) => + // ignore err details + console.appendLog(Level.Error, "completed") + case _ => // ignore } - } - case e => println(e.toString) + } } + printResponse() + } + + def onResponse(msg: JsonRpcResponseMessage): Unit = { + msg.id foreach { + case execId if pendingExecIds contains execId => + onReturningReponse(msg) + lock.synchronized { + pendingExecIds -= execId + } + case _ => + } + } + + def onNotification(msg: JsonRpcNotificationMessage): Unit = { + def splitToMessage: Vector[(Level.Value, String)] = + (msg.method, msg.params) match { + case ("window/logMessage", Some(json)) => + import sbt.internal.langserver.codec.JsonProtocol._ + Converter.fromJson[LogMessageParams](json) match { + case Success(params) => splitLogMessage(params) + case Failure(e) => Vector() + } + case ("textDocument/publishDiagnostics", Some(json)) => + import sbt.internal.langserver.codec.JsonProtocol._ + Converter.fromJson[PublishDiagnosticsParams](json) match { + case Success(params) => splitDiagnostics(params) + case Failure(e) => Vector() + } + case _ => + Vector( + ( + Level.Warn, + s"unknown event: ${msg.method} " + Serialization.compactPrintJsonOpt(msg.params) + ) + ) + } + splitToMessage foreach { + case (level, msg) => console.appendLog(level, msg) + } + } + + def splitLogMessage(params: LogMessageParams): Vector[(Level.Value, String)] = { + val level = messageTypeToLevel(params.`type`) + Vector((level, params.message)) + } + + def messageTypeToLevel(severity: Long): Level.Value = { + severity match { + case MessageType.Error => Level.Error + case MessageType.Warning => Level.Warn + case MessageType.Info => Level.Info + case MessageType.Log => Level.Debug + } + } + + def splitDiagnostics(params: PublishDiagnosticsParams): Vector[(Level.Value, String)] = { + val uri = new URI(params.uri) + val f = IO.toFile(uri) + + params.diagnostics map { d => + val level = d.severity match { + case Some(severity) => messageTypeToLevel(severity) + case _ => Level.Error + } + val line = d.range.start.line + 1 + val offset = d.range.start.character + 1 + val msg = s"$f:$line:$offset: ${d.message}" + (level, msg) + } + } + + def onRequest(msg: JsonRpcRequestMessage): Unit = { + // ignore + } def start(): Unit = { val reader = JLine.simple(None, JLine.HandleCONT, injectThreadSleep = true) @@ -93,12 +155,8 @@ class NetworkClient(arguments: List[String]) { self => reader.readLine("> ", None) match { case Some("exit") => running.set(false) - case Some(s) => - val execId = UUID.randomUUID.toString - publishCommand(ExecCommand(s, execId)) - lock.synchronized { - pendingExecIds += execId - } + case Some(s) if s.trim.nonEmpty => + val execId = sendExecCommand(s) while (pendingExecIds contains execId) { Thread.sleep(100) } @@ -107,10 +165,19 @@ class NetworkClient(arguments: List[String]) { self => } } - def publishCommand(command: CommandMessage): Unit = { - val bytes = Serialization.serializeCommand(command) + def sendExecCommand(commandLine: String): String = { + val execId = UUID.randomUUID.toString + sendCommand(ExecCommand(commandLine, execId)) + lock.synchronized { + pendingExecIds += execId + } + execId + } + + def sendCommand(command: CommandMessage): Unit = { try { - connection.publish(bytes) + val s = Serialization.serializeCommandAsJsonMessage(command) + connection.sendString(s) } catch { case _: IOException => // log.debug(e.getMessage) diff --git a/main-command/src/main/scala/sbt/internal/client/ServerConnection.scala b/main-command/src/main/scala/sbt/internal/client/ServerConnection.scala index 530e117e1..e1950cf33 100644 --- a/main-command/src/main/scala/sbt/internal/client/ServerConnection.scala +++ b/main-command/src/main/scala/sbt/internal/client/ServerConnection.scala @@ -12,11 +12,12 @@ package client import java.net.{ SocketTimeoutException, Socket } import java.util.concurrent.atomic.AtomicBoolean import sbt.protocol._ -import sbt.internal.util.StringEvent +import sbt.internal.protocol._ abstract class ServerConnection(connection: Socket) { private val running = new AtomicBoolean(true) + private val retByte: Byte = '\r'.toByte private val delimiter: Byte = '\n'.toByte private val out = connection.getOutputStream @@ -28,32 +29,63 @@ abstract class ServerConnection(connection: Socket) { val in = connection.getInputStream connection.setSoTimeout(5000) var buffer: Vector[Byte] = Vector.empty - var bytesRead = 0 - while (bytesRead != -1 && running.get) { - try { - bytesRead = in.read(readBuffer) - buffer = buffer ++ readBuffer.toVector.take(bytesRead) - // handle un-framing - var delimPos = buffer.indexOf(delimiter) - while (delimPos > -1) { - val chunk = buffer.take(delimPos) - buffer = buffer.drop(delimPos + 1) + def readFrame: Array[Byte] = { + def getContentLength: Int = { + readLine.drop(16).toInt + } + val l = getContentLength + readLine + readLine + readContentLength(l) + } - Serialization - .deserializeEvent(chunk) - .fold( - { errorDesc => - val s = new String(chunk.toArray, "UTF-8") - println(s"Got invalid chunk from server: $s \n" + errorDesc) - }, - _ match { - case event: EventMessage => onEvent(event) - case event: StringEvent => onLogEntry(event) - } - ) - delimPos = buffer.indexOf(delimiter) + def readLine: String = { + if (buffer.isEmpty) { + val bytesRead = in.read(readBuffer) + if (bytesRead > 0) { + buffer = buffer ++ readBuffer.toVector.take(bytesRead) } + } + val delimPos = buffer.indexOf(delimiter) + if (delimPos > 0) { + val chunk0 = buffer.take(delimPos) + buffer = buffer.drop(delimPos + 1) + // remove \r at the end of line. + val chunk1 = if (chunk0.lastOption contains retByte) chunk0.dropRight(1) else chunk0 + new String(chunk1.toArray, "utf-8") + } else readLine + } + def readContentLength(length: Int): Array[Byte] = { + if (buffer.size < length) { + val bytesRead = in.read(readBuffer) + if (bytesRead > 0) { + buffer = buffer ++ readBuffer.toVector.take(bytesRead) + } + } + if (length <= buffer.size) { + val chunk = buffer.take(length) + buffer = buffer.drop(length) + chunk.toArray + } else readContentLength(length) + } + + while (running.get) { + try { + val frame = readFrame + Serialization + .deserializeJsonMessage(frame) + .fold( + { errorDesc => + val s = new String(frame.toArray, "UTF-8") + println(s"Got invalid chunk from server: $s \n" + errorDesc) + }, + _ match { + case msg: JsonRpcRequestMessage => onRequest(msg) + case msg: JsonRpcResponseMessage => onResponse(msg) + case msg: JsonRpcNotificationMessage => onNotification(msg) + } + ) } catch { case _: SocketTimeoutException => // its ok } @@ -65,14 +97,28 @@ abstract class ServerConnection(connection: Socket) { } thread.start() - def publish(command: Array[Byte]): Unit = { - out.write(command) - out.write(delimiter.toInt) - out.flush() + def sendString(message: String): Unit = { + val a = message.getBytes("UTF-8") + writeLine(s"""Content-Length: ${a.length + 2}""".getBytes("UTF-8")) + writeLine(Array()) + writeLine(a) } - def onEvent(event: EventMessage): Unit - def onLogEntry(event: StringEvent): Unit + def writeLine(a: Array[Byte]): Unit = { + def writeEndLine(): Unit = { + out.write(retByte.toInt) + out.write(delimiter.toInt) + out.flush + } + if (a.nonEmpty) { + out.write(a) + } + writeEndLine + } + + def onRequest(msg: JsonRpcRequestMessage): Unit + def onResponse(msg: JsonRpcResponseMessage): Unit + def onNotification(msg: JsonRpcNotificationMessage): Unit def onShutdown(): Unit diff --git a/protocol/src/main/scala/sbt/protocol/Serialization.scala b/protocol/src/main/scala/sbt/protocol/Serialization.scala index 21d798b80..4d71f512b 100644 --- a/protocol/src/main/scala/sbt/protocol/Serialization.scala +++ b/protocol/src/main/scala/sbt/protocol/Serialization.scala @@ -12,6 +12,7 @@ import sjsonnew.{ JsonFormat, JsonWriter } import sjsonnew.support.scalajson.unsafe.{ Parser, Converter, CompactPrinter } import sjsonnew.shaded.scalajson.ast.unsafe.{ JValue, JObject, JString } import java.nio.ByteBuffer +import java.util.UUID import scala.util.{ Success, Failure } import sbt.internal.util.StringEvent import sbt.internal.protocol.{ @@ -35,6 +36,33 @@ object Serialization { CompactPrinter(json).getBytes("UTF-8") } + private[sbt] def serializeCommandAsJsonMessage(command: CommandMessage): String = { + import sjsonnew.BasicJsonProtocol._ + + command match { + case x: InitCommand => + val execId = x.execId.getOrElse(UUID.randomUUID.toString) + val opt = x.token match { + case Some(t) => + val json: JValue = Converter.toJson[String](t).get + val v = CompactPrinter(json) + s"""{ "token": $v }""" + case None => "{}" + } + s"""{ "jsonrpc": "2.0", "id": "$execId", "method": "initialize", "params": { "initializationOptions": $opt } }""" + case x: ExecCommand => + val execId = x.execId.getOrElse(UUID.randomUUID.toString) + val json: JValue = Converter.toJson[String](x.commandLine).get + val v = CompactPrinter(json) + s"""{ "jsonrpc": "2.0", "id": "$execId", "method": "sbt/exec", "params": { "commandLine": $v } }""" + case x: SettingQuery => + val execId = UUID.randomUUID.toString + val json: JValue = Converter.toJson[String](x.setting).get + val v = CompactPrinter(json) + s"""{ "jsonrpc": "2.0", "id": "$execId", "method": "sbt/setting", "params": { "setting": $v } }""" + } + } + def serializeEventMessage(event: EventMessage): Array[Byte] = { import codec.JsonProtocol._ val json: JValue = Converter.toJson[EventMessage](event).get @@ -141,18 +169,25 @@ object Serialization { private[sbt] def deserializeJsonMessage(bytes: Seq[Byte]): Either[String, JsonRpcMessage] = { val buffer = ByteBuffer.wrap(bytes.toArray) Parser.parseFromByteBuffer(buffer) match { - case Success(json) => + case Success(json @ JObject(fields)) => import sbt.internal.protocol.codec.JsonRPCProtocol._ - Converter.fromJson[JsonRpcRequestMessage](json) match { - case Success(request) if (request.id.nonEmpty) => Right(request) - case Failure(e) => throw e - case _ => { + if ((fields find { _.field == "method" }).isDefined) { + if ((fields find { _.field == "id" }).isDefined) + Converter.fromJson[JsonRpcRequestMessage](json) match { + case Success(request) => Right(request) + case Failure(e) => Left(s"Conversion error: ${e.getMessage}") + } else Converter.fromJson[JsonRpcNotificationMessage](json) match { case Success(notification) => Right(notification) - case Failure(e) => throw e + case Failure(e) => Left(s"Conversion error: ${e.getMessage}") } + } else + Converter.fromJson[JsonRpcResponseMessage](json) match { + case Success(res) => Right(res) + case Failure(e) => Left(s"Conversion error: ${e.getMessage}") } - } + case Success(json) => + Left(s"Expected JSON object but found $json") case Failure(e) => Left(s"Parse error: ${e.getMessage}") } From 1a1f5309854ebe00a07babc4324851a440c01c76 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 24 Jun 2018 23:37:28 -0400 Subject: [PATCH 345/356] implement -client option --- .../main/scala/sbt/BasicCommandStrings.scala | 2 ++ .../src/main/scala/sbt/BasicCommands.scala | 2 +- .../src/main/scala/sbt/CommandUtil.scala | 4 +++ .../sbt/internal/client/NetworkClient.scala | 8 ++--- main/src/main/scala/sbt/Main.scala | 34 +++++++++++++------ 5 files changed, 34 insertions(+), 16 deletions(-) diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index d7da62d90..f7f728a38 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -194,6 +194,8 @@ $AliasCommand name= def Client = "client" def ClientDetailed = "Provides an interactive prompt from which commands can be run on a server." + def DashClient = "-client" + def DashDashClient = "--client" def StashOnFailure = "sbtStashOnFailure" def PopOnFailure = "sbtPopOnFailure" diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index 896cd9fe1..cc58497a6 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -297,7 +297,7 @@ object BasicCommands { case e :: Nil if e.commandLine == "shell" => Nil case xs => xs map (_.commandLine) }) - NetworkClient.run(arguments) + NetworkClient.run(s0.configuration.baseDirectory, arguments) "exit" :: s0.copy(remainingCommands = Nil) } diff --git a/main-command/src/main/scala/sbt/CommandUtil.scala b/main-command/src/main/scala/sbt/CommandUtil.scala index 4087b5932..6bdfcf0be 100644 --- a/main-command/src/main/scala/sbt/CommandUtil.scala +++ b/main-command/src/main/scala/sbt/CommandUtil.scala @@ -15,6 +15,7 @@ import sbt.internal.util.complete.Parser import sbt.internal.util.complete.DefaultParsers._ import sbt.io.IO +import sbt.io.syntax._ object CommandUtil { def readLines(files: Seq[File]): Seq[String] = @@ -89,4 +90,7 @@ object CommandUtil { details.map { case (k, v) => k + "\n\n " + v } mkString ("\n", "\n\n", "\n") final val HelpPatternFlags = Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE + + private[sbt] def isSbtBuild(baseDir: File) = + (baseDir / "project").exists() || (baseDir * "*.sbt").get.nonEmpty } 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 758f8712d..90ef086e3 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -24,7 +24,7 @@ import sbt.io.syntax._ import sbt.io.IO import sjsonnew.support.scalajson.unsafe.Converter -class NetworkClient(arguments: List[String]) { self => +class NetworkClient(baseDirectory: File, arguments: List[String]) { self => private val channelName = new AtomicReference("_") private val status = new AtomicReference("Ready") private val lock: AnyRef = new AnyRef {} @@ -39,7 +39,7 @@ class NetworkClient(arguments: List[String]) { self => // Open server connection based on the portfile def init(): ServerConnection = { - val portfile = (file("project") / "target" / "active.json").getAbsoluteFile + val portfile = baseDirectory / "project" / "target" / "active.json" if (!portfile.exists) sys.error("server does not seem to be running.") val (sk, tkn) = ClientSocket.socket(portfile) val conn = new ServerConnection(sk) { @@ -190,9 +190,9 @@ class NetworkClient(arguments: List[String]) { self => } object NetworkClient { - def run(arguments: List[String]): Unit = + def run(baseDirectory: File, arguments: List[String]): Unit = try { - new NetworkClient(arguments) + new NetworkClient(baseDirectory, arguments) () } catch { case NonFatal(e) => println(e.getMessage) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 49094156b..720d3c10d 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -64,15 +64,30 @@ import CommandStrings.BootCommand final class xMain extends xsbti.AppMain { def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = { import BasicCommands.early - import BasicCommandStrings.runEarly + import BasicCommandStrings.{ runEarly, DashClient, DashDashClient } import BuiltinCommands.defaults import sbt.internal.CommandStrings.{ BootCommand, DefaultsCommand, InitCommand } - val state = StandardMain.initialState( - configuration, - Seq(defaults, early), - runEarly(DefaultsCommand) :: runEarly(InitCommand) :: BootCommand :: Nil - ) - StandardMain.runManaged(state) + import sbt.internal.client.NetworkClient + + // if we detect -Dsbt.client=true or -client, run thin client. + val clientModByEnv = java.lang.Boolean.getBoolean("sbt.client") + val userCommands = configuration.arguments.map(_.trim) + if (clientModByEnv || (userCommands.exists { cmd => + (cmd == DashClient) || (cmd == DashDashClient) + })) { + val args = userCommands.toList filterNot { cmd => + (cmd == DashClient) || (cmd == DashDashClient) + } + NetworkClient.run(configuration.baseDirectory, args) + Exit(0) + } else { + val state = StandardMain.initialState( + configuration, + Seq(defaults, early), + runEarly(DefaultsCommand) :: runEarly(InitCommand) :: BootCommand :: Nil + ) + StandardMain.runManaged(state) + } } } @@ -861,9 +876,6 @@ object BuiltinCommands { private val sbtVersionRegex = """sbt\.version\s*=.*""".r private def isSbtVersionLine(s: String) = sbtVersionRegex.pattern matcher s matches () - private def isSbtProject(baseDir: File, projectDir: File) = - projectDir.exists() || (baseDir * "*.sbt").get.nonEmpty - private def writeSbtVersionUnconditionally(state: State) = { val baseDir = state.baseDir val sbtVersion = BuiltinCommands.sbtVersion(state) @@ -877,7 +889,7 @@ object BuiltinCommands { if (sbtVersionAbsent) { val warnMsg = s"No sbt.version set in project/build.properties, base directory: $baseDir" try { - if (isSbtProject(baseDir, projectDir)) { + if (isSbtBuild(baseDir)) { val line = s"sbt.version=$sbtVersion" IO.writeLines(buildProps, line :: buildPropsLines) state.log info s"Updated file $buildProps: set sbt.version to $sbtVersion" From 5c202a564bcaa7a7d7b5e233be78559f63cb6efb Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 24 Jun 2018 23:39:36 -0400 Subject: [PATCH 346/356] skip debug log --- .../src/main/scala/sbt/internal/client/NetworkClient.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 90ef086e3..171eb98a4 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -117,7 +117,8 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self => def splitLogMessage(params: LogMessageParams): Vector[(Level.Value, String)] = { val level = messageTypeToLevel(params.`type`) - Vector((level, params.message)) + if (level == Level.Debug) Vector() + else Vector((level, params.message)) } def messageTypeToLevel(severity: Long): Level.Value = { From 3eb76125e068ae602cdd3fc7b42bb5ab8a3f5524 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 24 Jun 2018 23:39:51 -0400 Subject: [PATCH 347/356] implement batch mode --- .../sbt/internal/client/NetworkClient.scala | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) 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 171eb98a4..e234bb4f0 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -18,7 +18,7 @@ import scala.util.{ Success, Failure } import sbt.protocol._ import sbt.internal.protocol._ import sbt.internal.langserver.{ LogMessageParams, MessageType, PublishDiagnosticsParams } -import sbt.internal.util.{ JLine, StringEvent, ConsoleAppender } +import sbt.internal.util.{ JLine, ConsoleAppender } import sbt.util.Level import sbt.io.syntax._ import sbt.io.IO @@ -33,7 +33,7 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self => private val console = ConsoleAppender("thin1") - val connection = init() + lazy val connection = init() start() @@ -151,9 +151,31 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self => } def start(): Unit = { + console.appendLog(Level.Info, "entering *experimental* thin client - BEEP WHIRR") + val userCommands = arguments filterNot { cmd => + cmd.startsWith("-") + } + if (userCommands.isEmpty) shell() + else batchExecute(userCommands) + } + + def batchExecute(userCommands: List[String]): Unit = { + userCommands foreach { cmd => + println("> " + cmd) + val execId = sendExecCommand(cmd) + while (pendingExecIds contains execId) { + Thread.sleep(100) + } + } + } + + def shell(): Unit = { val reader = JLine.simple(None, JLine.HandleCONT, injectThreadSleep = true) while (running.get) { reader.readLine("> ", None) match { + case Some("shutdown") => + // `sbt -client shutdown` shuts down the server + sendExecCommand("exit") case Some("exit") => running.set(false) case Some(s) if s.trim.nonEmpty => From f3038167a5cb8661abb3e7cdb43a98c45cb90d5c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 25 Jun 2018 03:02:26 -0400 Subject: [PATCH 348/356] Fork server if it's not running Fixes https://github.com/sbt/sbt/issues/3508 This forks an instance of sbt in the background when it's not running already. ``` $ time sbt -client compile Getting org.scala-sbt sbt 1.2.0-SNAPSHOT (this may take some time)... :: retrieving :: org.scala-sbt#boot-app confs: [default] 79 artifacts copied, 0 already retrieved (28214kB/130ms) [info] entering *experimental* thin client - BEEP WHIRR [info] server was not detected. starting an instance [info] waiting for the server... [info] waiting for the server... [info] server found > compile [success] completed sbt -client compile 9.25s user 2.39s system 33% cpu 34.893 total $ time sbt -client compile [info] entering *experimental* thin client - BEEP WHIRR > compile [success] completed sbt -client compile 3.55s user 1.68s system 107% cpu 4.889 total ``` --- .../src/main/scala/sbt/BasicCommands.scala | 2 +- .../sbt/internal/client/NetworkClient.scala | 57 +++++++++++++++++-- main/src/main/scala/sbt/Main.scala | 2 +- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index cc58497a6..9deb2b6ca 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -297,7 +297,7 @@ object BasicCommands { case e :: Nil if e.commandLine == "shell" => Nil case xs => xs map (_.commandLine) }) - NetworkClient.run(s0.configuration.baseDirectory, arguments) + NetworkClient.run(s0.configuration, arguments) "exit" :: s0.copy(remainingCommands = Nil) } 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 e234bb4f0..995a1a4d2 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -9,12 +9,13 @@ package sbt package internal package client -import java.io.IOException +import java.io.{ File, IOException } import java.util.UUID import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } import scala.collection.mutable.ListBuffer import scala.util.control.NonFatal import scala.util.{ Success, Failure } +import scala.sys.process.{ BasicIO, Process, ProcessLogger } import sbt.protocol._ import sbt.internal.protocol._ import sbt.internal.langserver.{ LogMessageParams, MessageType, PublishDiagnosticsParams } @@ -24,7 +25,7 @@ import sbt.io.syntax._ import sbt.io.IO import sjsonnew.support.scalajson.unsafe.Converter -class NetworkClient(baseDirectory: File, arguments: List[String]) { self => +class NetworkClient(configuration: xsbti.AppConfiguration, arguments: List[String]) { self => private val channelName = new AtomicReference("_") private val status = new AtomicReference("Ready") private val lock: AnyRef = new AnyRef {} @@ -32,6 +33,7 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self => private val pendingExecIds = ListBuffer.empty[String] private val console = ConsoleAppender("thin1") + private def baseDirectory: File = configuration.baseDirectory lazy val connection = init() @@ -40,7 +42,9 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self => // Open server connection based on the portfile def init(): ServerConnection = { val portfile = baseDirectory / "project" / "target" / "active.json" - if (!portfile.exists) sys.error("server does not seem to be running.") + if (!portfile.exists) { + forkServer(portfile) + } val (sk, tkn) = ClientSocket.socket(portfile) val conn = new ServerConnection(sk) { override def onNotification(msg: JsonRpcNotificationMessage): Unit = self.onNotification(msg) @@ -57,6 +61,42 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self => conn } + /** + * Forks another instance of sbt in the background. + * This instance must be shutdown explicitly via `sbt -client shutdown` + */ + def forkServer(portfile: File): Unit = { + console.appendLog(Level.Info, "server was not detected. starting an instance") + val args = List[String]() + val launchOpts = List("-Xms2048M", "-Xmx2048M", "-Xss2M") + val launcherJarString = sys.props.get("java.class.path") match { + case Some(cp) => + cp.split(File.pathSeparator) + .toList + .headOption + .getOrElse(sys.error("launcher JAR classpath not found")) + case _ => sys.error("property java.class.path expected") + } + val cmd = "java" :: launchOpts ::: "-jar" :: launcherJarString :: args + // val cmd = "sbt" + val io = BasicIO(false, ProcessLogger(_ => ())) + val _ = Process(cmd, baseDirectory).run(io) + def waitForPortfile(n: Int): Unit = + if (portfile.exists) { + console.appendLog(Level.Info, "server found") + } else { + if (n <= 0) sys.error(s"timeout. $portfile is not found.") + else { + Thread.sleep(1000) + if ((n - 1) % 10 == 0) { + console.appendLog(Level.Info, "waiting for the server...") + } + waitForPortfile(n - 1) + } + } + waitForPortfile(90) + } + /** Called on the response for a returning message. */ def onReturningReponse(msg: JsonRpcResponseMessage): Unit = { def printResponse(): Unit = { @@ -152,6 +192,7 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self => def start(): Unit = { console.appendLog(Level.Info, "entering *experimental* thin client - BEEP WHIRR") + val _ = connection val userCommands = arguments filterNot { cmd => cmd.startsWith("-") } @@ -162,7 +203,9 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self => def batchExecute(userCommands: List[String]): Unit = { userCommands foreach { cmd => println("> " + cmd) - val execId = sendExecCommand(cmd) + val execId = + if (cmd == "shutdown") sendExecCommand("exit") + else sendExecCommand(cmd) while (pendingExecIds contains execId) { Thread.sleep(100) } @@ -176,6 +219,8 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self => case Some("shutdown") => // `sbt -client shutdown` shuts down the server sendExecCommand("exit") + Thread.sleep(100) + running.set(false) case Some("exit") => running.set(false) case Some(s) if s.trim.nonEmpty => @@ -213,9 +258,9 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self => } object NetworkClient { - def run(baseDirectory: File, arguments: List[String]): Unit = + def run(configuration: xsbti.AppConfiguration, arguments: List[String]): Unit = try { - new NetworkClient(baseDirectory, arguments) + new NetworkClient(configuration, arguments) () } catch { case NonFatal(e) => println(e.getMessage) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 720d3c10d..27a0cd015 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -78,7 +78,7 @@ final class xMain extends xsbti.AppMain { val args = userCommands.toList filterNot { cmd => (cmd == DashClient) || (cmd == DashDashClient) } - NetworkClient.run(configuration.baseDirectory, args) + NetworkClient.run(configuration, args) Exit(0) } else { val state = StandardMain.initialState( From 76b2ea03cdc91352592912f8bd239a389d920df8 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 25 Jun 2018 22:34:55 -0400 Subject: [PATCH 349/356] Fix mima issue --- build.sbt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.sbt b/build.sbt index 93237deed..95df5d4ab 100644 --- a/build.sbt +++ b/build.sbt @@ -459,6 +459,9 @@ lazy val commandProj = (project in file("main-command")) exclude[DirectMissingMethodProblem]("sbt.CommandSource.copy$default$*"), exclude[DirectMissingMethodProblem]("sbt.Exec.copy"), exclude[DirectMissingMethodProblem]("sbt.Exec.copy$default$*"), + + // internal + exclude[ReversedMissingMethodProblem]("sbt.internal.client.ServerConnection.*"), ), unmanagedSources in (Compile, headerCreate) := { val old = (unmanagedSources in (Compile, headerCreate)).value From f44e4bb1e9a90b872cd3bdd4069c0b8f9e6b8cf0 Mon Sep 17 00:00:00 2001 From: OlegYch Date: Tue, 26 Jun 2018 17:38:05 +0300 Subject: [PATCH 350/356] remove gittatributes, assume core.autocrlf=false scalafmt is configured to use lf, as your editor should be, so there is no point having git tinker with line endings otherwise on checkout on windows source files get converted to crlf (by git) and on compile they get converted back to lf (by scalafmt) --- .gitattributes | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.gitattributes b/.gitattributes index f3dbe80d8..c637cb2f6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,11 +1,3 @@ -# Set default behaviour, in case users don't have core.autocrlf set. -* text=auto - -# Explicitly declare text files we want to always be normalized and converted -# to native line endings on checkout. -*.scala text -*.java text - # Exclude contraband generated files from diff (by default - you can see it if you want) **/contraband-scala/**/* -diff merge=ours **/contraband-scala/**/* linguist-generated=true From c9aa0c52853c158e71d36f03eb7316c6a90380ee Mon Sep 17 00:00:00 2001 From: Jason Pickens <4659562+steinybot@users.noreply.github.com> Date: Mon, 4 Jun 2018 22:14:38 +1200 Subject: [PATCH 351/356] Add warning for unknown project configurations. --- .../sbt/internal/util/complete/Parser.scala | 13 +- .../sbt/internal/util/complete/Parsers.scala | 2 +- .../src/test/scala/ParserTest.scala | 4 +- .../src/main/scala/sbt/BasicCommands.scala | 2 +- main/src/main/scala/sbt/Extracted.scala | 2 +- main/src/main/scala/sbt/Main.scala | 2 +- main/src/main/scala/sbt/PluginCross.scala | 2 +- main/src/main/scala/sbt/ScriptedPlugin.scala | 2 +- .../main/scala/sbt/internal/IvyConsole.scala | 2 +- .../main/scala/sbt/internal/KeyIndex.scala | 104 +++++-- main/src/main/scala/sbt/internal/Load.scala | 28 +- main/src/main/scala/sbt/internal/Script.scala | 2 +- main/src/test/scala/ParseKey.scala | 256 +++++++++++++++++- main/src/test/scala/ParserSpec.scala | 77 ++++++ main/src/test/scala/PluginCommandTest.scala | 5 +- .../test/scala/sbt/internal/TestBuild.scala | 105 +++++-- .../internal/server/SettingQueryTest.scala | 4 +- notes/1.2.0/unknown-config-warning.md | 8 + 18 files changed, 534 insertions(+), 86 deletions(-) create mode 100644 main/src/test/scala/ParserSpec.scala create mode 100644 notes/1.2.0/unknown-config-warning.md diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parser.scala b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parser.scala index 09bf9a8a3..31c1ca67a 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parser.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parser.scala @@ -209,7 +209,11 @@ object Parser extends ParserMain { a.ifValid { a.result match { case Some(av) => success(f(av)) - case None => new MapParser(a, f) + case None => + a match { + case m: MapParser[_, A] => m.map(f) + case _ => new MapParser(a, f) + } } } @@ -383,8 +387,8 @@ trait ParserMain { } /** Presents a Char range as a Parser. A single Char is parsed only if it is in the given range.*/ - implicit def range(r: collection.immutable.NumericRange[Char]): Parser[Char] = - charClass(r contains _).examples(r.map(_.toString): _*) + implicit def range(r: collection.immutable.NumericRange[Char], label: String): Parser[Char] = + charClass(r contains _, label).examples(r.map(_.toString): _*) /** Defines a Parser that parses a single character only if it is contained in `legal`.*/ def chars(legal: String): Parser[Char] = { @@ -396,7 +400,7 @@ trait ParserMain { * Defines a Parser that parses a single character only if the predicate `f` returns true for that character. * If this parser fails, `label` is used as the failure message. */ - def charClass(f: Char => Boolean, label: String = ""): Parser[Char] = + def charClass(f: Char => Boolean, label: String): Parser[Char] = new CharacterClass(f, label) /** Presents a single Char `ch` as a Parser that only parses that exact character. */ @@ -746,6 +750,7 @@ private final class MapParser[A, B](a: Parser[A], f: A => B) extends ValidParser def completions(level: Int) = a.completions(level) override def isTokenStart = a.isTokenStart override def toString = "map(" + a + ")" + def map[C](g: B => C) = new MapParser[A, C](a, f.andThen(g)) } private final class Filter[T](p: Parser[T], f: T => Boolean, seen: String, msg: String => String) diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala index ce444e229..ab160524b 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parsers.scala @@ -166,7 +166,7 @@ trait Parsers { }, "non-double-quote-backslash character") /** Matches a single character that is valid somewhere in a URI. */ - lazy val URIChar = charClass(alphanum) | chars("_-!.~'()*,;:$&+=?/[]@%#") + lazy val URIChar = charClass(alphanum, "alphanum") | chars("_-!.~'()*,;:$&+=?/[]@%#") /** Returns true if `c` is an ASCII letter or digit. */ def alphanum(c: Char) = diff --git a/internal/util-complete/src/test/scala/ParserTest.scala b/internal/util-complete/src/test/scala/ParserTest.scala index df318d323..17ef8c3c0 100644 --- a/internal/util-complete/src/test/scala/ParserTest.scala +++ b/internal/util-complete/src/test/scala/ParserTest.scala @@ -121,8 +121,8 @@ object ParserTest extends Properties("Completing Parser") { property("repeatDep accepts two tokens") = matches(repeat, colors.toSeq.take(2).mkString(" ")) } object ParserExample { - val ws = charClass(_.isWhitespace).+ - val notws = charClass(!_.isWhitespace).+ + val ws = charClass(_.isWhitespace, "whitespace").+ + val notws = charClass(!_.isWhitespace, "not whitespace").+ val name = token("test") val options = (ws ~> token("quick" | "failed" | "new")).* diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index 896cd9fe1..bae0a3cb4 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -146,7 +146,7 @@ object BasicCommands { } def multiParser(s: State): Parser[List[String]] = { - val nonSemi = token(charClass(_ != ';').+, hide = const(true)) + val nonSemi = token(charClass(_ != ';', "not ';'").+, hide = const(true)) val semi = token(';' ~> OptSpace) val part = semi flatMap ( _ => matched((s.combinedParser & nonSemi) | nonSemi) <~ token(OptSpace) diff --git a/main/src/main/scala/sbt/Extracted.scala b/main/src/main/scala/sbt/Extracted.scala index 20c2115a1..bc6decb10 100644 --- a/main/src/main/scala/sbt/Extracted.scala +++ b/main/src/main/scala/sbt/Extracted.scala @@ -148,7 +148,7 @@ final case class Extracted( ): State = { val appendSettings = Load.transformSettings(Load.projectScope(currentRef), currentRef.build, rootProject, settings) - val newStructure = Load.reapply(sessionSettings ++ appendSettings, structure) + val newStructure = Load.reapply(sessionSettings ++ appendSettings, structure, state.log) Project.setProject(session, newStructure, state) } } diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 49094156b..4e1bab596 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -459,7 +459,7 @@ object BuiltinCommands { val loggerInject = LogManager.settingsLogger(s) val withLogger = newSession.appendRaw(loggerInject :: Nil) val show = Project.showContextKey2(newSession) - val newStructure = Load.reapply(withLogger.mergeSettings, structure)(show) + val newStructure = Load.reapply(withLogger.mergeSettings, structure, s.log)(show) Project.setProject(newSession, newStructure, s) } diff --git a/main/src/main/scala/sbt/PluginCross.scala b/main/src/main/scala/sbt/PluginCross.scala index ea0d3fda1..634f3890e 100644 --- a/main/src/main/scala/sbt/PluginCross.scala +++ b/main/src/main/scala/sbt/PluginCross.scala @@ -50,7 +50,7 @@ private[sbt] object PluginCross { scalaVersion := scalaVersionSetting.value ) val cleared = session.mergeSettings.filterNot(crossExclude) - val newStructure = Load.reapply(cleared ++ add, structure) + val newStructure = Load.reapply(cleared ++ add, structure, state.log) Project.setProject(session, newStructure, command :: state) } } diff --git a/main/src/main/scala/sbt/ScriptedPlugin.scala b/main/src/main/scala/sbt/ScriptedPlugin.scala index b17886904..d871b78b6 100644 --- a/main/src/main/scala/sbt/ScriptedPlugin.scala +++ b/main/src/main/scala/sbt/ScriptedPlugin.scala @@ -129,7 +129,7 @@ object ScriptedPlugin extends AutoPlugin { } val pairMap = pairs.groupBy(_._1).mapValues(_.map(_._2).toSet) - val id = charClass(c => !c.isWhitespace && c != '/').+.string + val id = charClass(c => !c.isWhitespace && c != '/', "not whitespace and not '/'").+.string val groupP = token(id.examples(pairMap.keySet)) <~ token('/') // A parser for page definitions diff --git a/main/src/main/scala/sbt/internal/IvyConsole.scala b/main/src/main/scala/sbt/internal/IvyConsole.scala index a2cd16b1d..360e18da2 100644 --- a/main/src/main/scala/sbt/internal/IvyConsole.scala +++ b/main/src/main/scala/sbt/internal/IvyConsole.scala @@ -57,7 +57,7 @@ object IvyConsole { depSettings ) - val newStructure = Load.reapply(session.original ++ append, structure) + val newStructure = Load.reapply(session.original ++ append, structure, state.log) val newState = state.copy(remainingCommands = Exec(Keys.consoleQuick.key.label, None) :: Nil) Project.setProject(session, newStructure, newState) } diff --git a/main/src/main/scala/sbt/internal/KeyIndex.scala b/main/src/main/scala/sbt/internal/KeyIndex.scala index fc6ef184c..34762eb81 100644 --- a/main/src/main/scala/sbt/internal/KeyIndex.scala +++ b/main/src/main/scala/sbt/internal/KeyIndex.scala @@ -41,11 +41,15 @@ object KeyIndex { } yield { val data = ids map { id => val configs = configurations.getOrElse(id, Seq()) - Option(id) -> new ConfigIndex(Map.empty, configs.map(c => (c.name, c.id)).toMap) + val namedConfigs = configs.map { config => + (config.name, ConfigData(Some(config.id), emptyAKeyIndex)) + }.toMap + val inverse = namedConfigs.map((ConfigIndex.invert _).tupled) + Option(id) -> new ConfigIndex(namedConfigs, inverse, emptyAKeyIndex) } Option(uri) -> new ProjectIndex(data.toMap) } - new KeyIndex0(new BuildIndex(data.toMap)) + new KeyIndex0(new BuildIndex(data)) } def combine(indices: Seq[KeyIndex]): KeyIndex = new KeyIndex { @@ -61,6 +65,7 @@ object KeyIndex { case Some(idx) => idx.fromConfigIdent(proj)(configIdent) case _ => Scope.unguessConfigIdent(configIdent) } + private[sbt] def guessedConfigIdents = concat(_.guessedConfigIdents) def tasks(proj: Option[ResolvedReference], conf: Option[String]) = concat(_.tasks(proj, conf)) def tasks(proj: Option[ResolvedReference], conf: Option[String], key: String) = concat(_.tasks(proj, conf, key)) @@ -74,7 +79,7 @@ object KeyIndex { private[sbt] def getOr[A, B](m: Map[A, B], key: A, or: B): B = m.getOrElse(key, or) private[sbt] def keySet[A, B](m: Map[Option[A], B]): Set[A] = m.keys.flatten.toSet private[sbt] val emptyAKeyIndex = new AKeyIndex(Relation.empty) - private[sbt] val emptyConfigIndex = new ConfigIndex(Map.empty, Map.empty) + private[sbt] val emptyConfigIndex = new ConfigIndex(Map.empty, Map.empty, emptyAKeyIndex) private[sbt] val emptyProjectIndex = new ProjectIndex(Map.empty) private[sbt] val emptyBuildIndex = new BuildIndex(Map.empty) } @@ -109,6 +114,7 @@ trait KeyIndex { ): Set[String] private[sbt] def configIdents(project: Option[ResolvedReference]): Set[String] private[sbt] def fromConfigIdent(proj: Option[ResolvedReference])(configIdent: String): String + private[sbt] def guessedConfigIdents: Set[(Option[ProjectReference], String, String)] } trait ExtendableKeyIndex extends KeyIndex { def add(scoped: ScopedKey[_]): ExtendableKeyIndex @@ -121,45 +127,74 @@ private[sbt] final class AKeyIndex(val data: Relation[Option[AttributeKey[_]], S def keys(task: Option[AttributeKey[_]]): Set[String] = data.forward(task) def allKeys: Set[String] = data._2s.toSet def tasks: Set[AttributeKey[_]] = data._1s.flatten.toSet - def tasks(key: String): Set[AttributeKey[_]] = data.reverse(key).flatten.toSet + def tasks(key: String): Set[AttributeKey[_]] = data.reverse(key).flatten } +private[sbt] case class IdentifiableConfig(name: String, ident: Option[String]) + +private[sbt] case class ConfigData(ident: Option[String], keys: AKeyIndex) + /* - * data contains the mapping between a configuration and keys. - * identData contains the mapping between a configuration and its identifier. + * data contains the mapping between a configuration name and its ident and keys. + * noConfigKeys contains the keys without a configuration. */ private[sbt] final class ConfigIndex( - val data: Map[Option[String], AKeyIndex], - val identData: Map[String, String] + val data: Map[String, ConfigData], + val inverse: Map[String, String], + val noConfigKeys: AKeyIndex ) { def add( - config: Option[String], + config: Option[IdentifiableConfig], task: Option[AttributeKey[_]], key: AttributeKey[_] ): ConfigIndex = { - new ConfigIndex(data updated (config, keyIndex(config).add(task, key)), this.identData) + config match { + case Some(c) => addKeyWithConfig(c, task, key) + case None => addKeyWithoutConfig(task, key) + } } - def keyIndex(conf: Option[String]): AKeyIndex = getOr(data, conf, emptyAKeyIndex) - def configs: Set[String] = keySet(data) + def addKeyWithConfig( + config: IdentifiableConfig, + task: Option[AttributeKey[_]], + key: AttributeKey[_] + ): ConfigIndex = { + val oldConfigData = data.getOrElse(config.name, ConfigData(None, emptyAKeyIndex)) + val newConfigData = ConfigData( + ident = oldConfigData.ident.orElse(config.ident), + keys = oldConfigData.keys.add(task, key) + ) + val newData = data.updated(config.name, newConfigData) + val newInverse = (inverse.updated _).tupled(ConfigIndex.invert(config.name, newConfigData)) + new ConfigIndex(newData, newInverse, noConfigKeys) + } - private[sbt] val configIdentsInverse: Map[String, String] = - identData map { _.swap } + def addKeyWithoutConfig(task: Option[AttributeKey[_]], key: AttributeKey[_]): ConfigIndex = { + new ConfigIndex(data, inverse, noConfigKeys.add(task, key)) + } - private[sbt] lazy val idents: Set[String] = - configs map { config => - identData.getOrElse(config, Scope.guessConfigIdent(config)) - } + def keyIndex(conf: Option[String]): AKeyIndex = conf match { + case Some(c) => data.get(c).map(_.keys).getOrElse(emptyAKeyIndex) + case None => noConfigKeys + } + + def configs: Set[String] = data.keySet // guess Configuration name from an identifier. // There's a guessing involved because we could have scoped key that Project is not aware of. private[sbt] def fromConfigIdent(ident: String): String = - configIdentsInverse.getOrElse(ident, Scope.unguessConfigIdent(ident)) + inverse.getOrElse(ident, Scope.unguessConfigIdent(ident)) +} +private[sbt] object ConfigIndex { + def invert(name: String, data: ConfigData): (String, String) = data match { + case ConfigData(Some(ident), _) => ident -> name + case ConfigData(None, _) => Scope.guessConfigIdent(name) -> name + } } private[sbt] final class ProjectIndex(val data: Map[Option[String], ConfigIndex]) { def add( id: Option[String], - config: Option[String], + config: Option[IdentifiableConfig], task: Option[AttributeKey[_]], key: AttributeKey[_] ): ProjectIndex = @@ -171,7 +206,7 @@ private[sbt] final class BuildIndex(val data: Map[Option[URI], ProjectIndex]) { def add( build: Option[URI], project: Option[String], - config: Option[String], + config: Option[IdentifiableConfig], task: Option[AttributeKey[_]], key: AttributeKey[_] ): BuildIndex = @@ -189,11 +224,30 @@ private[sbt] final class KeyIndex0(val data: BuildIndex) extends ExtendableKeyIn def configs(project: Option[ResolvedReference]): Set[String] = confIndex(project).configs private[sbt] def configIdents(project: Option[ResolvedReference]): Set[String] = - confIndex(project).idents + confIndex(project).configs private[sbt] def fromConfigIdent(proj: Option[ResolvedReference])(configIdent: String): String = confIndex(proj).fromConfigIdent(configIdent) + private[sbt] def guessedConfigIdents: Set[(Option[ProjectReference], String, String)] = { + val guesses = for { + (build, projIndex) <- data.data + (project, confIndex) <- projIndex.data + (config, data) <- confIndex.data + if data.ident.isEmpty && !Scope.configIdents.contains(config) + } yield (projRef(build, project), config, Scope.guessConfigIdent(config)) + guesses.toSet + } + + private def projRef(build: Option[URI], project: Option[String]): Option[ProjectReference] = { + (build, project) match { + case (Some(uri), Some(proj)) => Some(ProjectRef(uri, proj)) + case (Some(uri), None) => Some(RootProject(uri)) + case (None, Some(proj)) => Some(LocalProject(proj)) + case (None, None) => None + } + } + def tasks(proj: Option[ResolvedReference], conf: Option[String]): Set[AttributeKey[_]] = keyIndex(proj, conf).tasks def tasks( @@ -247,6 +301,8 @@ private[sbt] final class KeyIndex0(val data: BuildIndex) extends ExtendableKeyIn config: ScopeAxis[ConfigKey], task: ScopeAxis[AttributeKey[_]], key: AttributeKey[_] - ): ExtendableKeyIndex = - new KeyIndex0(data.add(uri, id, config.toOption.map(_.name), task.toOption, key)) + ): ExtendableKeyIndex = { + val keyConfig = config.toOption.map(c => IdentifiableConfig(c.name, None)) + new KeyIndex0(data.add(uri, id, keyConfig, task.toOption, key)) + } } diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index b0d8b53dc..0f1dc625d 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -267,7 +267,7 @@ private[sbt] object Load { } Project.checkTargets(data) foreach sys.error val index = timed("Load.apply: structureIndex", log) { - structureIndex(data, settings, loaded.extra(data), projects) + structureIndex(data, settings, loaded.extra(data), projects, log) } val streams = timed("Load.apply: mkStreams", log) { mkStreams(projects, loaded.root, data) } val bs = new BuildStructure( @@ -321,7 +321,8 @@ private[sbt] object Load { data: Settings[Scope], settings: Seq[Setting[_]], extra: KeyIndex => BuildUtil[_], - projects: Map[URI, LoadedBuildUnit] + projects: Map[URI, LoadedBuildUnit], + log: Logger ): StructureIndex = { val keys = Index.allKeys(settings) val attributeKeys = Index.attributeKeys(data) ++ keys.map(_.key) @@ -330,6 +331,7 @@ private[sbt] object Load { val configsMap: Map[String, Seq[Configuration]] = projects.values.flatMap(bu => bu.defined map { case (k, v) => (k, v.configurations) }).toMap val keyIndex = KeyIndex(scopedKeys.toVector, projectsMap, configsMap) + checkConfigurations(keyIndex, log) val aggIndex = KeyIndex.aggregate(scopedKeys.toVector, extra(keyIndex), projectsMap, configsMap) new StructureIndex( Index.stringToKeyMap(attributeKeys), @@ -340,15 +342,33 @@ private[sbt] object Load { ) } + private def checkConfigurations(keyIndex: KeyIndex, log: Logger): Unit = { + keyIndex.guessedConfigIdents + .collect { + // Filter out any global configurations since we don't have a way of fixing them. + // Chances are this is only going to be the Test configuration which will have guessed correctly. + case (Some(projectRef), config, guess) => + (Reference.display(projectRef), config, guess) + } + .foreach { + case (project, config, guess) => + log.warn( + s"""The project $project references an unknown configuration "$config" and was guessed to be "$guess".""" + ) + log.warn("This configuration should be explicitly added to the project.") + } + } + // Reevaluates settings after modifying them. Does not recompile or reload any build components. def reapply( newSettings: Seq[Setting[_]], - structure: BuildStructure + structure: BuildStructure, + log: Logger )(implicit display: Show[ScopedKey[_]]): BuildStructure = { val transformed = finalTransforms(newSettings) val newData = Def.make(transformed)(structure.delegates, structure.scopeLocal, display) def extra(index: KeyIndex) = BuildUtil(structure.root, structure.units, index, newData) - val newIndex = structureIndex(newData, transformed, extra, structure.units) + val newIndex = structureIndex(newData, transformed, extra, structure.units, log) val newStreams = mkStreams(structure.units, structure.root, newData) new BuildStructure( units = structure.units, diff --git a/main/src/main/scala/sbt/internal/Script.scala b/main/src/main/scala/sbt/internal/Script.scala index 2192785a6..f03dcacde 100644 --- a/main/src/main/scala/sbt/internal/Script.scala +++ b/main/src/main/scala/sbt/internal/Script.scala @@ -65,7 +65,7 @@ object Script { scriptSettings ++ embeddedSettings ) - val newStructure = Load.reapply(session.original ++ append, structure) + val newStructure = Load.reapply(session.original ++ append, structure, state.log) val arguments = state.remainingCommands.drop(1).map(e => s""""${e.commandLine}"""") val newState = arguments.mkString("run ", " ", "") :: state.copy(remainingCommands = Nil) Project.setProject(session, newStructure, newState) diff --git a/main/src/test/scala/ParseKey.scala b/main/src/test/scala/ParseKey.scala index ecc6374dc..9af98f56f 100644 --- a/main/src/test/scala/ParseKey.scala +++ b/main/src/test/scala/ParseKey.scala @@ -7,11 +7,18 @@ package sbt -import Def.{ displayFull, displayMasked, ScopedKey } -import sbt.internal.{ TestBuild, Resolve }, TestBuild._ -import sbt.internal.util.complete.Parser +import java.net.URI -import org.scalacheck._, Arbitrary.arbitrary, Gen._, Prop._ +import org.scalacheck.Arbitrary.{ arbBool, arbitrary } +import org.scalacheck.Gen._ +import org.scalacheck.Prop._ +import org.scalacheck._ +import sbt.Def.{ ScopedKey, displayFull, displayMasked } +import sbt.internal.TestBuild._ +import sbt.internal.util.AttributeKey +import sbt.internal.util.complete.{ DefaultParsers, Parser } +import sbt.internal.{ Resolve, TestBuild } +import sbt.librarymanagement.Configuration /** * Tests that the scoped key parser in Act can correctly parse a ScopedKey converted by Def.show*Key. @@ -26,10 +33,7 @@ object ParseKey extends Properties("Key parser test") { // Note that this explicitly displays the configuration axis set to Zero. // This is to disambiguate `proj/Zero/name`, which could render potentially // as `Zero/name`, but could be interpreted as `Zero/Zero/name`. - val expected = ScopedKey( - Resolve(structure.extra, Select(structure.current), key.key, mask)(key.scope), - key.key - ) + val expected = resolve(structure, key, mask) parseCheck(structure, key, mask, hasZeroConfig)( sk => Project.equal(sk, expected, mask) @@ -75,14 +79,20 @@ object ParseKey extends Properties("Key parser test") { scopes <- pickN(loadFactor, env.allFullScopes) current <- oneOf(env.allProjects.unzip._1) structure <- { - val settings = for (scope <- scopes; t <- env.tasks) - yield Def.setting(ScopedKey(scope, t.key), Def.value("")) + val settings = structureSettings(scopes, env) TestBuild.structure(env, settings, current) } } yield structure } - final class StructureKeyMask(val structure: Structure, val key: ScopedKey[_], val mask: ScopeMask) + def structureSettings(scopes: Seq[Scope], env: Env): Seq[Def.Setting[String]] = { + for { + scope <- scopes + t <- env.tasks + } yield Def.setting(ScopedKey(scope, t.key), Def.value("")) + } + + final case class StructureKeyMask(structure: Structure, key: ScopedKey[_], mask: ScopeMask) implicit val arbStructureKeyMask: Arbitrary[StructureKeyMask] = Arbitrary { for { @@ -92,9 +102,35 @@ object ParseKey extends Properties("Key parser test") { scope <- TestBuild.scope(structure.env) key <- oneOf(structure.allAttributeKeys.toSeq) } yield ScopedKey(scope, key) - } yield new StructureKeyMask(structure, key, mask) + skm = StructureKeyMask(structure, key, mask) + if configExistsInIndex(skm) + } yield skm } + private def configExistsInIndex(skm: StructureKeyMask): Boolean = { + import skm._ + val resolvedKey = resolve(structure, key, mask) + val proj = resolvedKey.scope.project.toOption + val maybeResolvedProj = proj.collect { + case ref: ResolvedReference => ref + } + val checkName = for { + configKey <- resolvedKey.scope.config.toOption + } yield { + val configID = Scope.display(configKey) + // This only works for known configurations or those that were guessed correctly. + val name = structure.keyIndex.fromConfigIdent(maybeResolvedProj)(configID) + name == configKey.name + } + checkName.getOrElse(true) + } + + def resolve(structure: Structure, key: ScopedKey[_], mask: ScopeMask): ScopedKey[_] = + ScopedKey( + Resolve(structure.extra, Select(structure.current), key.key, mask)(key.scope), + key.key + ) + def parseCheck( structure: Structure, key: ScopedKey[_], @@ -118,4 +154,200 @@ object ParseKey extends Properties("Key parser test") { // The rest of the tests expect at least one item, so I changed it to return 1 in case of 0. def pickN[T](load: Double, from: Seq[T]): Gen[Seq[T]] = pick((load * from.size).toInt max 1, from) + + implicit val shrinkStructureKeyMask: Shrink[StructureKeyMask] = Shrink { skm => + Shrink + .shrink(skm.structure) + .map(s => skm.copy(structure = s)) + .flatMap(fixKey) + } + + def fixKey(skm: StructureKeyMask): Stream[StructureKeyMask] = { + for { + scope <- fixScope(skm) + attributeKey <- fixAttributeKey(skm) + } yield skm.copy(key = ScopedKey(scope, attributeKey)) + } + + def fixScope(skm: StructureKeyMask): Stream[Scope] = { + def validScope(scope: Scope) = scope match { + case Scope(Select(BuildRef(build)), _, _, _) if !validBuild(build) => false + case Scope(Select(ProjectRef(build, project)), _, _, _) if !validProject(build, project) => + false + case Scope(Select(ProjectRef(build, project)), Select(ConfigKey(config)), _, _) + if !validConfig(build, project, config) => + false + case Scope(_, Select(ConfigKey(config)), _, _) if !configExists(config) => + false + case Scope(_, _, Select(task), _) => validTask(task) + case _ => true + } + def validBuild(build: URI) = skm.structure.env.buildMap.contains(build) + def validProject(build: URI, project: String) = { + skm.structure.env.buildMap + .get(build) + .exists(_.projectMap.contains(project)) + } + def validConfig(build: URI, project: String, config: String) = { + skm.structure.env.buildMap + .get(build) + .toSeq + .flatMap(_.projectMap.get(project)) + .flatMap(_.configurations.map(_.name)) + .contains(config) + } + def configExists(config: String) = { + val configs = for { + build <- skm.structure.env.builds + project <- build.projects + config <- project.configurations + } yield config.name + configs.contains(config) + } + def validTask(task: AttributeKey[_]) = skm.structure.env.taskMap.contains(task) + if (validScope(skm.key.scope)) { + Stream(skm.key.scope) + } else { + // We could return all scopes here but we want to explore the other paths first since there + // is a greater chance of a successful shrink. If necessary these could be appended to the end. + Stream.empty + } + } + + def fixAttributeKey(skm: StructureKeyMask): Stream[AttributeKey[_]] = { + if (skm.structure.allAttributeKeys.contains(skm.key.key)) { + Stream(skm.key.key) + } else { + // Likewise here, we should try other paths before trying different attribute keys. + Stream.empty + } + } + + implicit val shrinkStructure: Shrink[Structure] = Shrink { s => + Shrink.shrink(s.env).flatMap { env => + val scopes = s.data.scopes intersect env.allFullScopes.toSet + val settings = structureSettings(scopes.toSeq, env) + if (settings.nonEmpty) { + val currents = env.allProjects.find { + case (ref, _) => ref == s.current + } match { + case Some((current, _)) => Stream(current) + case None => env.allProjects.map(_._1).toStream + } + currents.map(structure(env, settings, _)) + } else { + Stream.empty + } + } + } + + implicit val shrinkEnv: Shrink[Env] = Shrink { env => + val shrunkBuilds = Shrink + .shrink(env.builds) + .filter(_.nonEmpty) + .map(b => env.copy(builds = b)) + .map(fixProjectRefs) + .map(fixConfigurations) + val shrunkTasks = shrinkTasks(env.tasks) + .map(t => env.copy(tasks = t)) + shrunkBuilds ++ shrunkTasks + } + + private def fixProjectRefs(env: Env): Env = { + def fixBuild(build: Build): Build = { + build.copy(projects = build.projects.map(fixProject)) + } + def fixProject(project: Proj): Proj = { + project.copy(delegates = project.delegates.filter(delegateExists)) + } + def delegateExists(delegate: ProjectRef): Boolean = { + env.buildMap + .get(delegate.build) + .flatMap(_.projectMap.get(delegate.project)) + .nonEmpty + } + env.copy(builds = env.builds.map(fixBuild)) + } + + private def fixConfigurations(env: Env): Env = { + val configs = env.allProjects.map { + case (_, proj) => proj -> proj.configurations.toSet + }.toMap + + def fixBuild(build: Build): Build = { + build.copy(projects = build.projects.map(fixProject(build.uri))) + } + def fixProject(buildURI: URI)(project: Proj): Proj = { + val projConfigs = configs(project) + project.copy(configurations = project.configurations.map(fixConfig(projConfigs))) + } + def fixConfig(projConfigs: Set[Configuration])(config: Configuration): Configuration = { + import config.{ name => configName, _ } + val extendsConfigs = config.extendsConfigs.filter(projConfigs.contains) + Configuration.of(id, configName, description, isPublic, extendsConfigs, transitive) + } + env.copy(builds = env.builds.map(fixBuild)) + } + + implicit val shrinkBuild: Shrink[Build] = Shrink { build => + Shrink + .shrink(build.projects) + .filter(_.nonEmpty) + .map(p => build.copy(projects = p)) + // Could also shrink the URI here but that requires updating all the references. + } + + implicit val shrinkProject: Shrink[Proj] = Shrink { project => + val shrunkDelegates = Shrink + .shrink(project.delegates) + .map(d => project.copy(delegates = d)) + val shrunkConfigs = Shrink + .shrink(project.configurations) + .map(c => project.copy(configurations = c)) + val shrunkID = shrinkID(project.id) + .map(id => project.copy(id = id)) + shrunkDelegates ++ shrunkConfigs ++ shrunkID + } + + implicit val shrinkDelegate: Shrink[ProjectRef] = Shrink { delegate => + val shrunkBuild = Shrink + .shrink(delegate.build) + .map(b => delegate.copy(build = b)) + val shrunkProject = Shrink + .shrink(delegate.project) + .map(p => delegate.copy(project = p)) + shrunkBuild ++ shrunkProject + } + + implicit val shrinkConfiguration: Shrink[Configuration] = Shrink { configuration => + import configuration.{ name => configName, _ } + val shrunkExtends = Shrink + .shrink(configuration.extendsConfigs) + .map(configuration.withExtendsConfigs) + val shrunkID = Shrink.shrink(id.tail).map { tail => + Configuration + .of(id.head + tail, configName, description, isPublic, extendsConfigs, transitive) + } + shrunkExtends ++ shrunkID + } + + val shrinkStringLength: Shrink[String] = Shrink { s => + // Only change the string length don't change the characters. + implicit val shrinkChar: Shrink[Char] = Shrink.shrinkAny + Shrink.shrinkContainer[List, Char].shrink(s.toList).map(_.mkString) + } + + def shrinkID(id: String): Stream[String] = { + Shrink.shrink(id).filter(DefaultParsers.validID) + } + + def shrinkTasks(tasks: Vector[Taskk]): Stream[Vector[Taskk]] = { + Shrink.shrink(tasks) + } + + implicit val shrinkTask: Shrink[Taskk] = Shrink { task => + Shrink.shrink((task.delegates, task.key)).map { + case (delegates, key) => Taskk(key, delegates) + } + } } diff --git a/main/src/test/scala/ParserSpec.scala b/main/src/test/scala/ParserSpec.scala new file mode 100644 index 000000000..72f44dbaf --- /dev/null +++ b/main/src/test/scala/ParserSpec.scala @@ -0,0 +1,77 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +import java.net.URI + +import org.scalatest.prop.PropertyChecks +import org.scalatest.{ Matchers, PropSpec } +import sbt.Def._ +import sbt._ +import sbt.internal.TestBuild +import sbt.internal.TestBuild._ +import sbt.internal.util.AttributeKey +import sbt.internal.util.complete.DefaultParsers +import sbt.librarymanagement.Configuration + +class ParserSpec extends PropSpec with PropertyChecks with Matchers { + + property("can parse any build") { + forAll(TestBuild.uriGen) { uri => + parse(buildURI = uri) + } + } + + property("can parse any project") { + forAll(TestBuild.idGen) { id => + parse(projectID = id) + } + } + + property("can parse any configuration") { + forAll(TestBuild.scalaIDGen) { name => + parse(configName = name) + } + } + + property("can parse any attribute") { + forAll(TestBuild.lowerIDGen) { name => + parse(attributeName = name) + } + } + + private def parse( + buildURI: URI = new java.net.URI("s", "p", null), + projectID: String = "p", + configName: String = "c", + attributeName: String = "a" + ) = { + val attributeKey = AttributeKey[String](attributeName) + val scope = Scope( + Select(BuildRef(buildURI)), + Select(ConfigKey(configName)), + Select(attributeKey), + Zero + ) + val scopedKey = ScopedKey(scope, attributeKey) + val config = Configuration.of(configName.capitalize, configName) + val project = Proj(projectID, Nil, Seq(config)) + val projects = Vector(project) + val build = Build(buildURI, projects) + val builds = Vector(build) + val task = Taskk(attributeKey, Nil) + val tasks = Vector(task) + val env = Env(builds, tasks) + val settings = env.tasks.map { t => + Def.setting(ScopedKey(scope, t.key), Def.value("value")) + } + val structure = TestBuild.structure(env, settings, build.allProjects.head._1) + val string = displayMasked(scopedKey, ScopeMask()) + val parser = makeParser(structure) + val result = DefaultParsers.result(parser, string).left.map(_().toString) + result shouldBe Right(scopedKey) + } +} diff --git a/main/src/test/scala/PluginCommandTest.scala b/main/src/test/scala/PluginCommandTest.scala index d40863578..627c6b568 100644 --- a/main/src/test/scala/PluginCommandTest.scala +++ b/main/src/test/scala/PluginCommandTest.scala @@ -10,7 +10,6 @@ package sbt import java.io._ import org.specs2.mutable.Specification - import sbt.internal._ import sbt.internal.util.{ AttributeEntry, @@ -20,6 +19,7 @@ import sbt.internal.util.{ MainAppender, Settings } +import sbt.util.Logger object PluginCommandTestPlugin0 extends AutoPlugin { override def requires = empty } @@ -103,7 +103,8 @@ object FakeState { val data: Settings[Scope] = Def.make(settings)(delegates, scopeLocal, Def.showFullKey) val extra: KeyIndex => BuildUtil[_] = (keyIndex) => BuildUtil(base.toURI, Map.empty, keyIndex, data) - val structureIndex: StructureIndex = Load.structureIndex(data, settings, extra, Map.empty) + val structureIndex: StructureIndex = + Load.structureIndex(data, settings, extra, Map.empty, Logger.Null) val streams: (State) => BuildStreams.Streams = null val loadedDefinitions: LoadedDefinitions = new LoadedDefinitions( diff --git a/main/src/test/scala/sbt/internal/TestBuild.scala b/main/src/test/scala/sbt/internal/TestBuild.scala index 825412d35..826e71867 100644 --- a/main/src/test/scala/sbt/internal/TestBuild.scala +++ b/main/src/test/scala/sbt/internal/TestBuild.scala @@ -39,8 +39,8 @@ abstract class TestBuild { def chooseShrinkable(min: Int, max: Int): Gen[Int] = sized(sz => choose(min, (max min sz) max 1)) - implicit val cGen = Arbitrary { genConfigs(idGen, MaxDepsGen, MaxConfigsGen) } - implicit val tGen = Arbitrary { genTasks(idGen, MaxDepsGen, MaxTasksGen) } + implicit val cGen = Arbitrary { genConfigs(scalaIDGen, MaxDepsGen, MaxConfigsGen) } + implicit val tGen = Arbitrary { genTasks(lowerIDGen, MaxDepsGen, MaxTasksGen) } val seed = rng.Seed.random final class Keys(val env: Env, val scopes: Seq[Scope]) { @@ -119,7 +119,7 @@ abstract class TestBuild { (taskAxes, zero.toSet, single.toSet, multi.toSet) } } - final class Env(val builds: Vector[Build], val tasks: Vector[Taskk]) { + final case class Env(builds: Vector[Build], tasks: Vector[Taskk]) { override def toString = "Env:\n " + " Tasks:\n " + tasks.mkString("\n ") + "\n" + builds.mkString("\n ") val root = builds.head @@ -159,7 +159,7 @@ abstract class TestBuild { } def getKey: Taskk => AttributeKey[_] = _.key def toConfigKey: Configuration => ConfigKey = c => ConfigKey(c.name) - final class Build(val uri: URI, val projects: Seq[Proj]) { + final case class Build(uri: URI, projects: Seq[Proj]) { override def toString = "Build " + uri.toString + " :\n " + projects.mkString("\n ") val allProjects = projects map { p => (ProjectRef(uri, p.id), p) @@ -167,10 +167,10 @@ abstract class TestBuild { val root = projects.head val projectMap = mapBy(projects)(_.id) } - final class Proj( - val id: String, - val delegates: Seq[ProjectRef], - val configurations: Seq[Configuration] + final case class Proj( + id: String, + delegates: Seq[ProjectRef], + configurations: Seq[Configuration] ) { override def toString = "Project " + id + "\n Delegates:\n " + delegates.mkString("\n ") + @@ -178,7 +178,7 @@ abstract class TestBuild { val confMap = mapBy(configurations)(_.name) } - final class Taskk(val key: AttributeKey[String], val delegates: Seq[Taskk]) { + final case class Taskk(key: AttributeKey[String], delegates: Seq[Taskk]) { override def toString = key.label + " (delegates: " + delegates.map(_.key.label).mkString(", ") + ")" } @@ -234,19 +234,17 @@ abstract class TestBuild { val keyMap = keys.map(k => (k.key.label, k.key)).toMap[String, AttributeKey[_]] val projectsMap = env.builds.map(b => (b.uri, b.projects.map(_.id).toSet)).toMap val confs = for { - b <- env.builds.toVector - p <- b.projects.toVector - c <- p.configurations.toVector - } yield c - val confMap = confs.map(c => (c.name, Seq(c))).toMap - new Structure(env, current, data, KeyIndex(keys, projectsMap, confMap), keyMap) + b <- env.builds + p <- b.projects + } yield p.id -> p.configurations + val confMap = confs.toMap + Structure(env, current, data, KeyIndex(keys, projectsMap, confMap), keyMap) } implicit lazy val mkEnv: Gen[Env] = { - implicit val cGen = genConfigs(idGen, MaxDepsGen, MaxConfigsGen) - implicit val tGen = genTasks(idGen, MaxDepsGen, MaxTasksGen) - implicit val pGen = (uri: URI) => genProjects(uri)(idGen, MaxDepsGen, MaxProjectsGen, cGen) - envGen(buildGen(uriGen, pGen), tGen) + implicit val pGen = (uri: URI) => + genProjects(uri)(idGen, MaxDepsGen, MaxProjectsGen, cGen.arbitrary) + envGen(buildGen(uriGen, pGen), tGen.arbitrary) } implicit def maskGen(implicit arbBoolean: Arbitrary[Boolean]): Gen[ScopeMask] = { @@ -255,18 +253,69 @@ abstract class TestBuild { yield ScopeMask(project = p, config = c, task = t, extra = x) } - implicit lazy val idGen: Gen[String] = + val allChars: Seq[Char] = ((0x0000 to 0xD7FF) ++ (0xE000 to 0xFFFD)).map(_.toChar) + + val letters: Seq[Char] = allChars.filter(_.isLetter) + + val upperLetters: Gen[Char] = Gen.oneOf(letters.filter(_.isUpper)) + + val lowerLetters: Gen[Char] = Gen.oneOf(letters.filter(_.isLower)) + + val lettersAndDigits: Gen[Char] = Gen.oneOf(allChars.filter(_.isLetterOrDigit)) + + val scalaIDCharGen: Gen[Char] = { + val others = Gen.const('_') + frequency(19 -> lettersAndDigits, 1 -> others) + } + + val idCharGen: Gen[Char] = { + val others = Gen.const('-') + frequency(19 -> scalaIDCharGen, 1 -> others) + } + + def isIDChar(c: Char) = { + c.isLetterOrDigit || "-_".toSeq.contains(c) + } + + val idGen: Gen[String] = idGen(upperLetters, idCharGen, _.isUpper) + + val lowerIDGen: Gen[String] = idGen(lowerLetters, idCharGen, _.isLower) + + val scalaIDGen: Gen[String] = idGen(upperLetters, scalaIDCharGen, _.isUpper) + + def idGen(start: Gen[Char], end: Gen[Char], headFilter: Char => Boolean): Gen[String] = { for { size <- chooseShrinkable(1, MaxIDSize) - cs <- listOfN(size, alphaChar) - } yield { - val xs = cs.mkString - xs.take(1).toLowerCase + xs.drop(1) - } + idStart <- start + idEnd <- listOfN(size - 1, end) + } yield idStart + idEnd.mkString + } filter { id => + // The filter ensure that shrinking works + id.headOption.exists(headFilter) && id.tail.forall(isIDChar) + } - implicit lazy val optIDGen: Gen[Option[String]] = frequency((1, idGen map some.fn), (1, None)) - implicit lazy val uriGen: Gen[URI] = for (sch <- idGen; ssp <- idGen; frag <- optIDGen) - yield new URI(sch, ssp, frag.orNull) + val schemeGen: Gen[String] = { + for { + schemeStart <- alphaChar + schemeEnd <- listOf(frequency(19 -> alphaNumChar, 1 -> oneOf('+', '-', '.'))) + } yield schemeStart + schemeEnd.mkString + } + + val uriChar: Gen[Char] = { + frequency(9 -> alphaNumChar, 1 -> oneOf(";/?:@&=+$,-_.!~*'()".toSeq)) + } + + val uriStringGen: Gen[String] = nonEmptyListOf(uriChar).map(_.mkString) + + val optIDGen: Gen[Option[String]] = oneOf(uriStringGen.map(some.fn), Gen.const(None)) + + val uriGen: Gen[URI] = { + for { + sch <- schemeGen + ssp <- uriStringGen + frag <- optIDGen + } yield new URI(sch, ssp, frag.orNull) + } implicit def envGen(implicit bGen: Gen[Build], tasks: Gen[Vector[Taskk]]): Gen[Env] = for (i <- MaxBuildsGen; bs <- containerOfN[Vector, Build](i, bGen); ts <- tasks) diff --git a/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala b/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala index a971c818a..4342afeb2 100644 --- a/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala +++ b/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala @@ -16,7 +16,7 @@ import java.util.concurrent._ import scala.collection.mutable -import xsbti._ +import xsbti.{ Logger => _, _ } import sbt.io.IO import sbt.internal.util._ import sbt.internal.BuildStreams.{ Streams => _, _ } @@ -174,7 +174,7 @@ object SettingQueryTest extends org.specs2.mutable.Specification { val data: Settings[Scope] = Def.make(settings)(delegates, scopeLocal, display) val extra: KeyIndex => BuildUtil[_] = index => BuildUtil(baseUri, units, index, data) - val index: StructureIndex = structureIndex(data, settings, extra, units) + val index: StructureIndex = structureIndex(data, settings, extra, units, Logger.Null) val streams: State => Streams = mkStreams(units, baseUri, data) val structure: BuildStructure = diff --git a/notes/1.2.0/unknown-config-warning.md b/notes/1.2.0/unknown-config-warning.md new file mode 100644 index 000000000..645cbedf9 --- /dev/null +++ b/notes/1.2.0/unknown-config-warning.md @@ -0,0 +1,8 @@ +[@steinybot]: https://github.com/steinybot + +[#4065]: https://github.com/sbt/sbt/issues/4065 +[#4231]: https://github.com/sbt/sbt/pull/4231 + +### Improvements + +- Add a warning for unknown project configurations. [#4065][]/[#4231][] by [@steinybot][] From 3b7e6974c32c0bf8135ff995f6e416c5c9100cb2 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 27 Jun 2018 06:26:38 -0400 Subject: [PATCH 352/356] Fixes stacktrace of backgroun run Ref https://github.com/sbt/sbt/issues/4121 sbt already has the facility to trim stack traces. This sets the trace level of the background run, which fixes the upper half of the `run` stacktrace. ``` [error] (run-main-0) java.lang.Exception [error] java.lang.Exception [error] at Hello$.delayedEndpoint$Hello$1(Hello.scala:5) [error] at Hello$delayedInit$body.apply(Hello.scala:1) [error] at scala.Function0.apply$mcV$sp(Function0.scala:34) [error] at scala.Function0.apply$mcV$sp$(Function0.scala:34) [error] at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12) [error] at scala.App.$anonfun$main$1$adapted(App.scala:76) [error] at scala.collection.immutable.List.foreach(List.scala:389) [error] at scala.App.main(App.scala:76) [error] at scala.App.main$(App.scala:74) [error] at Hello$.main(Hello.scala:1) [error] at Hello.main(Hello.scala) [error] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [error] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) [error] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) [error] at java.lang.reflect.Method.invoke(Method.java:498) [error] java.lang.RuntimeException: Nonzero exit code: 1 [error] at sbt.Run$.executeTrapExit(Run.scala:127) [error] at sbt.Run.run(Run.scala:77) [error] at sbt.Defaults$.$anonfun$bgRunTask$5(Defaults.scala:1254) [error] at sbt.Defaults$.$anonfun$bgRunTask$5$adapted(Defaults.scala:1249) [error] at sbt.internal.BackgroundThreadPool.$anonfun$run$1(DefaultBackgroundJobService.scala:377) [error] at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12) [error] at scala.util.Try$.apply(Try.scala:209) [error] at sbt.internal.BackgroundThreadPool$BackgroundRunnable.run(DefaultBackgroundJobService.scala:299) [error] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [error] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [error] at java.lang.Thread.run(Thread.java:748) [error] (Compile / run) Nonzero exit code: 1 ``` The bottom half requires a similar fix to the foreground log. --- main/src/main/scala/sbt/Defaults.scala | 2 ++ main/src/main/scala/sbt/internal/LogManager.scala | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index bd28131d9..5d5055c33 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -154,6 +154,8 @@ object Defaults extends BuildCommon { classpathEntryDefinesClass :== FileValueCache(Locate.definesClass _).get, traceLevel in run :== 0, traceLevel in runMain :== 0, + traceLevel in bgRun :== 0, + traceLevel in fgRun :== 0, traceLevel in console :== Int.MaxValue, traceLevel in consoleProject :== Int.MaxValue, autoCompilerPlugins :== true, diff --git a/main/src/main/scala/sbt/internal/LogManager.scala b/main/src/main/scala/sbt/internal/LogManager.scala index 982b6a45c..8409f01c2 100644 --- a/main/src/main/scala/sbt/internal/LogManager.scala +++ b/main/src/main/scala/sbt/internal/LogManager.scala @@ -198,13 +198,19 @@ object LogManager { val scope = task.scope val screenLevel = getOr(logLevel.key, data, scope, state, Level.Info) val backingLevel = getOr(persistLogLevel.key, data, scope, state, Level.Debug) + val screenTrace = getOr(traceLevel.key, data, scope, state, 0) val execOpt = state.currentCommand val loggerName: String = s"bg-${task.key.label}-${generateId.incrementAndGet}" val channelName: Option[String] = execOpt flatMap (_.source map (_.channelName)) // val execId: Option[String] = execOpt flatMap { _.execId } val log = LogExchange.logger(loggerName, channelName, None) LogExchange.unbindLoggerAppenders(loggerName) - val consoleOpt = consoleLocally(state, console) + val consoleOpt = consoleLocally(state, console) map { + case a: ConsoleAppender => + a.setTrace(screenTrace) + a + case a => a + } LogExchange.bindLoggerAppenders( loggerName, (consoleOpt.toList map { _ -> screenLevel }) ::: (relay -> backingLevel) :: Nil From 306ee82db3e885ffadbaba9ced542c960363436f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 27 Jun 2018 07:09:59 -0400 Subject: [PATCH 353/356] Use MessageOnlyException for "Nonzero exit code" --- build.sbt | 2 +- run/src/main/scala/sbt/Run.scala | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 93237deed..ee744ae6c 100644 --- a/build.sbt +++ b/build.sbt @@ -302,7 +302,7 @@ lazy val runProj = (project in file("run")) exclude[DirectMissingMethodProblem]("sbt.OutputStrategy#LoggedOutput.copy$default$*"), ) ) - .configure(addSbtIO, addSbtUtilLogging, addSbtCompilerClasspath) + .configure(addSbtIO, addSbtUtilLogging, addSbtUtilControl, addSbtCompilerClasspath) val sbtProjDepsCompileScopeFilter = ScopeFilter(inDependencies(LocalProject("sbtProj"), includeRoot = false), inConfigurations(Compile)) diff --git a/run/src/main/scala/sbt/Run.scala b/run/src/main/scala/sbt/Run.scala index a1fb598d6..6c6e599a6 100644 --- a/run/src/main/scala/sbt/Run.scala +++ b/run/src/main/scala/sbt/Run.scala @@ -12,6 +12,7 @@ import java.lang.reflect.{ Method, Modifier } import Modifier.{ isPublic, isStatic } import sbt.internal.inc.classpath.ClasspathUtilities import sbt.internal.inc.ScalaInstance +import sbt.internal.util.MessageOnlyException import sbt.io.Path @@ -29,7 +30,9 @@ class ForkRun(config: ForkOptions) extends ScalaRun { if (exitCode == 0) Success(()) else Failure( - new RuntimeException(s"""Nonzero exit code returned from $label: $exitCode""".stripMargin) + new MessageOnlyException( + s"""Nonzero exit code returned from $label: $exitCode""".stripMargin + ) ) val process = fork(mainClass, classpath, options, log) def cancel() = { @@ -124,6 +127,6 @@ object Run { if (exitCode == 0) { log.debug("Exited with code 0") Success(()) - } else Failure(new RuntimeException("Nonzero exit code: " + exitCode)) + } else Failure(new MessageOnlyException("Nonzero exit code: " + exitCode)) } } From 76aa7299606aff3d45cf897b84e2adab9842de1d Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 27 Jun 2018 07:44:34 -0400 Subject: [PATCH 354/356] Increase the timeout to 90s Sometimes 30s is not enough. --- sbt/src/test/scala/testpkg/ServerSpec.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sbt/src/test/scala/testpkg/ServerSpec.scala b/sbt/src/test/scala/testpkg/ServerSpec.scala index 303e0c630..ccd753595 100644 --- a/sbt/src/test/scala/testpkg/ServerSpec.scala +++ b/sbt/src/test/scala/testpkg/ServerSpec.scala @@ -87,10 +87,13 @@ case class TestServer(baseDirectory: File) { if (n <= 0) sys.error(s"Timeout. $portfile is not found.") else { Thread.sleep(1000) + if ((n - 1) % 10 == 0) { + hostLog("waiting for the server...") + } waitForPortfile(n - 1) } } - waitForPortfile(30) + waitForPortfile(90) // make connection to the socket described in the portfile val (sk, tkn) = ClientSocket.socket(portfile) From 14a31634e753ded5a92d2f56e2f5ea49e6fc5c55 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 27 Jun 2018 21:06:40 -0400 Subject: [PATCH 355/356] Code formatting --- main-settings/src/main/scala/sbt/Scope.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main-settings/src/main/scala/sbt/Scope.scala b/main-settings/src/main/scala/sbt/Scope.scala index 9f8eecdf5..2d761f0b3 100644 --- a/main-settings/src/main/scala/sbt/Scope.scala +++ b/main-settings/src/main/scala/sbt/Scope.scala @@ -296,8 +296,9 @@ object Scope { val scope = Scope.replaceThis(GlobalScope)(rawScope) // This is a hot method that gets called many times - def expandDelegateScopes(resolvedProj: ResolvedReference)( - pLin: Seq[ScopeAxis[ResolvedReference]]): Vector[Scope] = { + def expandDelegateScopes( + resolvedProj: ResolvedReference + )(pLin: Seq[ScopeAxis[ResolvedReference]]): Vector[Scope] = { val tLin = scope.task match { case t @ Select(_) => linearize(t)(taskInherit) case _ => withZeroAxis(scope.task) From 17e0042bd8fe161450d5091ac9ceddfd82d51b83 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 27 Jun 2018 21:07:03 -0400 Subject: [PATCH 356/356] Util 1.2.0-M2, LM 1.2.0-M2 --- project/Dependencies.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index a7e6e69b1..4da547a9d 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -9,8 +9,8 @@ object Dependencies { // sbt modules private val ioVersion = "1.2.0-M2" - private val utilVersion = "1.2.0-M1" - private val lmVersion = "1.2.0-M1" + private val utilVersion = "1.2.0-M2" + private val lmVersion = "1.2.0-M2" private val zincVersion = "1.2.0-M1" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion