From 0bea7e9e182329f888d7c9b2f5ce8c3fc034defe Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 5 Mar 2018 18:06:46 +1000 Subject: [PATCH 01/34] 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 02/34] 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 03/34] 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 04/34] 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 05/34] 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 06/34] 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 07/34] 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 08/34] 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 09/34] 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 10/34] 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 11/34] 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 12/34] 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 13/34] 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 14/34] 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 15/34] 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 16/34] 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 17/34] 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 18/34] 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 19/34] 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 20/34] 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 21/34] 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 22/34] 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 23/34] 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 24/34] 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 25/34] 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 26/34] 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 27/34] 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 28/34] 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 29/34] 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 30/34] 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 31/34] 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 1fef1a5a6a29605e2a0c15f4fac35749ee210936 Mon Sep 17 00:00:00 2001 From: Hiroshi Ito Date: Wed, 7 Mar 2018 11:14:49 +0900 Subject: [PATCH 32/34] 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 33/34] 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 34/34] 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