From f77c4927bc807831d22187040eff36bf2fb6a0e1 Mon Sep 17 00:00:00 2001 From: Jacek Laskowski Date: Sat, 4 Jan 2014 17:19:41 +0100 Subject: [PATCH 01/38] deprecated says to use pair and so it's used in the body --- util/io/src/main/scala/sbt/Path.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/io/src/main/scala/sbt/Path.scala b/util/io/src/main/scala/sbt/Path.scala index a21e96bd8..3b79e17ae 100644 --- a/util/io/src/main/scala/sbt/Path.scala +++ b/util/io/src/main/scala/sbt/Path.scala @@ -118,7 +118,7 @@ sealed abstract class PathFinder final def \ (literal: String): PathFinder = this / literal @deprecated("Use pair.", "0.13.1") - def x_![T](mapper: File => Option[T]): Traversable[(File,T)] = x(mapper, false) + def x_![T](mapper: File => Option[T]): Traversable[(File,T)] = pair(mapper, false) /** Applies `mapper` to each path selected by this PathFinder and returns the path paired with the non-empty result. * If the result is empty (None) and `errorIfNone` is true, an exception is thrown. From dc246cade7027c0539f607758fc351a32529ad31 Mon Sep 17 00:00:00 2001 From: Jacek Laskowski Date: Mon, 6 Jan 2014 14:08:02 +0100 Subject: [PATCH 02/38] Javadoc changes --- launch/interface/src/main/java/xsbti/Exit.java | 6 ++++-- .../interface/src/main/java/xsbti/MainResult.java | 14 +++++++++----- launch/interface/src/main/java/xsbti/Reboot.java | 8 +++++--- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/launch/interface/src/main/java/xsbti/Exit.java b/launch/interface/src/main/java/xsbti/Exit.java index f88c8c591..3363fce39 100644 --- a/launch/interface/src/main/java/xsbti/Exit.java +++ b/launch/interface/src/main/java/xsbti/Exit.java @@ -1,7 +1,9 @@ package xsbti; -/** A launched application returns an instance of this class in order to communicate to the launcher -* that the application is completely finished and the launcher should exit with the given exit code.*/ +/** + * A launched application returns an instance of this class in order to communicate to the launcher + * that the application finished and the launcher should exit with the given exit code. + */ public interface Exit extends MainResult { public int code(); diff --git a/launch/interface/src/main/java/xsbti/MainResult.java b/launch/interface/src/main/java/xsbti/MainResult.java index e81aede2d..b6f27a680 100644 --- a/launch/interface/src/main/java/xsbti/MainResult.java +++ b/launch/interface/src/main/java/xsbti/MainResult.java @@ -1,8 +1,12 @@ package xsbti; -/** A launched application should return an instance of this from its 'run' method -* to communicate to the launcher what should be done now that the application -* has competed. This interface should be treated as 'sealed', with Exit and Reboot the only -* direct subtypes. -*/ +/** + * A launched application should return an instance of this from its 'run' method + * to communicate to the launcher what should be done now that the application + * has completed. This interface should be treated as 'sealed', with Exit and Reboot the only + * direct subtypes. + * + * @see xsbti.Exit + * @see xsbti.Reboot + */ public interface MainResult {} \ No newline at end of file diff --git a/launch/interface/src/main/java/xsbti/Reboot.java b/launch/interface/src/main/java/xsbti/Reboot.java index cb978c32a..0d6136a53 100644 --- a/launch/interface/src/main/java/xsbti/Reboot.java +++ b/launch/interface/src/main/java/xsbti/Reboot.java @@ -2,9 +2,11 @@ package xsbti; import java.io.File; -/** A launched application returns an instance of this class in order to communicate to the launcher -* that the application should be restarted. Different versions of the application and Scala can be used. -* The application can be given different arguments and a new working directory as well.*/ +/** + * A launched application returns an instance of this class in order to communicate to the launcher + * that the application should be restarted. Different versions of the application and Scala can be used. + * The application can be given different arguments as well as a new working directory. + */ public interface Reboot extends MainResult { public String[] arguments(); From f49ed56c1f66bec8a594a2c94b05316e5551318a Mon Sep 17 00:00:00 2001 From: Jacek Laskowski Date: Mon, 6 Jan 2014 14:17:42 +0100 Subject: [PATCH 03/38] Use string interpolation and replace deprecated methods --- main/command/src/main/scala/sbt/BasicCommandStrings.scala | 2 +- main/src/main/scala/sbt/CommandStrings.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main/command/src/main/scala/sbt/BasicCommandStrings.scala b/main/command/src/main/scala/sbt/BasicCommandStrings.scala index 3c8a38f14..237db8d7d 100644 --- a/main/command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main/command/src/main/scala/sbt/BasicCommandStrings.scala @@ -19,7 +19,7 @@ object BasicCommandStrings /** The command name to terminate the program.*/ val TerminateAction: String = Exit - def helpBrief = (HelpCommand, "Displays this help message or prints detailed help on requested commands (run 'help ').") + def helpBrief = (HelpCommand, s"Displays this help message or prints detailed help on requested commands (run '$HelpCommand ').") def helpDetailed = HelpCommand + """ Prints a help summary. diff --git a/main/src/main/scala/sbt/CommandStrings.scala b/main/src/main/scala/sbt/CommandStrings.scala index 073480c31..e85141ad8 100644 --- a/main/src/main/scala/sbt/CommandStrings.scala +++ b/main/src/main/scala/sbt/CommandStrings.scala @@ -38,7 +38,7 @@ s"""$multiTaskSyntax def multiTaskBrief = """Executes all of the specified tasks concurrently.""" - def showHelp = Help(ShowCommand, (ShowCommand + " ", actBrief), actDetailed) + def showHelp = Help(ShowCommand, (s"$ShowCommand ", showBrief), showDetailed) def showBrief = "Displays the result of evaluating the setting or task associated with 'key'." def showDetailed = s"""$ShowCommand From 6e72324b88e5141cd65bfa15be7b08c552ebb62c Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sat, 29 Mar 2014 13:06:27 -0400 Subject: [PATCH 04/38] Bump to our own nightly of ivy 2.4.0 (RC1+). * Testing the infrastructure to use our own instance of ivy * Allow us to push bug-fixes/performance directly into ivy on our own pace, as we submit patches back to ivy master --- ivy/src/main/scala/sbt/IvyLogger.scala | 2 +- project/Util.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ivy/src/main/scala/sbt/IvyLogger.scala b/ivy/src/main/scala/sbt/IvyLogger.scala index acd07781b..6e1b136fe 100644 --- a/ivy/src/main/scala/sbt/IvyLogger.scala +++ b/ivy/src/main/scala/sbt/IvyLogger.scala @@ -31,7 +31,7 @@ private final class IvyLoggerInterface(logger: Logger) extends MessageLogger def warn(msg: String) = logger.warn(msg) def error(msg: String) = if(SbtIvyLogger.acceptError(msg)) logger.error(msg) - private def emptyList = java.util.Collections.emptyList[T forSome { type T}] + private def emptyList = java.util.Collections.emptyList[String] def getProblems = emptyList def getWarns = emptyList def getErrors = emptyList diff --git a/project/Util.scala b/project/Util.scala index 125d46b81..f01bcbcdc 100644 --- a/project/Util.scala +++ b/project/Util.scala @@ -168,7 +168,7 @@ object Common def lib(m: ModuleID) = libraryDependencies += m lazy val jlineDep = "jline" % "jline" % "2.11" lazy val jline = lib(jlineDep) - lazy val ivy = lib("org.apache.ivy" % "ivy" % "2.3.0") + lazy val ivy = lib("org.scala-sbt.ivy" % "ivy" % "2.4.0-sbt-c41b2df1c586e0f647a9a5f006b2e7d49378c4a3") lazy val httpclient = lib("commons-httpclient" % "commons-httpclient" % "3.1") lazy val jsch = lib("com.jcraft" % "jsch" % "0.1.46" intransitive() ) lazy val sbinary = libraryDependencies <+= Util.nightly211(n => "org.scala-tools.sbinary" % "sbinary" % "0.4.2" cross(if(n) CrossVersion.full else CrossVersion.binary)) From d34ca37c409cd8ac81c97ee7d6ef2bcd2b042c4b Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Mon, 31 Mar 2014 16:02:39 -0400 Subject: [PATCH 05/38] Bump ivy version to include exclusion performance fix. --- project/Util.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Util.scala b/project/Util.scala index f01bcbcdc..251a7a109 100644 --- a/project/Util.scala +++ b/project/Util.scala @@ -168,7 +168,7 @@ object Common def lib(m: ModuleID) = libraryDependencies += m lazy val jlineDep = "jline" % "jline" % "2.11" lazy val jline = lib(jlineDep) - lazy val ivy = lib("org.scala-sbt.ivy" % "ivy" % "2.4.0-sbt-c41b2df1c586e0f647a9a5f006b2e7d49378c4a3") + lazy val ivy = lib("org.scala-sbt.ivy" % "ivy" % "2.4.0-sbt-d6fca11d63402c92e4167cdf2da91a660d043392") lazy val httpclient = lib("commons-httpclient" % "commons-httpclient" % "3.1") lazy val jsch = lib("com.jcraft" % "jsch" % "0.1.46" intransitive() ) lazy val sbinary = libraryDependencies <+= Util.nightly211(n => "org.scala-tools.sbinary" % "sbinary" % "0.4.2" cross(if(n) CrossVersion.full else CrossVersion.binary)) From 60a457d0833d63a67d7c0bbf0aced3305a65dab5 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 4 Apr 2014 16:38:44 -0400 Subject: [PATCH 06/38] Ensure that if artifact is published, we overwrite default checksums. Fixes # 1233 * Add a new "shim" for RepositoryResolvers that modifies the "put" method so that it will ignore the overwrite flag for checksums. --- ivy/src/main/scala/sbt/ConvertResolver.scala | 77 +++++++++++++++++++- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/ivy/src/main/scala/sbt/ConvertResolver.scala b/ivy/src/main/scala/sbt/ConvertResolver.scala index a93a57011..ab1cf95ce 100644 --- a/ivy/src/main/scala/sbt/ConvertResolver.scala +++ b/ivy/src/main/scala/sbt/ConvertResolver.scala @@ -10,13 +10,84 @@ import core.module.id.ModuleRevisionId import core.module.descriptor.DependencyDescriptor import core.resolve.ResolveData import core.settings.IvySettings -import plugins.resolver.{BasicResolver, DependencyResolver, IBiblioResolver} +import plugins.resolver.{BasicResolver, DependencyResolver, IBiblioResolver, RepositoryResolver} import plugins.resolver.{AbstractPatternsBasedResolver, AbstractSshBasedResolver, FileSystemResolver, SFTPResolver, SshResolver, URLResolver} import plugins.repository.url.{URLRepository => URLRepo} import plugins.repository.file.{FileRepository => FileRepo, FileResource} +import java.io.File +import org.apache.ivy.util.ChecksumHelper +import org.apache.ivy.core.module.descriptor.{Artifact=>IArtifact} + private object ConvertResolver { + /** This class contains all the reflective lookups used in the + * checksum-friendly URL publishing shim. + */ + private object ChecksumFriendlyURLResolver { + import java.lang.reflect.AccessibleObject + private def reflectiveLookup[A <: AccessibleObject](f: Class[_] => A): Option[A] = + try { + val cls = classOf[RepositoryResolver] + val thing = f(cls) + thing.setAccessible(true) + Some(thing) + } catch { + case e: java.lang.ReflectiveOperationException => None + } + private val signerNameField: Option[java.lang.reflect.Field] = + reflectiveLookup(_.getDeclaredField("signerName")) + private val putChecksumMethod: Option[java.lang.reflect.Method] = + reflectiveLookup(_.getDeclaredMethod("putChecksum", + classOf[IArtifact], classOf[File], classOf[String], + classOf[Boolean], classOf[String])) + private val putSignatureMethod: Option[java.lang.reflect.Method] = + reflectiveLookup(_.getDeclaredMethod("putSignature", + classOf[IArtifact], classOf[File], classOf[String], + classOf[Boolean])) + } + /** + * The default behavior of ivy's overwrite flags ignores the fact that a lot of repositories + * will autogenerate checksums *for* an artifact if it doesn't already exist. Therefore + * if we succeed in publishing an artifact, we need to just blast the checksums in place. + * This acts as a "shim" on RepositoryResolvers so that we can hook our methods into + * both the IBiblioResolver + URLResolver without having to duplicate the code in two + * places. However, this does mean our use of reflection is awesome. + * + * TODO - See about contributing back to ivy. + */ + private trait ChecksumFriendlyURLResolver extends RepositoryResolver { + import ChecksumFriendlyURLResolver._ + private def signerName: String = signerNameField match { + case Some(field) => field.get(this).asInstanceOf[String] + case None => null + } + override protected def put(artifact: IArtifact, src: File, dest: String, overwrite: Boolean): Unit = { + // verify the checksum algorithms before uploading artifacts! + val checksums = getChecksumAlgorithms() + val repository = getRepository() + for { + checksum <- checksums + if !ChecksumHelper.isKnownAlgorithm(checksum) + } throw new IllegalArgumentException("Unknown checksum algorithm: " + checksum) + repository.put(artifact, src, dest, overwrite); + // Fix for sbt#1156 - Artifactory will auto-generate MD5/sha1 files, so + // we need to overwrite what it has. + for (checksum <- checksums) { + putChecksumMethod match { + case Some(method) => method.invoke(this, artifact, src, dest, true: java.lang.Boolean, checksum) + case None => // TODO - issue warning? + } + } + if (signerName != null) { + putSignatureMethod match { + case None => () + case Some(method) => method.invoke(artifact, src, dest, true: java.lang.Boolean) + } + } + } + } + /** Converts the given sbt resolver into an Ivy resolver..*/ def apply(r: Resolver, settings: IvySettings, log: Logger) = { @@ -25,7 +96,7 @@ private object ConvertResolver case repo: MavenRepository => { val pattern = Collections.singletonList(Resolver.resolvePattern(repo.root, Resolver.mavenStyleBasePattern)) - final class PluginCapableResolver extends IBiblioResolver with DescriptorRequired { + final class PluginCapableResolver extends IBiblioResolver with ChecksumFriendlyURLResolver with DescriptorRequired { def setPatterns() { // done this way for access to protected methods. setArtifactPatterns(pattern) setIvyPatterns(pattern) @@ -77,7 +148,7 @@ private object ConvertResolver } case repo: URLRepository => { - val resolver = new URLResolver with DescriptorRequired + val resolver = new URLResolver with ChecksumFriendlyURLResolver with DescriptorRequired resolver.setName(repo.name) initializePatterns(resolver, repo.patterns, settings) resolver From e8dd19cb92b3c3befa00d02c899629eeaf279153 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 4 Apr 2014 20:25:37 -0400 Subject: [PATCH 07/38] Remove JDK7 features from Resolver shim. --- ivy/src/main/scala/sbt/ConvertResolver.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ivy/src/main/scala/sbt/ConvertResolver.scala b/ivy/src/main/scala/sbt/ConvertResolver.scala index ab1cf95ce..74c5c119c 100644 --- a/ivy/src/main/scala/sbt/ConvertResolver.scala +++ b/ivy/src/main/scala/sbt/ConvertResolver.scala @@ -25,15 +25,22 @@ private object ConvertResolver * checksum-friendly URL publishing shim. */ private object ChecksumFriendlyURLResolver { - import java.lang.reflect.AccessibleObject + // TODO - When we dump JDK6 support we can remove this hackery + // import java.lang.reflect.AccessibleObject + type AccessibleObject = { + def setAccessible(value: Boolean): Unit + } private def reflectiveLookup[A <: AccessibleObject](f: Class[_] => A): Option[A] = try { val cls = classOf[RepositoryResolver] val thing = f(cls) + import scala.language.reflectiveCalls thing.setAccessible(true) Some(thing) } catch { - case e: java.lang.ReflectiveOperationException => None + case (_: java.lang.NoSuchFieldException) | + (_: java.lang.SecurityException) | + (_: java.lang.NoSuchMethodException) => None } private val signerNameField: Option[java.lang.reflect.Field] = reflectiveLookup(_.getDeclaredField("signerName")) From d8ee16c600b54105b270a90c26fa37daab412e6f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 5 Apr 2014 01:51:08 -0700 Subject: [PATCH 08/38] Roll back sbt/sbt@482f99 (aggressive deletion of cache). Fixes #1091. * sbt 0.12.3 introduced sbt/sbt@482f99 to fix #637 and #641. * The underlying issue of #641 was an incorrect classifier, and it was fixed in sbt/sbt@718fa9 for #683 and shipped as sbt 0.13.0. --- ivy/src/main/scala/sbt/Ivy.scala | 36 -------------------------------- 1 file changed, 36 deletions(-) diff --git a/ivy/src/main/scala/sbt/Ivy.scala b/ivy/src/main/scala/sbt/Ivy.scala index 2408992e6..a6519c6bc 100644 --- a/ivy/src/main/scala/sbt/Ivy.scala +++ b/ivy/src/main/scala/sbt/Ivy.scala @@ -7,7 +7,6 @@ import Resolver.PluginPattern import java.io.File import java.net.URI -import java.text.ParseException import java.util.concurrent.Callable import java.util.{Collection, Collections => CS} import CS.singleton @@ -24,9 +23,7 @@ import core.settings.IvySettings import plugins.latest.LatestRevisionStrategy import plugins.matcher.PatternMatcher import plugins.parser.m2.PomModuleDescriptorParser -import plugins.repository.ResourceDownloader import plugins.resolver.{ChainResolver, DependencyResolver} -import plugins.resolver.util.ResolvedResource import util.{Message, MessageLogger} import util.extendable.ExtendableItem @@ -358,41 +355,8 @@ private object IvySbt case pr: ProjectResolver => true case _ => false } - /** This is overridden to delete outofdate artifacts of changing modules that are not listed in the metadata. - * This occurs for artifacts with classifiers, for example. */ - @throws(classOf[ParseException]) - override def cacheModuleDescriptor(resolver: DependencyResolver, mdRef: ResolvedResource, dd: DependencyDescriptor, moduleArtifact: IArtifact, downloader: ResourceDownloader, options: CacheMetadataOptions): ResolvedModuleRevision = - { - val rmrRaw = super.cacheModuleDescriptor(null, mdRef, dd, moduleArtifact, downloader, options) - val rmr = resetArtifactResolver(rmrRaw) - val mrid = moduleArtifact.getModuleRevisionId - def shouldClear(): Boolean = rmr != null && - ( (rmr.getReport != null && rmr.getReport.isSearched && isChanging(dd, mrid)) || - isProjectResolver(rmr.getResolver) ) - // only handle changing modules whose metadata actually changed. - // Typically, the publication date in the metadata has to change to get here. - if(shouldClear()) { - // this is the locally cached metadata as originally retrieved (e.g. the pom) - val original = rmr.getReport.getOriginalLocalFile - if(original != null) { - // delete all files in subdirectories that are older than the original metadata file's publication date - // The publication date is used because the metadata will be redownloaded for changing files, - // so the last modified time changes, but the publication date doesn't - val pubDate = rmrRaw.getPublicationDate - val lm = if(pubDate eq null) original.lastModified else pubDate.getTime - val indirectFiles = PathFinder(original.getParentFile).*(DirectoryFilter).**(-DirectoryFilter).get.toList - val older = indirectFiles.filter(f => f.lastModified < lm).toList - Message.verbose("Deleting additional old artifacts from cache for changed module " + mrid + older.mkString(":\n\t", "\n\t", "")) - IO.delete(older) - } - } - rmr - } // ignore the original resolver wherever possible to avoid issues like #704 override def saveResolvers(descriptor: ModuleDescriptor, metadataResolverName: String, artifactResolverName: String) {} - - def isChanging(dd: DependencyDescriptor, requestedRevisionId: ModuleRevisionId): Boolean = - !localOnly && (dd.isChanging || requestedRevisionId.getRevision.contains("-SNAPSHOT")) } manager.setArtifactPattern(PluginPattern + manager.getArtifactPattern) manager.setDataFilePattern(PluginPattern + manager.getDataFilePattern) From 12a799c9293982ffa285daf0a4720ee90f6ed1d2 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 7 Apr 2014 11:25:27 +0200 Subject: [PATCH 09/38] Add two pending tests for sbt/sbt#1237 Add two scripted tests that illustrate a problem occurring with Scala 2.11-RC3 where some macros have themselves attached as original tree, which causes a stack overflow during dependency extraction. --- .../macro-client/Client.scala | 5 ++++ .../macro-arg-dep-2-11/macro-client/Foo.scala | 5 ++++ .../macro-client/changes/Foo.scala | 3 ++ .../macro-provider/Provider.scala | 12 ++++++++ .../macro-arg-dep-2-11/pending | 12 ++++++++ .../macro-arg-dep-2-11/project/build.scala | 30 +++++++++++++++++++ .../macro-client/Client.scala | 5 ++++ .../macro-client/Foo.scala | 5 ++++ .../macro-client/changes/Foo.scala | 3 ++ .../macro-provider/Provider.scala | 12 ++++++++ .../macro-arg-dep-nested-2-11/pending | 13 ++++++++ .../project/build.scala | 30 +++++++++++++++++++ 12 files changed, 135 insertions(+) create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-client/Client.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-client/Foo.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-client/changes/Foo.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-provider/Provider.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/pending create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/project/build.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-client/Client.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-client/Foo.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-client/changes/Foo.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-provider/Provider.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/pending create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/project/build.scala diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-client/Client.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-client/Client.scala new file mode 100644 index 000000000..0ecbe6fce --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-client/Client.scala @@ -0,0 +1,5 @@ +package macros + +object Client { + Provider.printTree(Foo.str) +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-client/Foo.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-client/Foo.scala new file mode 100644 index 000000000..6f410fca2 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-client/Foo.scala @@ -0,0 +1,5 @@ +package macros + +object Foo { + def str: String = "abc" +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-client/changes/Foo.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-client/changes/Foo.scala new file mode 100644 index 000000000..4f2a62b39 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-client/changes/Foo.scala @@ -0,0 +1,3 @@ +package macros +object Foo { +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-provider/Provider.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-provider/Provider.scala new file mode 100644 index 000000000..b39b4c282 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/macro-provider/Provider.scala @@ -0,0 +1,12 @@ +package macros +import scala.language.experimental.macros +import scala.reflect.macros._ + +object Provider { + def printTree(arg: Any) = macro printTreeImpl + def printTreeImpl(c: Context)(arg: c.Expr[Any]): c.Expr[String] = { + val argStr = arg.tree.toString + val literalStr = c.universe.Literal(c.universe.Constant(argStr)) + c.Expr[String](literalStr) + } +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/pending b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/pending new file mode 100644 index 000000000..183aa6c49 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/pending @@ -0,0 +1,12 @@ +> compile + +# remove `Foo.str` which is an argument to a macro +$ copy-file macro-client/changes/Foo.scala macro-client/Foo.scala + +# we should recompile Foo.scala first and then fail to compile Client.scala due to missing +# `Foo.str` +-> macro-client/compile + +> clean + +-> compile diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/project/build.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/project/build.scala new file mode 100644 index 000000000..89cd91ef8 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/project/build.scala @@ -0,0 +1,30 @@ +import sbt._ +import Keys._ + +object build extends Build { + val defaultSettings = Seq( + libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-reflect" % _ ), + incOptions := incOptions.value.withNameHashing(true), + scalaVersion := "2.11.0-RC3" + ) + + lazy val root = Project( + base = file("."), + id = "macro", + aggregate = Seq(macroProvider, macroClient), + settings = Defaults.defaultSettings ++ defaultSettings + ) + + lazy val macroProvider = Project( + base = file("macro-provider"), + id = "macro-provider", + settings = Defaults.defaultSettings ++ defaultSettings + ) + + lazy val macroClient = Project( + base = file("macro-client"), + id = "macro-client", + dependencies = Seq(macroProvider), + settings = Defaults.defaultSettings ++ defaultSettings + ) +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-client/Client.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-client/Client.scala new file mode 100644 index 000000000..76c16af24 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-client/Client.scala @@ -0,0 +1,5 @@ +package macros + +object Client { + Provider.printTree(Provider.printTree(Foo.str)) +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-client/Foo.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-client/Foo.scala new file mode 100644 index 000000000..6f410fca2 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-client/Foo.scala @@ -0,0 +1,5 @@ +package macros + +object Foo { + def str: String = "abc" +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-client/changes/Foo.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-client/changes/Foo.scala new file mode 100644 index 000000000..4f2a62b39 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-client/changes/Foo.scala @@ -0,0 +1,3 @@ +package macros +object Foo { +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-provider/Provider.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-provider/Provider.scala new file mode 100644 index 000000000..b39b4c282 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/macro-provider/Provider.scala @@ -0,0 +1,12 @@ +package macros +import scala.language.experimental.macros +import scala.reflect.macros._ + +object Provider { + def printTree(arg: Any) = macro printTreeImpl + def printTreeImpl(c: Context)(arg: c.Expr[Any]): c.Expr[String] = { + val argStr = arg.tree.toString + val literalStr = c.universe.Literal(c.universe.Constant(argStr)) + c.Expr[String](literalStr) + } +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/pending b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/pending new file mode 100644 index 000000000..231939418 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/pending @@ -0,0 +1,13 @@ +> compile + +# remove `Foo.str` which is an argument to a macro +# (this macro itself that is an argument to another macro) +$ copy-file macro-client/changes/Foo.scala macro-client/Foo.scala + +# we should recompile Foo.scala first and then fail to compile Client.scala due to missing +# `Foo.str` +-> macro-client/compile + +> clean + +-> compile diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/project/build.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/project/build.scala new file mode 100644 index 000000000..89cd91ef8 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/project/build.scala @@ -0,0 +1,30 @@ +import sbt._ +import Keys._ + +object build extends Build { + val defaultSettings = Seq( + libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-reflect" % _ ), + incOptions := incOptions.value.withNameHashing(true), + scalaVersion := "2.11.0-RC3" + ) + + lazy val root = Project( + base = file("."), + id = "macro", + aggregate = Seq(macroProvider, macroClient), + settings = Defaults.defaultSettings ++ defaultSettings + ) + + lazy val macroProvider = Project( + base = file("macro-provider"), + id = "macro-provider", + settings = Defaults.defaultSettings ++ defaultSettings + ) + + lazy val macroClient = Project( + base = file("macro-client"), + id = "macro-client", + dependencies = Seq(macroProvider), + settings = Defaults.defaultSettings ++ defaultSettings + ) +} From a80966e394b8f119b0875d9a9a7994591bdaf1d8 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 7 Apr 2014 11:33:47 +0200 Subject: [PATCH 10/38] Handle macros that have themselves as original tree It has been reported in sbt/sbt#1237 that stack overflows may occur during the extraction of used names (and later of dependencies between files). This problem has been introduced by sbt/sbt#1163, which was about recording the dependencies of macro arguments. When a macro is expanded, the compiler attaches the tree before expansion to the tree representing the expanded macro. As of Scala 2.11-RC3, some macros have themselves attached as original tree, which caused the same macro to be inspected over and over until a stack overflow. This commit solves this problem by making sure that the original of a macro expansion will be inspected if and only if it is different from the expanded tree. Fixes sbt/sbt#1237 --- compile/interface/src/main/scala/xsbt/Dependency.scala | 7 ++++++- .../interface/src/main/scala/xsbt/ExtractUsedNames.scala | 8 +++++++- .../macro-arg-dep-2-11/{pending => test} | 0 .../macro-arg-dep-nested-2-11/{pending => test} | 0 4 files changed, 13 insertions(+), 2 deletions(-) rename sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/{pending => test} (100%) rename sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/{pending => test} (100%) diff --git a/compile/interface/src/main/scala/xsbt/Dependency.scala b/compile/interface/src/main/scala/xsbt/Dependency.scala index b8a55c8a9..77dd9355f 100644 --- a/compile/interface/src/main/scala/xsbt/Dependency.scala +++ b/compile/interface/src/main/scala/xsbt/Dependency.scala @@ -146,7 +146,12 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile deps.foreach(addDependency) case Template(parents, self, body) => traverseTrees(body) - case MacroExpansionOf(original) => + /* + * Some macros appear to contain themselves as original tree + * In this case, we don't need to inspect the original tree because + * we already inspected its expansion, which is equal. + */ + case MacroExpansionOf(original) if original != tree => this.traverse(original) case other => () } diff --git a/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala b/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala index 6ab01c9eb..1bcaf125f 100644 --- a/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala +++ b/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala @@ -55,7 +55,13 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext } def handleTreeNode(node: Tree): Unit = { - def handleMacroExpansion(original: Tree): Unit = original.foreach(handleTreeNode) + def handleMacroExpansion(original: Tree): Unit = { + // Some macros seem to have themselves registered as original tree. + // In this case, we only need to handle the children of the original tree, + // because we already handled the expanded tree. + if(original == node) original.children.foreach(handleTreeNode) + else original.foreach(handleTreeNode) + } def handleClassicTreeNode(node: Tree): Unit = node match { case _: DefTree | _: Template => () diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/pending b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/test similarity index 100% rename from sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/pending rename to sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/test diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/pending b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/test similarity index 100% rename from sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/pending rename to sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/test From 5c268bd9ea60df958710eb4d46570a5b6b252282 Mon Sep 17 00:00:00 2001 From: Jacek Laskowski Date: Mon, 7 Apr 2014 22:31:40 +0200 Subject: [PATCH 11/38] Follow deprecation - @deprecated says to use pair and so it's used in the body --- util/io/src/main/scala/sbt/Path.scala | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/util/io/src/main/scala/sbt/Path.scala b/util/io/src/main/scala/sbt/Path.scala index 3b79e17ae..10ec194c3 100644 --- a/util/io/src/main/scala/sbt/Path.scala +++ b/util/io/src/main/scala/sbt/Path.scala @@ -124,19 +124,15 @@ sealed abstract class PathFinder * If the result is empty (None) and `errorIfNone` is true, an exception is thrown. * If `errorIfNone` is false, the path is dropped from the returned Traversable.*/ def pair[T](mapper: File => Option[T], errorIfNone: Boolean = true): Seq[(File,T)] = - x(mapper, errorIfNone) + { + val apply = if(errorIfNone) mapper | fail else mapper + for(file <- get; mapped <- apply(file)) yield (file, mapped) + } - /** Applies `mapper` to each path selected by this PathFinder and returns the path paired with the non-empty result. - * If the result is empty (None) and `errorIfNone` is true, an exception is thrown. - * If `errorIfNone` is false, the path is dropped from the returned Traversable.*/ @deprecated("Use pair.", "0.13.1") - def x[T](mapper: File => Option[T], errorIfNone: Boolean = true): Seq[(File,T)] = - { - val apply = if(errorIfNone) mapper | fail else mapper - for(file <- get; mapped <- apply(file)) yield (file, mapped) - } + def x[T](mapper: File => Option[T], errorIfNone: Boolean = true): Seq[(File,T)] = pair(mapper, errorIfNone) - /** Selects all descendant paths with a name that matches include and do not have an intermediate + /** Selects all descendant paths with a name that matches include and do not have an intermediate * path with a name that matches intermediateExclude. Typical usage is: * * descendantsExcept("*.jar", ".svn")*/ From 2686acd99326d99bf2f17a488858d6df5e27d52a Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Mon, 7 Apr 2014 16:42:08 -0400 Subject: [PATCH 12/38] Part #1 of cancel-task-hooks - Hooks EvaluateTask. * Create a new EvaluateTaskConfig which gives us a bit more freedom over changign config options to EvaluateTask in the future. * Create adapted from old EvaluateTask to new EvaluateTask * Add hooks into signals class to register/remote a signal listener directly, rather than in an "arm" block. * Create TaskEvaluationCancelHandler to control the strategy of who/whom can cancel (sbt-server vs. sbt-terminal). * Create a null-object for the "can't cancel" scenario so the code path is exactly the same. This commit does not wire settings into the build yet, nor does it fix the config extractio methods. --- main/src/main/scala/sbt/EvaluateTask.scala | 98 ++++++++++++++++--- .../src/main/scala/sbt/Signal.scala | 32 ++++++ 2 files changed, 118 insertions(+), 12 deletions(-) diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 4c8fe8756..59010eeb7 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -14,7 +14,73 @@ package sbt import std.Transform.{DummyTaskMap,TaskAndValue} import TaskName._ +@deprecated("Use EvaluateTaskConfig instead.", "0.13.2") final case class EvaluateConfig(cancelable: Boolean, restrictions: Seq[Tags.Rule], checkCycles: Boolean = false, progress: ExecuteProgress[Task] = EvaluateTask.defaultProgress) + + + +/** Represents something that can be cancelled. */ +trait TaskCancel { + /** cancels whatever this points at. */ + def cancel(): Unit +} +/** A handler for registering/remmoving listeners that allow you to cancel tasks. */ +trait TaskEvaluationCancelHandler { + /* Evaluation is starting, here's a mechanism to cancel things. */ + def start(canceller: TaskCancel): Unit + /* Task Evaluation is complete, whether success or failure. */ + def finish(): Unit +} +object TaskEvaluationCancelHandler { + /** An empty handler that does nothing. */ + object Null extends TaskEvaluationCancelHandler { + def start(canceller: TaskCancel): Unit = () + def finish(): Unit = () + } + /** Cancel handler which registers for SIGINT and cancels tasks when it is received. */ + object Signal extends TaskEvaluationCancelHandler { + private var registration: Option[Signals.Registration] = None + def start(canceller: TaskCancel): Unit = { + registration = Some(Signals.register(() => canceller.cancel())) + } + def finish(): Unit = + registration match { + case Some(value) => + value.remove() + registration = None + case None => + } + } +} + + +/** The new API for running tasks. + * + * This represents all the hooks possible when running the task engine. + * We expose this trait so that we can, in a binary compatible way, modify what is used + * inside this configuration and how to construct it. + */ +sealed trait EvaluateTaskConfig { + def restrictions: Seq[Tags.Rule] + def checkCycles: Boolean + def progressReporter: ExecuteProgress[Task] + def cancelHandler: TaskEvaluationCancelHandler +} +final object EvaluateTaskConfig { + /** Pulls in the old configuration format. */ + def apply(old: EvaluateConfig): EvaluateTaskConfig = { + object AdaptedTaskConfig extends EvaluateTaskConfig { + def restrictions: Seq[Tags.Rule] = old.restrictions + def checkCycles: Boolean = old.checkCycles + def progressReporter: ExecuteProgress[Task] = old.progress + def cancelHandler: TaskEvaluationCancelHandler = + if(old.cancelable) TaskEvaluationCancelHandler.Signal + else TaskEvaluationCancelHandler.Null + } + AdaptedTaskConfig + } +} + final case class PluginData(dependencyClasspath: Seq[Attributed[File]], definitionClasspath: Seq[Attributed[File]], resolvers: Option[Seq[Resolver]], report: Option[UpdateReport], scalacOptions: Seq[String]) { val classpath: Seq[Attributed[File]] = definitionClasspath ++ dependencyClasspath @@ -176,12 +242,18 @@ object EvaluateTask def nodeView[HL <: HList](state: State, streams: Streams, roots: Seq[ScopedKey[_]], dummies: DummyTaskMap = DummyTaskMap(Nil)): NodeView[Task] = Transform((dummyRoots, roots) :: (dummyStreamsManager, streams) :: (dummyState, state) :: dummies ) - def runTask[T](root: Task[T], state: State, streams: Streams, triggers: Triggers[Task], config: EvaluateConfig)(implicit taskToNode: NodeView[Task]): (State, Result[T]) = + @deprecated("Use new EvalauteTaskConfig option to runTask", "0.13.2") + def runTask[T](root: Task[T], state: State, streams: Streams, triggers: Triggers[Task], config: EvaluateConfig)(implicit taskToNode: NodeView[Task]): (State, Result[T]) = + { + val newConfig = EvaluateTaskConfig(config) + runTask(root, state, streams, triggers, config)(taskToNode) + } + def runTask[T](root: Task[T], state: State, streams: Streams, triggers: Triggers[Task], config: EvaluateTaskConfig)(implicit taskToNode: NodeView[Task]): (State, Result[T]) = { - import ConcurrentRestrictions.{completionService, TagMap, Tag, tagged, tagsKey} + import ConcurrentRestrictions.{completionService, TagMap, Tag, tagged, tagsKey} val log = state.log - log.debug("Running task... Cancelable: " + config.cancelable + ", check cycles: " + config.checkCycles) + log.debug("Running task... Cancel: " + config.cancelHandler + ", check cycles: " + config.checkCycles) val tags = tagged[Task[_]](_.info get tagsKey getOrElse Map.empty, Tags.predicate(config.restrictions)) val (service, shutdown) = completionService[Task[_], Completed](tags, (s: String) => log.warn(s)) @@ -191,7 +263,7 @@ object EvaluateTask case _ => true } def run() = { - val x = new Execute[Task]( Execute.config(config.checkCycles, overwriteNode), triggers, config.progress)(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) @@ -203,15 +275,17 @@ object EvaluateTask logIncResult(replaced, state, streams) (newState, replaced) } - val cancel = () => { - println("") - log.warn("Canceling execution...") - shutdown() + object taskCancel extends TaskCancel { + def cancel(): Unit = { + println("") + log.warn("Canceling execution...") + shutdown() + } } - if(config.cancelable) - Signals.withHandler(cancel) { run } - else - run() + // Register with our cancel handler we're about to start. + config.cancelHandler.start(taskCancel) + try run() + finally config.cancelHandler.finish() } private[this] def storeValuesForPrevious(results: RMap[Task, Result], state: State, streams: Streams): Unit = diff --git a/util/collection/src/main/scala/sbt/Signal.scala b/util/collection/src/main/scala/sbt/Signal.scala index 8bad472cd..0069e4b53 100644 --- a/util/collection/src/main/scala/sbt/Signal.scala +++ b/util/collection/src/main/scala/sbt/Signal.scala @@ -19,6 +19,38 @@ object Signals case Right(v) => v } } + + /** Helper interface so we can expose internals of signal-isms to others. */ + sealed trait Registration { + def remove(): Unit + } + /** Register a signal handler that can be removed later. + * NOTE: Does not stack with other signal handlers!!!! + */ + def register(handler: () => Unit, signal: String = INT): Registration = + // TODO - Maybe we can just ignore things if not is-supported. + if(supported(signal)) { + import sun.misc.{Signal,SignalHandler} + val intSignal = new Signal(signal) + val newHandler = new SignalHandler { + def handle(sig: Signal) { handler() } + } + val oldHandler = Signal.handle(intSignal, newHandler) + object unregisterNewHandler extends Registration { + override def remove(): Unit = { + Signal.handle(intSignal, oldHandler) + } + } + unregisterNewHandler + } else { + // TODO - Maybe we should just throw an exception if we don't support signals... + object NullUnregisterNewHandler extends Registration { + override def remove(): Unit = () + } + NullUnregisterNewHandler + } + + def supported(signal: String): Boolean = try { From 6e480fc2b6685861ab78aa5f48292633900d9e50 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Mon, 7 Apr 2014 16:54:32 -0400 Subject: [PATCH 13/38] Fix deprecated 'since' clauses to reflect our actual release versions. --- main/src/main/scala/sbt/EvaluateTask.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 59010eeb7..842ba403d 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -14,7 +14,7 @@ package sbt import std.Transform.{DummyTaskMap,TaskAndValue} import TaskName._ -@deprecated("Use EvaluateTaskConfig instead.", "0.13.2") +@deprecated("Use EvaluateTaskConfig instead.", "0.13.5") final case class EvaluateConfig(cancelable: Boolean, restrictions: Seq[Tags.Rule], checkCycles: Boolean = false, progress: ExecuteProgress[Task] = EvaluateTask.defaultProgress) @@ -242,7 +242,7 @@ object EvaluateTask def nodeView[HL <: HList](state: State, streams: Streams, roots: Seq[ScopedKey[_]], dummies: DummyTaskMap = DummyTaskMap(Nil)): NodeView[Task] = Transform((dummyRoots, roots) :: (dummyStreamsManager, streams) :: (dummyState, state) :: dummies ) - @deprecated("Use new EvalauteTaskConfig option to runTask", "0.13.2") + @deprecated("Use new EvalauteTaskConfig option to runTask", "0.13.5") def runTask[T](root: Task[T], state: State, streams: Streams, triggers: Triggers[Task], config: EvaluateConfig)(implicit taskToNode: NodeView[Task]): (State, Result[T]) = { val newConfig = EvaluateTaskConfig(config) From 171eb19b96361d5df1558fb01e75c19c27fda805 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 8 Apr 2014 09:23:49 -0400 Subject: [PATCH 14/38] Part #2 of task cancellation hooks. * Expose new EvaluateTaskConfig throughout all the APIs * Create a key for cancellation configuration * Add default values for cancellation in GlobalPlugin * Create a test to ensure that cancellation can cancel tasks. * Deprecate all the existing mechanisms of evaluating tasks which use the EvaluateConfig API. --- main/src/main/scala/sbt/Aggregation.scala | 2 +- main/src/main/scala/sbt/Defaults.scala | 4 ++ main/src/main/scala/sbt/EvaluateTask.scala | 39 ++++++++++++++++--- main/src/main/scala/sbt/Keys.scala | 1 + main/src/main/scala/sbt/Project.scala | 14 ++++++- .../sbt-test/actions/task-cancel/build.sbt | 8 ++++ .../actions/task-cancel/project/Build.scala | 5 +++ .../task-cancel/src/main/scala/test.scala | 5 +++ sbt/src/sbt-test/actions/task-cancel/test | 3 ++ 9 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 sbt/src/sbt-test/actions/task-cancel/build.sbt create mode 100644 sbt/src/sbt-test/actions/task-cancel/project/Build.scala create mode 100644 sbt/src/sbt-test/actions/task-cancel/src/main/scala/test.scala create mode 100644 sbt/src/sbt-test/actions/task-cancel/test diff --git a/main/src/main/scala/sbt/Aggregation.scala b/main/src/main/scala/sbt/Aggregation.scala index 57b7c8c4f..60e9b64bb 100644 --- a/main/src/main/scala/sbt/Aggregation.scala +++ b/main/src/main/scala/sbt/Aggregation.scala @@ -59,7 +59,7 @@ final object Aggregation import extracted.structure val toRun = ts map { case KeyValue(k,t) => t.map(v => KeyValue(k,v)) } join; val roots = ts map { case KeyValue(k,_) => k } - val config = extractedConfig(extracted, structure, s) + val config = extractedTaskConfig(extracted, structure, s) val start = System.currentTimeMillis val (newS, result) = withStreams(structure, s){ str => diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 04b73d5ed..ba14788e9 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -120,6 +120,10 @@ object Defaults extends BuildCommon trapExit :== true, connectInput :== false, cancelable :== false, + taskCancelHandler := { state: State => + if(cancelable.value) TaskEvaluationCancelHandler.Signal + else TaskEvaluationCancelHandler.Null + }, envVars :== Map.empty, sbtVersion := appConfiguration.value.provider.id.version, sbtBinaryVersion := binarySbtVersion(sbtVersion.value), diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 842ba403d..4931ef077 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -79,6 +79,19 @@ final object EvaluateTaskConfig { } AdaptedTaskConfig } + /** Raw constructor for EvaluateTaskConfig. */ + def apply(restrictions: Seq[Tags.Rule], + checkCycles: Boolean, + progressReporter: ExecuteProgress[Task], + cancelHandler: TaskEvaluationCancelHandler): EvaluateTaskConfig = { + object SimpleEvaluateTaskConfig extends EvaluateTaskConfig { + def restrictions = restrictions + def checkCycles = checkCycles + def progressReporter = progressReporter + def cancelHandler = cancelHandler + } + SimpleEvaluateTaskConfig + } } final case class PluginData(dependencyClasspath: Seq[Attributed[File]], definitionClasspath: Seq[Attributed[File]], resolvers: Option[Seq[Resolver]], report: Option[UpdateReport], scalacOptions: Seq[String]) @@ -106,24 +119,25 @@ object EvaluateTask val SystemProcessors = Runtime.getRuntime.availableProcessors - @deprecated("Use extractedConfig.", "0.13.0") + @deprecated("Use extractedTaskConfig.", "0.13.0") def defaultConfig(state: State): EvaluateConfig = { val extracted = Project.extract(state) extractedConfig(extracted, extracted.structure, state) } - @deprecated("Use extractedConfig.", "0.13.0") + @deprecated("Use extractedTaskConfig.", "0.13.0") def defaultConfig(extracted: Extracted, structure: BuildStructure) = EvaluateConfig(false, restrictions(extracted, structure), progress = defaultProgress) - @deprecated("Use other extractedConfig", "0.13.2") + @deprecated("Use other extractedTaskConfig", "0.13.2") def extractedConfig(extracted: Extracted, structure: BuildStructure): EvaluateConfig = { val workers = restrictions(extracted, structure) val canCancel = cancelable(extracted, structure) EvaluateConfig(cancelable = canCancel, restrictions = workers, progress = defaultProgress) } + @deprecated("Use other extractedTaskConfig", "0.13.5") def extractedConfig(extracted: Extracted, structure: BuildStructure, state: State): EvaluateConfig = { val workers = restrictions(extracted, structure) @@ -131,6 +145,13 @@ object EvaluateTask val progress = executeProgress(extracted, structure, state) EvaluateConfig(cancelable = canCancel, restrictions = workers, progress = progress) } + def extractedTaskConfig(extracted: Extracted, structure: BuildStructure, state: State): EvaluateTaskConfig = + { + val rs = restrictions(extracted, structure) + val canceller = cancelHandler(extracted, structure, state) + val progress = executeProgress(extracted, structure, state) + EvaluateTaskConfig(rs, false, progress, canceller) + } def defaultRestrictions(maxWorkers: Int) = Tags.limitAll(maxWorkers) :: Nil def defaultRestrictions(extracted: Extracted, structure: BuildStructure): Seq[Tags.Rule] = @@ -150,11 +171,13 @@ object EvaluateTask 1 def cancelable(extracted: Extracted, structure: BuildStructure): Boolean = getSetting(Keys.cancelable, false, extracted, structure) + def cancelHandler(extracted: Extracted, structure: BuildStructure, state: State): TaskEvaluationCancelHandler = + getSetting(Keys.taskCancelHandler, {(_: State) => TaskEvaluationCancelHandler.Null}, extracted, structure)(state) 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) - maker(state).progress + val maker: State => Keys.TaskProgress = getSetting(Keys.executeProgress, const(new Keys.TaskProgress(defaultProgress)), extracted, structure) + maker(state).progress } def getSetting[T](key: SettingKey[T], default: T, extracted: Extracted, structure: BuildStructure): T = @@ -185,16 +208,20 @@ 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, extractedConfig(Project.extract(state), structure)) + 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. */ + @deprecated("Use EvalauteTaskConfig option instead.", "0.13.5") def apply[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef, config: EvaluateConfig): Option[(State, Result[T])] = + apply(structure, taskKey, state, ref, EvaluateTaskConfig(config)) + 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) } + } def logIncResult(result: Result[_], state: State, streams: Streams) = result match { case Inc(i) => logIncomplete(i, state, streams); case _ => () } def logIncomplete(result: Incomplete, state: State, streams: Streams) { diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 53ccdfc97..2f2f9b09e 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -346,6 +346,7 @@ object Keys // wrapper to work around SI-2915 private[sbt] final class TaskProgress(val progress: ExecuteProgress[Task]) private[sbt] val executeProgress = SettingKey[State => TaskProgress]("executeProgress", "Experimental task execution listener.", DTask) + private[sbt] val taskCancelHandler = SettingKey[State => TaskEvaluationCancelHandler]("taskCancelHandler", "Experimental task cancellation handler.", DTask) // Experimental in sbt 0.13.2 to enable grabing semantic compile failures. private[sbt] val compilerReporter = TaskKey[Option[xsbti.Reporter]]("compilerReporter", "Experimental hook to listen (or send) compilation failure messages.", DTask) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index faea25e13..de8b6d311 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -498,13 +498,25 @@ object Project extends ProjectExtra @deprecated("This method does not apply state changes requested during task execution. Use 'runTask' instead, which does.", "0.11.1") def evaluateTask[T](taskKey: ScopedKey[Task[T]], state: State, checkCycles: Boolean = false, maxWorkers: Int = EvaluateTask.SystemProcessors): Option[Result[T]] = runTask(taskKey, state, EvaluateConfig(true, EvaluateTask.defaultRestrictions(maxWorkers), checkCycles)).map(_._2) + def runTask[T](taskKey: ScopedKey[Task[T]], state: State, checkCycles: Boolean = false): Option[(State, Result[T])] = - runTask(taskKey, state, EvaluateConfig(true, EvaluateTask.restrictions(state), checkCycles)) + { + val extracted = Project.extract(state) + val ch = EvaluateTask.cancelHandler(extracted, extracted.structure, state) + val p = EvaluateTask.executeProgress(extracted, extracted.structure, state) + val r = EvaluateTask.restrictions(state) + runTask(taskKey, state, EvaluateTaskConfig(r, checkCycles, p, ch)) + } + @deprecated("Use EvalauteTaskConfig option instead.", "0.13.5") def runTask[T](taskKey: ScopedKey[Task[T]], state: State, config: EvaluateConfig): Option[(State, Result[T])] = { val extracted = Project.extract(state) EvaluateTask(extracted.structure, taskKey, state, extracted.currentRef, config) } + def runTask[T](taskKey: ScopedKey[Task[T]], state: State, config: EvaluateTaskConfig): Option[(State, Result[T])] = { + val extracted = Project.extract(state) + EvaluateTask(extracted.structure, taskKey, state, extracted.currentRef, config) + } implicit def projectToRef(p: Project): ProjectReference = LocalProject(p.id) diff --git a/sbt/src/sbt-test/actions/task-cancel/build.sbt b/sbt/src/sbt-test/actions/task-cancel/build.sbt new file mode 100644 index 000000000..b621673ef --- /dev/null +++ b/sbt/src/sbt-test/actions/task-cancel/build.sbt @@ -0,0 +1,8 @@ +import sbt.ExposeYourself._ + +taskCancelHandler := { (state: State) => + new TaskEvaluationCancelHandler { + override def start(canceller: TaskCancel): Unit = canceller.cancel() + override def finish(): Unit = () + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/actions/task-cancel/project/Build.scala b/sbt/src/sbt-test/actions/task-cancel/project/Build.scala new file mode 100644 index 000000000..cd7055cf0 --- /dev/null +++ b/sbt/src/sbt-test/actions/task-cancel/project/Build.scala @@ -0,0 +1,5 @@ +package sbt // this API is private[sbt], so only exposed for trusted clients and folks who like breaking. + +object ExposeYourself { + val taskCancelHandler = sbt.Keys.taskCancelHandler +} \ No newline at end of file diff --git a/sbt/src/sbt-test/actions/task-cancel/src/main/scala/test.scala b/sbt/src/sbt-test/actions/task-cancel/src/main/scala/test.scala new file mode 100644 index 000000000..c3853a565 --- /dev/null +++ b/sbt/src/sbt-test/actions/task-cancel/src/main/scala/test.scala @@ -0,0 +1,5 @@ +import scala + +object Foo { + val x = "this should be long to compile or the test may fail." +} \ No newline at end of file diff --git a/sbt/src/sbt-test/actions/task-cancel/test b/sbt/src/sbt-test/actions/task-cancel/test new file mode 100644 index 000000000..87501cf8a --- /dev/null +++ b/sbt/src/sbt-test/actions/task-cancel/test @@ -0,0 +1,3 @@ +# All tasks should fail. +-> compile +-> test \ No newline at end of file From 8f1ef5395dc897e0fcff3778e3a6caf90628d2ba Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 8 Apr 2014 11:09:23 -0400 Subject: [PATCH 15/38] Improve scaladoc comments for the task cancellation api. --- main/src/main/scala/sbt/EvaluateTask.scala | 24 +++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 4931ef077..b58243b23 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -19,20 +19,34 @@ final case class EvaluateConfig(cancelable: Boolean, restrictions: Seq[Tags.Rule -/** Represents something that can be cancelled. */ +/** Represents something that can be cancelled. + * For example, this is implemented by the TaskEngine; invoking `cancel()` allows you + * to cancel the current task exeuction. A `TaskCancel` is passed to the + * [[TaskEvalautionCancelHandler]] which is responsible for calling `cancel()` when + * appropriate. + */ trait TaskCancel { /** cancels whatever this points at. */ def cancel(): Unit } -/** A handler for registering/remmoving listeners that allow you to cancel tasks. */ +/** + * A handler for registering/remmoving listeners that allow you to cancel task + * execution. + * + * Implementations of this trait determine what will trigger `cancel()` for + * the task engine, providing in the `start` method. + */ trait TaskEvaluationCancelHandler { - /* Evaluation is starting, here's a mechanism to cancel things. */ + /** Called when task evaluation starts. + * + * @param canceller An object that can cancel the current task evaluation session. + */ def start(canceller: TaskCancel): Unit - /* Task Evaluation is complete, whether success or failure. */ + /** Called when task evaluation completes, either in success or failure. */ def finish(): Unit } object TaskEvaluationCancelHandler { - /** An empty handler that does nothing. */ + /** An empty handler that does not cancel tasks. */ object Null extends TaskEvaluationCancelHandler { def start(canceller: TaskCancel): Unit = () def finish(): Unit = () From f42dee8090848919582aad51f8b366472b83aabb Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 8 Apr 2014 12:08:30 -0400 Subject: [PATCH 16/38] Naming fixes to TaskCancellationStrategy to be clearer. --- main/src/main/scala/sbt/Defaults.scala | 6 ++-- main/src/main/scala/sbt/EvaluateTask.scala | 40 ++++++++++++---------- main/src/main/scala/sbt/Keys.scala | 2 +- main/src/main/scala/sbt/Project.scala | 2 +- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index ba14788e9..d08c1e95b 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -120,9 +120,9 @@ object Defaults extends BuildCommon trapExit :== true, connectInput :== false, cancelable :== false, - taskCancelHandler := { state: State => - if(cancelable.value) TaskEvaluationCancelHandler.Signal - else TaskEvaluationCancelHandler.Null + taskCancelStrategy := { state: State => + if(cancelable.value) TaskCancellationStrategy.Signal + else TaskCancellationStrategy.Null }, envVars :== Map.empty, sbtVersion := appConfiguration.value.provider.id.version, diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index b58243b23..5b7883f45 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -19,7 +19,8 @@ final case class EvaluateConfig(cancelable: Boolean, restrictions: Seq[Tags.Rule -/** Represents something that can be cancelled. +/** An API that allows you to cancel executing tasks upon some signal. + * * For example, this is implemented by the TaskEngine; invoking `cancel()` allows you * to cancel the current task exeuction. A `TaskCancel` is passed to the * [[TaskEvalautionCancelHandler]] which is responsible for calling `cancel()` when @@ -30,13 +31,14 @@ trait TaskCancel { def cancel(): Unit } /** - * A handler for registering/remmoving listeners that allow you to cancel task - * execution. + * A handler for how to handle task cancellation. * * Implementations of this trait determine what will trigger `cancel()` for * the task engine, providing in the `start` method. + * + * All methods on this API are expected to be called from the same thread. */ -trait TaskEvaluationCancelHandler { +trait TaskCancellationStrategy { /** Called when task evaluation starts. * * @param canceller An object that can cancel the current task evaluation session. @@ -45,14 +47,14 @@ trait TaskEvaluationCancelHandler { /** Called when task evaluation completes, either in success or failure. */ def finish(): Unit } -object TaskEvaluationCancelHandler { +object TaskCancellationStrategy { /** An empty handler that does not cancel tasks. */ - object Null extends TaskEvaluationCancelHandler { + object Null extends TaskCancellationStrategy { def start(canceller: TaskCancel): Unit = () def finish(): Unit = () } /** Cancel handler which registers for SIGINT and cancels tasks when it is received. */ - object Signal extends TaskEvaluationCancelHandler { + object Signal extends TaskCancellationStrategy { private var registration: Option[Signals.Registration] = None def start(canceller: TaskCancel): Unit = { registration = Some(Signals.register(() => canceller.cancel())) @@ -78,7 +80,7 @@ sealed trait EvaluateTaskConfig { def restrictions: Seq[Tags.Rule] def checkCycles: Boolean def progressReporter: ExecuteProgress[Task] - def cancelHandler: TaskEvaluationCancelHandler + def cancelStrategy: TaskCancellationStrategy } final object EvaluateTaskConfig { /** Pulls in the old configuration format. */ @@ -87,9 +89,9 @@ final object EvaluateTaskConfig { def restrictions: Seq[Tags.Rule] = old.restrictions def checkCycles: Boolean = old.checkCycles def progressReporter: ExecuteProgress[Task] = old.progress - def cancelHandler: TaskEvaluationCancelHandler = - if(old.cancelable) TaskEvaluationCancelHandler.Signal - else TaskEvaluationCancelHandler.Null + def cancelStrategy: TaskCancellationStrategy = + if(old.cancelable) TaskCancellationStrategy.Signal + else TaskCancellationStrategy.Null } AdaptedTaskConfig } @@ -97,12 +99,12 @@ final object EvaluateTaskConfig { def apply(restrictions: Seq[Tags.Rule], checkCycles: Boolean, progressReporter: ExecuteProgress[Task], - cancelHandler: TaskEvaluationCancelHandler): EvaluateTaskConfig = { + cancelStrategy: TaskCancellationStrategy): EvaluateTaskConfig = { object SimpleEvaluateTaskConfig extends EvaluateTaskConfig { def restrictions = restrictions def checkCycles = checkCycles def progressReporter = progressReporter - def cancelHandler = cancelHandler + def cancelStrategy = cancelStrategy } SimpleEvaluateTaskConfig } @@ -162,7 +164,7 @@ object EvaluateTask def extractedTaskConfig(extracted: Extracted, structure: BuildStructure, state: State): EvaluateTaskConfig = { val rs = restrictions(extracted, structure) - val canceller = cancelHandler(extracted, structure, state) + val canceller = cancelStrategy(extracted, structure, state) val progress = executeProgress(extracted, structure, state) EvaluateTaskConfig(rs, false, progress, canceller) } @@ -185,8 +187,8 @@ object EvaluateTask 1 def cancelable(extracted: Extracted, structure: BuildStructure): Boolean = getSetting(Keys.cancelable, false, extracted, structure) - def cancelHandler(extracted: Extracted, structure: BuildStructure, state: State): TaskEvaluationCancelHandler = - getSetting(Keys.taskCancelHandler, {(_: State) => TaskEvaluationCancelHandler.Null}, extracted, structure)(state) + 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] = { import Types.const @@ -294,7 +296,7 @@ object EvaluateTask import ConcurrentRestrictions.{completionService, TagMap, Tag, tagged, tagsKey} val log = state.log - log.debug("Running task... Cancel: " + config.cancelHandler + ", check cycles: " + config.checkCycles) + log.debug("Running task... Cancel: " + config.cancelStrategy + ", check cycles: " + config.checkCycles) val tags = tagged[Task[_]](_.info get tagsKey getOrElse Map.empty, Tags.predicate(config.restrictions)) val (service, shutdown) = completionService[Task[_], Completed](tags, (s: String) => log.warn(s)) @@ -324,9 +326,9 @@ object EvaluateTask } } // Register with our cancel handler we're about to start. - config.cancelHandler.start(taskCancel) + config.cancelStrategy.start(taskCancel) try run() - finally config.cancelHandler.finish() + finally config.cancelStrategy.finish() } private[this] def storeValuesForPrevious(results: RMap[Task, Result], state: State, streams: Streams): Unit = diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 2f2f9b09e..16294ef80 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -346,7 +346,7 @@ object Keys // wrapper to work around SI-2915 private[sbt] final class TaskProgress(val progress: ExecuteProgress[Task]) private[sbt] val executeProgress = SettingKey[State => TaskProgress]("executeProgress", "Experimental task execution listener.", DTask) - private[sbt] val taskCancelHandler = SettingKey[State => TaskEvaluationCancelHandler]("taskCancelHandler", "Experimental task cancellation handler.", DTask) + private[sbt] val taskCancelStrategy = SettingKey[State => TaskCancellationStrategy]("taskCancelStrategy", "Experimental task cancellation handler.", DTask) // Experimental in sbt 0.13.2 to enable grabing semantic compile failures. private[sbt] val compilerReporter = TaskKey[Option[xsbti.Reporter]]("compilerReporter", "Experimental hook to listen (or send) compilation failure messages.", DTask) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index de8b6d311..06420cf84 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -502,7 +502,7 @@ object Project extends ProjectExtra def runTask[T](taskKey: ScopedKey[Task[T]], state: State, checkCycles: Boolean = false): Option[(State, Result[T])] = { val extracted = Project.extract(state) - val ch = EvaluateTask.cancelHandler(extracted, extracted.structure, state) + val ch = EvaluateTask.cancelStrategy(extracted, extracted.structure, state) val p = EvaluateTask.executeProgress(extracted, extracted.structure, state) val r = EvaluateTask.restrictions(state) runTask(taskKey, state, EvaluateTaskConfig(r, checkCycles, p, ch)) From 062cd1c77634e8faf12c6eb4707a43b939c4f40e Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 8 Apr 2014 23:18:48 +0200 Subject: [PATCH 17/38] Add link to corresponding issue in Scala issue tracker --- compile/interface/src/main/scala/xsbt/Dependency.scala | 1 + compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala | 1 + 2 files changed, 2 insertions(+) diff --git a/compile/interface/src/main/scala/xsbt/Dependency.scala b/compile/interface/src/main/scala/xsbt/Dependency.scala index 77dd9355f..1edae4ac0 100644 --- a/compile/interface/src/main/scala/xsbt/Dependency.scala +++ b/compile/interface/src/main/scala/xsbt/Dependency.scala @@ -150,6 +150,7 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile * Some macros appear to contain themselves as original tree * In this case, we don't need to inspect the original tree because * we already inspected its expansion, which is equal. + * See https://issues.scala-lang.org/browse/SI-8486 */ case MacroExpansionOf(original) if original != tree => this.traverse(original) diff --git a/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala b/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala index 1bcaf125f..ba8e87a1e 100644 --- a/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala +++ b/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala @@ -59,6 +59,7 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext // Some macros seem to have themselves registered as original tree. // In this case, we only need to handle the children of the original tree, // because we already handled the expanded tree. + // See https://issues.scala-lang.org/browse/SI-8486 if(original == node) original.children.foreach(handleTreeNode) else original.foreach(handleTreeNode) } From 3673f531196ef31b4b5b7ba3d3f78712482df24b Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Tue, 8 Apr 2014 23:26:39 +0200 Subject: [PATCH 18/38] Clarify the role and uses cases of Analysis.compilations. It's mainly debugging tool. I added documentation describing the intent of Analysis.compilations field. --- compile/inc/src/main/scala/sbt/inc/Analysis.scala | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/compile/inc/src/main/scala/sbt/inc/Analysis.scala b/compile/inc/src/main/scala/sbt/inc/Analysis.scala index aaa63918d..c2657aa0d 100644 --- a/compile/inc/src/main/scala/sbt/inc/Analysis.scala +++ b/compile/inc/src/main/scala/sbt/inc/Analysis.scala @@ -30,7 +30,18 @@ trait Analysis /** Mappings between sources, classes, and binaries. */ val relations: Relations val infos: SourceInfos - /** Information about compiler runs accumulated since `clean` command has been run. */ + /** + * Information about compiler runs accumulated since `clean` command has been run. + * + * The main use-case for using `compilations` field is to determine how + * many iterations it took to compilen give code. The `Compilation` object + * are also stored in `Source` objects so there's an indirect way to recover + * information about files being recompiled in every iteration. + * + * The incremental compilation algorithm doesn't use information stored in + * `compilations`. It's safe to prune contents of that field without breaking + * internal consistency of the entire Analysis object. + */ val compilations: Compilations /** Concatenates Analysis objects naively, i.e., doesn't internalize external deps on added files. See `Analysis.merge`. */ From 3890c98115f88d12f0665bb1c29e3fb224a0c364 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 8 Apr 2014 20:59:50 -0400 Subject: [PATCH 19/38] Remove mutability from the task cancel api. * Rename TaskCancel to RunningTaskEngine for clarity * Explicitly pass a state value in TaskCancellationStrategy * Update hooks to be immutable/safe. --- main/src/main/scala/sbt/EvaluateTask.scala | 44 +++++++++---------- .../sbt-test/actions/task-cancel/build.sbt | 5 ++- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 5b7883f45..60e0a44ad 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -26,12 +26,12 @@ final case class EvaluateConfig(cancelable: Boolean, restrictions: Seq[Tags.Rule * [[TaskEvalautionCancelHandler]] which is responsible for calling `cancel()` when * appropriate. */ -trait TaskCancel { - /** cancels whatever this points at. */ - def cancel(): Unit +trait RunningTaskEngine { + /** Attempts to kill and shutdown the running task engine.*/ + def cancelAndShutdown(): Unit } /** - * A handler for how to handle task cancellation. + * A startegy for being able to cancle tasks. * * Implementations of this trait determine what will trigger `cancel()` for * the task engine, providing in the `start` method. @@ -39,33 +39,32 @@ trait TaskCancel { * All methods on this API are expected to be called from the same thread. */ trait TaskCancellationStrategy { + /** The state used by this task. */ + type State /** Called when task evaluation starts. * * @param canceller An object that can cancel the current task evaluation session. + * @return Whatever state you need to cleanup in your finish method. */ - def start(canceller: TaskCancel): Unit + def onTaskEngineStart(canceller: RunningTaskEngine): State /** Called when task evaluation completes, either in success or failure. */ - def finish(): Unit + def onTaskEngineFinish(state: State): Unit } object TaskCancellationStrategy { /** An empty handler that does not cancel tasks. */ object Null extends TaskCancellationStrategy { - def start(canceller: TaskCancel): Unit = () - def finish(): Unit = () + type State = Unit + def onTaskEngineStart(canceller: RunningTaskEngine): Unit = () + def onTaskEngineFinish(state: Unit): Unit = () } /** Cancel handler which registers for SIGINT and cancels tasks when it is received. */ object Signal extends TaskCancellationStrategy { - private var registration: Option[Signals.Registration] = None - def start(canceller: TaskCancel): Unit = { - registration = Some(Signals.register(() => canceller.cancel())) + type State = Signals.Registration + def onTaskEngineStart(canceller: RunningTaskEngine): Signals.Registration = { + Signals.register(() => canceller.cancelAndShutdown()) } - def finish(): Unit = - registration match { - case Some(value) => - value.remove() - registration = None - case None => - } + def onTaskEngineFinish(registration: Signals.Registration): Unit = + registration.remove() } } @@ -318,17 +317,18 @@ object EvaluateTask logIncResult(replaced, state, streams) (newState, replaced) } - object taskCancel extends TaskCancel { - def cancel(): Unit = { + object runningEngine extends RunningTaskEngine { + def cancelAndShutdown(): Unit = { println("") log.warn("Canceling execution...") shutdown() } } // Register with our cancel handler we're about to start. - config.cancelStrategy.start(taskCancel) + val strat = config.cancelStrategy + val cancelState = strat.onTaskEngineStart(runningEngine) try run() - finally config.cancelStrategy.finish() + finally strat.onTaskEngineFinish(cancelState) } private[this] def storeValuesForPrevious(results: RMap[Task, Result], state: State, streams: Streams): Unit = diff --git a/sbt/src/sbt-test/actions/task-cancel/build.sbt b/sbt/src/sbt-test/actions/task-cancel/build.sbt index b621673ef..149e93163 100644 --- a/sbt/src/sbt-test/actions/task-cancel/build.sbt +++ b/sbt/src/sbt-test/actions/task-cancel/build.sbt @@ -2,7 +2,8 @@ import sbt.ExposeYourself._ taskCancelHandler := { (state: State) => new TaskEvaluationCancelHandler { - override def start(canceller: TaskCancel): Unit = canceller.cancel() - override def finish(): Unit = () + type State = Unit + override def onTaskEngineStart(canceller: TaskCancel): Unit = canceller.cancel() + override def finish(result: Unit): Unit = () } } \ No newline at end of file From 195129a3e71b9784a1ec00b8b06babc133a2fddc Mon Sep 17 00:00:00 2001 From: David Barri Date: Wed, 9 Apr 2014 19:14:29 +1000 Subject: [PATCH 20/38] Added TestResultLogger for customising test feedback. 1) When `test` is run and there are no tests available, omit logging output. Especially useful for aggregate modules. `test-only` et al unaffected. (#1185) 2) Added a new setting `testResultLogger` to allow customisation of logging of test results. --- .../src/main/scala/sbt/TestResultLogger.scala | 159 ++++++++++++++++++ main/actions/src/main/scala/sbt/Tests.scala | 74 +------- main/src/main/scala/sbt/Defaults.scala | 16 +- main/src/main/scala/sbt/Keys.scala | 1 + src/sphinx/Community/Changes.rst | 3 + 5 files changed, 174 insertions(+), 79 deletions(-) create mode 100644 main/actions/src/main/scala/sbt/TestResultLogger.scala diff --git a/main/actions/src/main/scala/sbt/TestResultLogger.scala b/main/actions/src/main/scala/sbt/TestResultLogger.scala new file mode 100644 index 000000000..c211035d2 --- /dev/null +++ b/main/actions/src/main/scala/sbt/TestResultLogger.scala @@ -0,0 +1,159 @@ +package sbt + +import sbt.Tests.{Output, Summary} + +/** + * Logs information about tests after they finish. + * + * Log output can be customised by providing a specialised instance of this + * trait via the `testTestResultLogger` setting. + * + * @since 0.13.5 + */ +trait TestResultLogger { + + /** + * Perform logging. + * + * @param log The target logger to write output to. + * @param results The test results about which to log. + * @param taskName The task about which we are logging. Eg. "my-module-b/test:test" + */ + 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) = + 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) = + TestResultLogger.choose(f, otherwise, this) +} + +object TestResultLogger { + + /** A `TestResultLogger` that does nothing. */ + val Null = const(_ => ()) + + /** SBT's default `TestResultLogger`. Use `copy()` to change selective portions. */ + val Default = Defaults.Main() + + /** Twist on the default which is completely silent when the subject module doesn't contain any tests. */ + def SilentWhenNoTests = silenceWhenNoTests(Default) + + /** Creates a `TestResultLogger` using a given function. */ + def apply(f: (Logger, Output, String) => Unit): TestResultLogger = + new TestResultLogger { + override def run(log: Logger, results: Output, taskName: String) = + f(log, results, taskName) + } + + /** Creates a `TestResultLogger` that ignores its input and always performs the same logging. */ + def const(f: Logger => Unit) = apply((l,_,_) => f(l)) + + /** + * Selects a `TestResultLogger` based on a given predicate. + * + * @param t The `TestResultLogger` to choose if the predicate passes. + * @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)) + + /** Transforms the input to be completely silent when the subject module doesn't contain any tests. */ + def silenceWhenNoTests(d: Defaults.Main) = + d.copy( + printStandard = d.printStandard.unless((results, _) => results.events.isEmpty), + printNoTests = Null + ) + + object Defaults { + + /** SBT's default `TestResultLogger`. Use `copy()` to change selective portions. */ + case class Main( + printStandard_? : Output => Boolean = Defaults.printStandard_?, + printSummary : TestResultLogger = Defaults.printSummary, + printStandard : TestResultLogger = Defaults.printStandard, + printFailures : TestResultLogger = Defaults.printFailures, + printNoTests : TestResultLogger = Defaults.printNoTests + ) extends TestResultLogger { + + override def run(log: Logger, results: Output, taskName: String): Unit = { + def run(r: TestResultLogger): Unit = r.run(log, results, taskName) + + run(printSummary) + + if (printStandard_?(results)) + run(printStandard) + + if (results.events.isEmpty) + run(printNoTests) + else + run(printFailures) + + results.overall match { + case TestResult.Error | TestResult.Failed => throw new TestsFailedException + case TestResult.Passed => + } + } + } + + val printSummary = TestResultLogger((log, results, _) => { + val multipleFrameworks = results.summaries.size > 1 + for (Summary(name, message) <- results.summaries) + if(message.isEmpty) + log.debug("Summary for " + name + " not available.") + else { + if(multipleFrameworks) log.info(name) + log.info(message) + } + }) + + val printStandard_? : Output => Boolean = + results => + // Print the standard one-liner statistic if no framework summary is defined, or when > 1 framework is in used. + results.summaries.size > 1 || results.summaries.headOption.forall(_.summaryText.size == 0) + + val printStandard = TestResultLogger((log, results, _) => { + val (skippedCount, errorsCount, passedCount, failuresCount, ignoredCount, canceledCount, pendingCount) = + results.events.foldLeft((0, 0, 0, 0, 0, 0, 0)) { case ((skippedAcc, errorAcc, passedAcc, failureAcc, ignoredAcc, canceledAcc, pendingAcc), (name, testEvent)) => + (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 extra = otherCounts.filter(_._2 > 0).map{case(label,count) => s", $label $count" } + + val postfix = base + extra.mkString + results.overall match { + case TestResult.Error => log.error("Error: " + postfix) + case TestResult.Passed => log.info("Passed: " + postfix) + case TestResult.Failed => log.error("Failed: " + postfix) + } + }) + + val printFailures = TestResultLogger((log, results, _) => { + def select(resultTpe: TestResult.Value) = results.events collect { + case (name, tpe) if tpe.result == resultTpe => + scala.reflect.NameTransformer.decode(name) + } + + def show(label: String, level: Level.Value, tests: Iterable[String]): Unit = + if (!tests.isEmpty) { + log.log(level, label) + log.log(level, tests.mkString("\t", "\n\t", "")) + } + + show("Passed tests:", Level.Debug, select(TestResult.Passed)) + show("Failed tests:", Level.Error, select(TestResult.Failed)) + show("Error during tests:", Level.Error, select(TestResult.Error)) + }) + + val printNoTests = TestResultLogger((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 fb7f2cdcb..c75521ced 100644 --- a/main/actions/src/main/scala/sbt/Tests.scala +++ b/main/actions/src/main/scala/sbt/Tests.scala @@ -264,78 +264,10 @@ object Tests (tests, mains.toSet) } + @deprecated("Tests.showResults() has been superseded with TestResultLogger and setting 'testResultLogger'.", "0.13.5") def showResults(log: Logger, results: Output, noTestsMessage: =>String): Unit = - { - val multipleFrameworks = results.summaries.size > 1 - def printSummary(name: String, message: String) - { - if(message.isEmpty) - log.debug("Summary for " + name + " not available.") - else - { - if(multipleFrameworks) log.info(name) - log.info(message) - } - } - - for (Summary(name, messages) <- results.summaries) - printSummary(name, messages) - val noSummary = results.summaries.headOption.forall(_.summaryText.size == 0) - val printStandard = multipleFrameworks || noSummary - // Print the standard one-liner statistic if no framework summary is defined, or when > 1 framework is in used. - if (printStandard) - { - val (skippedCount, errorsCount, passedCount, failuresCount, ignoredCount, canceledCount, pendingCount) = - results.events.foldLeft((0, 0, 0, 0, 0, 0, 0)) { case ((skippedAcc, errorAcc, passedAcc, failureAcc, ignoredAcc, canceledAcc, pendingAcc), (name, testEvent)) => - (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 extra = otherCounts.filter(_._2 > 0).map{case(label,count) => s", $label $count" } - - val postfix = base + extra.mkString - results.overall match { - case TestResult.Error => log.error("Error: " + postfix) - case TestResult.Passed => log.info("Passed: " + postfix) - case TestResult.Failed => log.error("Failed: " + postfix) - } - } - // Let's always print out Failed tests for now - if (results.events.isEmpty) - log.info(noTestsMessage) - else { - import TestResult.{Error, Failed, Passed} - import scala.reflect.NameTransformer.decode - - def select(resultTpe: TestResult.Value) = results.events collect { - case (name, tpe) if tpe.result == resultTpe => - decode(name) - } - - val failures = select(Failed) - val errors = select(Error) - val passed = select(Passed) - - def show(label: String, level: Level.Value, tests: Iterable[String]): Unit = - if(!tests.isEmpty) - { - log.log(level, label) - log.log(level, tests.mkString("\t", "\n\t", "")) - } - - show("Passed tests:", Level.Debug, passed ) - show("Failed tests:", Level.Error, failures) - show("Error during tests:", Level.Error, errors) - } - - results.overall match { - case TestResult.Error | TestResult.Failed => throw new TestsFailedException - case TestResult.Passed => - } - } + TestResultLogger.Default.copy(printNoTests = TestResultLogger.const(_ info noTestsMessage)) + .run(log, results, "") } final class TestsFailedException extends RuntimeException("Tests unsuccessful") with FeedbackProvidedException diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index d08c1e95b..96390d869 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -371,6 +371,7 @@ object Defaults extends BuildCommon }, testListeners :== Nil, testOptions :== Nil, + testResultLogger :== TestResultLogger.Default, testFilter in testOnly :== (selectedFilter _) )) lazy val testTasks: Seq[Setting[_]] = testTaskOptions(test) ++ testTaskOptions(testOnly) ++ testTaskOptions(testQuick) ++ testDefaults ++ Seq( @@ -380,16 +381,15 @@ object Defaults extends BuildCommon definedTestNames <<= definedTests map ( _.map(_.name).distinct) storeAs definedTestNames triggeredBy compile, testFilter in testQuick <<= testQuickFilter, executeTests <<= (streams in test, loadedTestFrameworks, testLoader, testGrouping in test, testExecution in test, fullClasspath in test, javaHome in test, testForkedParallel) flatMap allTestGroupsTask, + testResultLogger in (Test, test) :== TestResultLogger.SilentWhenNoTests, // https://github.com/sbt/sbt/issues/1185 test := { - implicit val display = Project.showContextKey(state.value) - Tests.showResults(streams.value.log, executeTests.value, noTestsMessage(resolvedScoped.value)) + val trl = (testResultLogger in (Test, test)).value + val taskName = Project.showContextKey(state.value)(resolvedScoped.value) + trl.run(streams.value.log, executeTests.value, taskName) }, testOnly <<= inputTests(testOnly), testQuick <<= inputTests(testQuick) ) - private[this] def noTestsMessage(scoped: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): String = - "No tests to run for " + display(scoped) - lazy val TaskGlobal: Scope = ThisScope.copy(task = Global) lazy val ConfigGlobal: Scope = ThisScope.copy(config = Global) def testTaskOptions(key: Scoped): Seq[Setting[_]] = inTask(key)( Seq( @@ -488,9 +488,9 @@ object Defaults extends BuildCommon val modifiedOpts = Tests.Filters(filter(selected)) +: Tests.Argument(frameworkOptions : _*) +: config.options val newConfig = config.copy(options = modifiedOpts) val output = allTestGroupsTask(s, loadedTestFrameworks.value, testLoader.value, testGrouping.value, newConfig, fullClasspath.value, javaHome.value, testForkedParallel.value) - val processed = - for(out <- output) yield - Tests.showResults(s.log, out, noTestsMessage(resolvedScoped.value)) + val taskName = display(resolvedScoped.value) + val trl = testResultLogger.value + val processed = output.map(out => trl.run(s.log, out, taskName)) Def.value(processed) } } diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 16294ef80..3ea64983b 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -197,6 +197,7 @@ object Keys val testForkedParallel = SettingKey[Boolean]("test-forked-parallel", "Whether forked tests should be executed in parallel", CTask) val testExecution = TaskKey[Tests.Execution]("test-execution", "Settings controlling test execution", DTask) val testFilter = TaskKey[Seq[String] => Seq[String => Boolean]]("test-filter", "Filter controlling whether the test is executed", DTask) + val testResultLogger = SettingKey[TestResultLogger]("test-result-logger", "Logs results after a test task completes.", DTask) val testGrouping = TaskKey[Seq[Tests.Group]]("test-grouping", "Collects discovered tests into groups. Whether to fork and the options for forking are configurable on a per-group basis.", BMinusTask) val isModule = AttributeKey[Boolean]("is-module", "True if the target is a module.", DSetting) diff --git a/src/sphinx/Community/Changes.rst b/src/sphinx/Community/Changes.rst index 600a28f68..1c63180e5 100644 --- a/src/sphinx/Community/Changes.rst +++ b/src/sphinx/Community/Changes.rst @@ -5,6 +5,9 @@ Changes 0.13.2 to 0.13.5 ~~~~~~~~~~~~~~~~ - The Scala version for sbt and sbt plugins is now 2.10.4. This is a compatible version bump. +- Added a new setting ``testResultLogger`` to allow customisation of logging of test results. (gh-1225) +- When ``test`` is run and there are no tests available, omit logging output. + Especially useful for aggregate modules. ``test-only`` et al unaffected. (gh-1185) 0.13.1 to 0.13.2 ~~~~~~~~~~~~~~~~ From 43c25beeed80cc65d769d5cac0ace8df85a8ebf1 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 9 Apr 2014 10:11:21 -0400 Subject: [PATCH 21/38] Fix infinite loop of death. --- main/src/main/scala/sbt/EvaluateTask.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 60e0a44ad..c7df90b96 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -99,11 +99,15 @@ final object EvaluateTaskConfig { checkCycles: Boolean, progressReporter: ExecuteProgress[Task], cancelStrategy: TaskCancellationStrategy): EvaluateTaskConfig = { + val r = restrictions + val check = checkCycles + val cs = cancelStrategy + val pr = progressReporter object SimpleEvaluateTaskConfig extends EvaluateTaskConfig { - def restrictions = restrictions - def checkCycles = checkCycles - def progressReporter = progressReporter - def cancelStrategy = cancelStrategy + def restrictions = r + def checkCycles = check + def progressReporter = pr + def cancelStrategy = cs } SimpleEvaluateTaskConfig } From 2ffb85ee7ce2f892165b4c648ae14aab12bfae3d Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Tue, 4 Mar 2014 12:13:22 -0600 Subject: [PATCH 22/38] Relativizing CONTRIBUTING.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 920f80a70..385099c0f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [Google Code]: http://code.google.com/p/simple-build-tool -[CONTRIBUTING]: https://github.com/sbt/sbt/blob/0.13/CONTRIBUTING.md +[CONTRIBUTING]: CONTRIBUTING.md [Setup]: http://www.scala-sbt.org/release/docs/Getting-Started/Setup [FAQ]: http://www.scala-sbt.org/release/docs/faq @@ -10,4 +10,4 @@ This is the 0.13.x series of sbt. * [Setup]: Describes getting started with the latest binary release. * See [CONTRIBUTING] for how to build from source, open an issue, fix or add documentation, or submit a pull request. * [FAQ]: Explains how to get help and more. - * [Google Code]: hosts sbt 0.7.7 and earlier versions \ No newline at end of file + * [Google Code]: hosts sbt 0.7.7 and earlier versions From f1d61be12524cd562ceb7e3e417f46165c405dc8 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Thu, 10 Apr 2014 03:16:41 -0500 Subject: [PATCH 23/38] `DefaultOptions` for resolvers should not resets existing values. `DefaultOptions.addResolvers` and `DefaultOptions.addPluginResolvers` should not reset the existing value while adding the new ones. The names are prefixed with _add_ afterall. --- main/src/main/scala/sbt/Opts.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/Opts.scala b/main/src/main/scala/sbt/Opts.scala index cb78f5adc..1fddce915 100644 --- a/main/src/main/scala/sbt/Opts.scala +++ b/main/src/main/scala/sbt/Opts.scala @@ -52,8 +52,8 @@ object DefaultOptions { def pluginResolvers(plugin: Boolean, snapshot: Boolean): Seq[Resolver] = { if (plugin && snapshot) Seq(Classpaths.typesafeSnapshots, Classpaths.sbtPluginSnapshots) else Nil } - def addResolvers: Setting[_] = Keys.resolvers <<= Keys.isSnapshot apply resolvers - def addPluginResolvers: Setting[_] = Keys.resolvers <<= (Keys.sbtPlugin, Keys.isSnapshot) apply pluginResolvers + def addResolvers: Setting[_] = Keys.resolvers <++= Keys.isSnapshot apply resolvers + def addPluginResolvers: Setting[_] = Keys.resolvers <++= (Keys.sbtPlugin, Keys.isSnapshot) apply pluginResolvers @deprecated("Use `credentials(State)` instead to make use of configuration path dynamically configured via `Keys.globalSettingsDirectory`; relying on ~/.ivy2 is not recommended anymore.", "0.12.0") def credentials: Credentials = Credentials(userHome / ".ivy2" / ".credentials") From 717991238797966094fadfc36a3b8f5daf7fcf32 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Thu, 10 Apr 2014 03:16:41 -0500 Subject: [PATCH 24/38] `DefaultOptions` for resolvers should not resets existing values. `DefaultOptions.addResolvers` and `DefaultOptions.addPluginResolvers` should not reset the existing value while adding the new ones. The names are prefixed with _add_ afterall. --- main/src/main/scala/sbt/Opts.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/Opts.scala b/main/src/main/scala/sbt/Opts.scala index cb78f5adc..1fddce915 100644 --- a/main/src/main/scala/sbt/Opts.scala +++ b/main/src/main/scala/sbt/Opts.scala @@ -52,8 +52,8 @@ object DefaultOptions { def pluginResolvers(plugin: Boolean, snapshot: Boolean): Seq[Resolver] = { if (plugin && snapshot) Seq(Classpaths.typesafeSnapshots, Classpaths.sbtPluginSnapshots) else Nil } - def addResolvers: Setting[_] = Keys.resolvers <<= Keys.isSnapshot apply resolvers - def addPluginResolvers: Setting[_] = Keys.resolvers <<= (Keys.sbtPlugin, Keys.isSnapshot) apply pluginResolvers + def addResolvers: Setting[_] = Keys.resolvers <++= Keys.isSnapshot apply resolvers + def addPluginResolvers: Setting[_] = Keys.resolvers <++= (Keys.sbtPlugin, Keys.isSnapshot) apply pluginResolvers @deprecated("Use `credentials(State)` instead to make use of configuration path dynamically configured via `Keys.globalSettingsDirectory`; relying on ~/.ivy2 is not recommended anymore.", "0.12.0") def credentials: Credentials = Credentials(userHome / ".ivy2" / ".credentials") From 7fead66244bd428e611849b63c891ae2e5ee369c Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Thu, 10 Apr 2014 15:24:07 +0200 Subject: [PATCH 25/38] Pick the right overload in EvaluateTask.runTask The deprecated method should forward to the other overloaded alternative but it recursed instead. This kind of mistake would be easily caught by linter warning about unused `newConfig` local variable. I hope we'll get there some day. Fixes #1251 --- main/src/main/scala/sbt/EvaluateTask.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index c7df90b96..0ea0a4f9f 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -292,7 +292,7 @@ object EvaluateTask def runTask[T](root: Task[T], state: State, streams: Streams, triggers: Triggers[Task], config: EvaluateConfig)(implicit taskToNode: NodeView[Task]): (State, Result[T]) = { val newConfig = EvaluateTaskConfig(config) - runTask(root, state, streams, triggers, config)(taskToNode) + runTask(root, state, streams, triggers, newConfig)(taskToNode) } def runTask[T](root: Task[T], state: State, streams: Streams, triggers: Triggers[Task], config: EvaluateTaskConfig)(implicit taskToNode: NodeView[Task]): (State, Result[T]) = { From 57adb5ff7a719d90dfce2e3adae46273912840b9 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Apr 2014 10:05:15 -0400 Subject: [PATCH 26/38] Create test for configuration ordering regression. --- .../actions/configuration-delegation/build.sbt | 17 +++++++++++++++++ .../actions/configuration-delegation/test | 8 ++++++++ 2 files changed, 25 insertions(+) create mode 100644 sbt/src/sbt-test/actions/configuration-delegation/build.sbt create mode 100644 sbt/src/sbt-test/actions/configuration-delegation/test diff --git a/sbt/src/sbt-test/actions/configuration-delegation/build.sbt b/sbt/src/sbt-test/actions/configuration-delegation/build.sbt new file mode 100644 index 000000000..029644912 --- /dev/null +++ b/sbt/src/sbt-test/actions/configuration-delegation/build.sbt @@ -0,0 +1,17 @@ +lazy val foo = taskKey[Unit]("Runs the foo task") + +lazy val bar = taskKey[Unit]("Runs the bar task") + +def makeFoo(config: Configuration): Setting[_] = + foo in config := IO.write(file(s"${config.name}-foo"), "foo") + +lazy val PerformanceTest = (config("pt") extend Test) + +lazy val root = ( + (project in file(".")) + .configs(PerformanceTest) + .settings(Seq(Compile, Test, Runtime, PerformanceTest).map(makeFoo) :_*) + .settings( + bar in PerformanceTest := IO.write(file("pt-bar"), "bar") + ) +) \ No newline at end of file diff --git a/sbt/src/sbt-test/actions/configuration-delegation/test b/sbt/src/sbt-test/actions/configuration-delegation/test new file mode 100644 index 000000000..f0a77a058 --- /dev/null +++ b/sbt/src/sbt-test/actions/configuration-delegation/test @@ -0,0 +1,8 @@ +# First make sure a task just on the new config delegates. +> bar +$ exists pt-bar + + +# Now make sure compile is the default configuration +> foo +$ exists compile-foo \ No newline at end of file From 22b99a9a1ccde1343e9bde2981400cbc5fca6f96 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Apr 2014 10:12:00 -0400 Subject: [PATCH 27/38] Autoplugin confgiurations must be injected first into project. Fixes #1247 --- main/src/main/scala/sbt/Load.scala | 2 +- main/src/main/scala/sbt/Project.scala | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index a45dec8dd..672266c94 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -469,7 +469,7 @@ object Load val autoConfigs = autoPlugins.flatMap(_.projectConfigurations) val loadedSbtFiles = loadSbtFiles(project.auto, project.base, autoPlugins, project.settings) // add the automatically selected settings, record the selected AutoPlugins, and register the automatically selected configurations - val transformed = project.copy(settings = loadedSbtFiles.settings).setAutoPlugins(autoPlugins).overrideConfigs(autoConfigs : _*) + val transformed = project.copy(settings = loadedSbtFiles.settings).setAutoPlugins(autoPlugins).prefixConfigs(autoConfigs : _*) (transformed, loadedSbtFiles.projects) } def defaultLoad = loadSbtFiles(AddSettings.defaultSbtFiles, buildBase, Nil, Nil).projects diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 06420cf84..b2b4332e8 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -109,6 +109,11 @@ sealed trait Project extends ProjectDefinition[ProjectReference] /** Adds configurations to this project. Added configurations replace existing configurations with the same name.*/ def overrideConfigs(cs: Configuration*): Project = copy(configurations = Defaults.overrideConfigs(cs : _*)(configurations)) + /** Adds configuration at the *start* of the configuration list for this rpoject. Prevous configurations replace this prefix + * list with the same name. + */ + def prefixConfigs(cs: Configuration*): Project = copy(configurations = Defaults.overrideConfigs(configurations : _*)(cs)) + /** Adds new configurations directly to this project. To override an existing configuration, use `overrideConfigs`. */ def configs(cs: Configuration*): Project = copy(configurations = configurations ++ cs) From bc07810f7e7520937515149287974934c72ad55b Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Apr 2014 13:17:39 -0400 Subject: [PATCH 28/38] Mark new method private[sbt] because no one else should use it. --- 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 b2b4332e8..b26cd4088 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -112,7 +112,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference] /** Adds configuration at the *start* of the configuration list for this rpoject. Prevous configurations replace this prefix * list with the same name. */ - def prefixConfigs(cs: Configuration*): Project = copy(configurations = Defaults.overrideConfigs(configurations : _*)(cs)) + private[sbt] def prefixConfigs(cs: Configuration*): Project = copy(configurations = Defaults.overrideConfigs(configurations : _*)(cs)) /** Adds new configurations directly to this project. To override an existing configuration, use `overrideConfigs`. */ def configs(cs: Configuration*): Project = copy(configurations = configurations ++ cs) From b802196f77f0068134b20f47860c91172a8224c9 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Apr 2014 13:34:58 -0400 Subject: [PATCH 29/38] Add some of the latest 0.13.5 changes to the doc. --- src/sphinx/Community/Changes.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sphinx/Community/Changes.rst b/src/sphinx/Community/Changes.rst index 1c63180e5..4b038de3b 100644 --- a/src/sphinx/Community/Changes.rst +++ b/src/sphinx/Community/Changes.rst @@ -8,6 +8,12 @@ Changes - Added a new setting ``testResultLogger`` to allow customisation of logging of test results. (gh-1225) - When ``test`` is run and there are no tests available, omit logging output. Especially useful for aggregate modules. ``test-only`` et al unaffected. (gh-1185) +- sbt now uses minor-patch version of ivy 2.4 (org.scala-sbt.ivy:ivy:2.4.0-sbt-) +- ``sbt.Plugin`` deprecated in favor of ``sbt.AutoPlugin`` +- name-hashing incremental compiler now supports scala macros. +- ``testResultLogger`` is now configured. +- sbt-server hooks for task cancellation. + 0.13.1 to 0.13.2 ~~~~~~~~~~~~~~~~ From 36db1207cf278b2c3945150f2a2aab27b5c3ed43 Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Thu, 10 Apr 2014 20:21:42 +0200 Subject: [PATCH 30/38] Move NameHashing from incremental to api subproject. It should have been there from the beginning because NameHashing is tied to internals of the API subproject. It was added to incremental subproject by mistake. --- .../inc => api/src/main/scala/xsbt/api}/NameHashing.scala | 7 +++++-- .../test/scala/xsbt/api}/NameHashingSpecification.scala | 3 +-- compile/inc/src/main/scala/sbt/inc/Compile.scala | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) rename compile/{inc/src/main/scala/sbt/inc => api/src/main/scala/xsbt/api}/NameHashing.scala (96%) rename compile/{inc/src/test/scala/sbt/inc => api/src/test/scala/xsbt/api}/NameHashingSpecification.scala (99%) diff --git a/compile/inc/src/main/scala/sbt/inc/NameHashing.scala b/compile/api/src/main/scala/xsbt/api/NameHashing.scala similarity index 96% rename from compile/inc/src/main/scala/sbt/inc/NameHashing.scala rename to compile/api/src/main/scala/xsbt/api/NameHashing.scala index da998af2b..68e4933c4 100644 --- a/compile/inc/src/main/scala/sbt/inc/NameHashing.scala +++ b/compile/api/src/main/scala/xsbt/api/NameHashing.scala @@ -1,4 +1,4 @@ -package sbt.inc +package xsbt.api import xsbti.api.SourceAPI import xsbti.api.Definition @@ -6,7 +6,10 @@ import xsbti.api.DefinitionType import xsbti.api.ClassLike import xsbti.api._internalOnly_NameHash import xsbti.api._internalOnly_NameHashes -import xsbt.api.Visit +import xsbti.api.DefinitionType.ClassDef +import xsbti.api.DefinitionType.Module +import xsbti.api.DefinitionType.PackageModule +import xsbti.api.DefinitionType.Trait /** * A class that computes hashes for each group of definitions grouped by a simple name. diff --git a/compile/inc/src/test/scala/sbt/inc/NameHashingSpecification.scala b/compile/api/src/test/scala/xsbt/api/NameHashingSpecification.scala similarity index 99% rename from compile/inc/src/test/scala/sbt/inc/NameHashingSpecification.scala rename to compile/api/src/test/scala/xsbt/api/NameHashingSpecification.scala index 3a6e93827..1eddaec7d 100644 --- a/compile/inc/src/test/scala/sbt/inc/NameHashingSpecification.scala +++ b/compile/api/src/test/scala/xsbt/api/NameHashingSpecification.scala @@ -1,8 +1,7 @@ -package sbt.inc +package xsbt.api import org.junit.runner.RunWith import xsbti.api._ -import xsbt.api.HashAPI import org.specs2.mutable.Specification import org.specs2.runner.JUnitRunner diff --git a/compile/inc/src/main/scala/sbt/inc/Compile.scala b/compile/inc/src/main/scala/sbt/inc/Compile.scala index 925e8fd3d..95d9c31b1 100644 --- a/compile/inc/src/main/scala/sbt/inc/Compile.scala +++ b/compile/inc/src/main/scala/sbt/inc/Compile.scala @@ -154,7 +154,7 @@ private final class AnalysisCallback(internalMap: File => Option[File], external if (APIUtil.isScalaSourceName(sourceFile.getName) && APIUtil.hasMacro(source)) macroSources += sourceFile publicNameHashes(sourceFile) = { if (nameHashing) - (new NameHashing).nameHashes(source) + (new xsbt.api.NameHashing).nameHashes(source) else emptyNameHashes } From 004d9c3f4a1f1bb2dbbf47852e91fe9cc880b7bb Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Apr 2014 15:51:55 -0400 Subject: [PATCH 31/38] Remove deprecated EvaluateTask usage for new, easier to update variant. --- main/src/main/scala/sbt/GlobalPlugin.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/GlobalPlugin.scala b/main/src/main/scala/sbt/GlobalPlugin.scala index 0fa1b40e9..9be58542d 100644 --- a/main/src/main/scala/sbt/GlobalPlugin.scala +++ b/main/src/main/scala/sbt/GlobalPlugin.scala @@ -57,10 +57,10 @@ object GlobalPlugin } def evaluate[T](state: State, structure: BuildStructure, t: Task[T], roots: Seq[ScopedKey[_]]): (State, T) = { - import EvaluateTask._ + import EvaluateTask._ withStreams(structure, state) { str => val nv = nodeView(state, str, roots) - val config = EvaluateTask.defaultConfig(Project.extract(state), structure) + 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)) } From a7fb54e4dfd26c80da5dc402a88f6315c8d0e68e Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Fri, 11 Apr 2014 13:42:21 +0200 Subject: [PATCH 32/38] Add debug logging in ClassfileManager Add logging of various operations the transactional class file manager is doing. You can pass logger to be used by the transactional class file manager by using overloaded definition of `ClassfileManager.transactional` method. The old overload has been deprecated. The factory methods for class file manager in IncOptions companion object has been deprecated in favor of using ClassfileManager companion object directly. The code in Defaults.scala has been updated to use non-deprecated methods. The logging is turned off by default. The canonical way of enabling transactional class file manager in sbt project is: ``` incOptions := incOptions.value.withNewClassfileManager( sbt.inc.ClassfileManager.transactional( crossTarget.value / "classes.bak", (streams in (compile, Compile)).value.log ) ) ``` It's a bit verbose which shows that the api for this is not the best. However, I don't expect sbt users to need this code very often. This patch should help debug the problem described in #1184 --- .../main/scala/sbt/inc/ClassfileManager.scala | 26 +++++++++++++++---- .../src/main/scala/sbt/inc/IncOptions.scala | 7 +++-- main/src/main/scala/sbt/Defaults.scala | 3 ++- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/compile/inc/src/main/scala/sbt/inc/ClassfileManager.scala b/compile/inc/src/main/scala/sbt/inc/ClassfileManager.scala index 636c0aec3..e4a449a4e 100644 --- a/compile/inc/src/main/scala/sbt/inc/ClassfileManager.scala +++ b/compile/inc/src/main/scala/sbt/inc/ClassfileManager.scala @@ -13,7 +13,7 @@ trait ClassfileManager * Any empty ancestor directories of deleted files must not exist either.*/ def delete(classes: Iterable[File]): Unit - /** Called once per compilation step with the class files generated during that step.*/ + /** Called once per compilation step with the class files generated during that step.*/ def generated(classes: Iterable[File]): Unit /** Called once at the end of the whole compilation run, with `success` indicating whether compilation succeeded (true) or not (false).*/ @@ -29,29 +29,45 @@ object ClassfileManager def generated(classes: Iterable[File]) {} def complete(success: Boolean) {} } + @deprecated("Use overloaded variant that takes additional logger argument, instead.", "0.13.5") + def transactional(tempDir0: File): () => ClassfileManager = + transactional(tempDir0, sbt.Logger.Null) /** When compilation fails, this ClassfileManager restores class files to the way they were before compilation.*/ - def transactional(tempDir0: File): () => ClassfileManager = () => new ClassfileManager + def transactional(tempDir0: File, logger: sbt.Logger): () => ClassfileManager = () => new ClassfileManager { val tempDir = tempDir0.getCanonicalFile IO.delete(tempDir) IO.createDirectory(tempDir) + logger.debug(s"Created transactional ClassfileManager with tempDir = $tempDir") private[this] val generatedClasses = new mutable.HashSet[File] private[this] val movedClasses = new mutable.HashMap[File, File] - + + private def showFiles(files: Iterable[File]): String = files.map(f => s"\t$f").mkString("\n") def delete(classes: Iterable[File]) { - for(c <- classes) if(c.exists && !movedClasses.contains(c) && !generatedClasses(c)) + logger.debug(s"About to delete class files:\n${showFiles(classes)}") + val toBeBackedUp = classes.filter(c => c.exists && !movedClasses.contains(c) && !generatedClasses(c)) + logger.debug(s"We backup classs files:\n${showFiles(toBeBackedUp)}") + for(c <- toBeBackedUp) { movedClasses.put(c, move(c)) + } IO.deleteFilesEmptyDirs(classes) } - def generated(classes: Iterable[File]): Unit = generatedClasses ++= classes + def generated(classes: Iterable[File]): Unit = { + logger.debug(s"Registering generated classes:\n${showFiles(classes)}") + generatedClasses ++= classes + } def complete(success: Boolean) { if(!success) { + logger.debug("Rolling back changes to class files.") + logger.debug(s"Removing generated classes:\n${showFiles(generatedClasses)}") IO.deleteFilesEmptyDirs(generatedClasses) + logger.debug(s"Restoring class files: \n${showFiles(movedClasses.map(_._1))}") for( (orig, tmp) <- movedClasses ) IO.move(tmp, orig) } + logger.debug(s"Removing the temporary directory used for backing up class files: $tempDir") IO.delete(tempDir) } diff --git a/compile/inc/src/main/scala/sbt/inc/IncOptions.scala b/compile/inc/src/main/scala/sbt/inc/IncOptions.scala index 0e634aa4f..70add5183 100644 --- a/compile/inc/src/main/scala/sbt/inc/IncOptions.scala +++ b/compile/inc/src/main/scala/sbt/inc/IncOptions.scala @@ -234,9 +234,12 @@ object IncOptions extends Serializable { private def readResolve(): Object = IncOptions //- EXPANDED CASE CLASS METHOD END -// - def defaultTransactional(tempDir: File): IncOptions = setTransactional(Default, tempDir) + @deprecated("Use IncOptions.Default.withNewClassfileManager(ClassfileManager.transactional(tempDir)), instead.", "0.13.5") + def defaultTransactional(tempDir: File): IncOptions = + setTransactional(Default, tempDir) + @deprecated("Use opts.withNewClassfileManager(ClassfileManager.transactional(tempDir)), instead.", "0.13.5") def setTransactional(opts: IncOptions, tempDir: File): IncOptions = - opts.copy(newClassfileManager = ClassfileManager.transactional(tempDir)) + opts.withNewClassfileManager(ClassfileManager.transactional(tempDir, sbt.Logger.Null)) private val transitiveStepKey = "transitiveStep" private val recompileAllFractionKey = "recompileAllFraction" diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 96390d869..d24010467 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -215,7 +215,8 @@ object Defaults extends BuildCommon ) def compileBase = inTask(console)(compilersSetting :: Nil) ++ compileBaseGlobal ++ Seq( - incOptions := IncOptions.setTransactional(incOptions.value, crossTarget.value / "classes.bak"), + incOptions := incOptions.value.withNewClassfileManager( + sbt.inc.ClassfileManager.transactional(crossTarget.value / "classes.bak", sbt.Logger.Null)), scalaInstance <<= scalaInstanceTask, crossVersion := (if(crossPaths.value) CrossVersion.binary else CrossVersion.Disabled), crossTarget := makeCrossTarget(target.value, scalaBinaryVersion.value, sbtBinaryVersion.value, sbtPlugin.value, crossPaths.value) From fcdbfcf4f46d43e36365a89dfebab841b74715f9 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 14 Apr 2014 12:13:39 -0400 Subject: [PATCH 33/38] Update CONTRIBUTING.md --- CONTRIBUTING.md | 96 ++++++++++++++++++++++++++++++++++++++++--------- LICENSE | 2 +- NOTICE | 4 +-- README.md | 32 +++++++++++++---- 4 files changed, 109 insertions(+), 25 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc29bb59f..90d5e2245 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,36 +1,99 @@ -[Setup]: http://www.scala-sbt.org/release/docs/Getting-Started/Setup + [sbt-dev]: https://groups.google.com/d/forum/sbt-dev‎ + [StackOverflow]: http://stackoverflow.com/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‎ + [subscriptions]: http://typesafe.com/how/subscription + [327]: https://github.com/sbt/sbt/issues/327 -# Issues and Pull Requests +Issues and Pull Requests +======================== -## New issues +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. -Please use the issue tracker to report confirmed bugs. -Do not use it to ask questions. -If you are uncertain whether something is a bug, please ask on StackOverflow or the sbt-dev mailing list first. +Effective bug reports are more likely to be fixed. These guidelines explain how to write such reports and pull requests. -When opening a new issue, +Preliminatries +-------------- - * Please state the problem clearly and provide enough context. - + Code examples and build transcripts are often useful when appropriately edited. - + Show error messages and stack traces if appropriate. - * Minimize the problem to reduce non-essential factors. For example, dependencies or special environments. - * Include all relevant information needed to reproduce, such as the version of sbt and Scala being used. +- 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 +------------------------------------------ + +sbt project uses GitHub issue as a publically visible todo list. Please open a GitHub issue only when asked to do so. + +- 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 need faster response time, consider one of the [Typesafe subscriptions][subscriptions]. + +What to report +-------------- + +The developers need three things from you: **steps**, **problems**, and **expectations**. + +### 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 + +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 complication errors, include stack trace. More raw info the better. + +### Expectations + +Same as the problems. Describe what *you think* should've happened. + +### Notes + +Add an optional notes section to describe your analysis. + +### Subject + +The subject of the bug report doesn't matter. A more descriptive subject is certainly better, but a good subject really depends on the analysis of the problem, so don't worry too much about it. "StackOverflowError while name hashing is enabled" is good enough. + +### Formatting + +If possible, please format code or console outputs. + +On Github it's: + + ```scala + name := "foo" + ``` + +On StackOverflow, it's: + +``` + + + name := "foo" +``` + +Here's a simple sample case: [#327][327]. Finally, thank you for taking the time to report a problem. -## Pull Requests +Pull Requests +------------- Whether implementing a new feature, fixing a bug, or modifying documentation, please work against the latest development branch (currently, 0.13). Binary compatible changes will be backported to a previous series (currently, 0.12.x) at the time of the next stable release. See below for instructions on building sbt from source. -## Documentation +Documentation +------------- Documentation fixes and contributions are welcome. They are made via pull requests, as described in the previous section. See below for details on getting sbt sources and modifying the documentation. -# Build from source +Build 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. @@ -73,7 +136,8 @@ See below for details on getting sbt sources and modifying the documentation. 4. If a project has `project/build.properties` defined, either delete the file or change `sbt.version` to `0.13.2-SNAPSHOT`. -## Building Documentation +Building Documentation +---------------------- The scala-sbt.org site documentation is built using sphinx and requires some external packages to be manually installed first: diff --git a/LICENSE b/LICENSE index 46c73ae23..d70192438 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2008, 2009, 2010 Steven Blundy, Josh Cough, Mark Harrah, Stuart Roebuck, Tony Sloane, Vesa Vilhonen, Jason Zaugg +Copyright (c) 2008-2014 Typesafe Inc, Mark Harrah, Grzegorz Kossakowski, Josh Suereth, Indrajit Raychaudhuri, Eugene Yokota, and other contributors. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/NOTICE b/NOTICE index 88899abdc..55efecac8 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ -Simple Build Tool -Copyright 2008, 2009, 2010 Mark Harrah, Jason Zaugg +sbt +Copyright (c) 2008-2014 Typesafe Inc, Mark Harrah, Grzegorz Kossakowski, Josh Suereth, Indrajit Raychaudhuri, Eugene Yokota, and other contributors. Licensed under BSD-style license (see LICENSE) Portions based on code from the Scala compiler. Portions of the Scala diff --git a/README.md b/README.md index 385099c0f..4455751f3 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,33 @@ -[Google Code]: http://code.google.com/p/simple-build-tool -[CONTRIBUTING]: CONTRIBUTING.md -[Setup]: http://www.scala-sbt.org/release/docs/Getting-Started/Setup -[FAQ]: http://www.scala-sbt.org/release/docs/faq + [Google Code]: http://code.google.com/p/simple-build-tool + [CONTRIBUTING]: CONTRIBUTING.md + [Setup]: http://www.scala-sbt.org/release/docs/Getting-Started/Setup + [FAQ]: http://www.scala-sbt.org/release/docs/faq + [sbt-dev]: https://groups.google.com/d/forum/sbt-dev‎ + [StackOverflow]: http://stackoverflow.com/tags/sbt + [LICENSE]: LICENSE -# sbt 0.13 +sbt +=== + +sbt is a build tool for Scala, Java, and more. +For general documentation, see http://www.scala-sbt.org/. + +Issues and Pull Requests +------------------------ + +Please read [CONTRIBUTING] carefully before opening a GitHub Issue. +The short version: try [StackOverflow] and [sbt-dev]. Don't open an Issue. + +sbt 0.13 +-------- This is the 0.13.x series of sbt. * [Setup]: Describes getting started with the latest binary release. - * See [CONTRIBUTING] for how to build from source, open an issue, fix or add documentation, or submit a pull request. * [FAQ]: Explains how to get help and more. * [Google Code]: hosts sbt 0.7.7 and earlier versions + +license +------- + +See [LICENSE]. From 3632a998ca8bbfc70dcbfc44689c3d62caeb519f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 14 Apr 2014 12:46:34 -0400 Subject: [PATCH 34/38] Fixing typos --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 90d5e2245..59ec999f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ 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. -Preliminatries +Preliminaries -------------- - Make sure your sbt version is up to date. @@ -43,7 +43,7 @@ Repro steps are the most important part of a bug report. If we cannot reproduce ### 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 complication errors, include stack trace. More raw info the better. +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 From 1c3231f017bb79657857c87e1bf93117716f0118 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 15 Apr 2014 13:22:40 -0400 Subject: [PATCH 35/38] Documentation for EvaluateConfigurations class. --- .../scala/sbt/EvaluateConfigurations.scala | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/main/src/main/scala/sbt/EvaluateConfigurations.scala b/main/src/main/scala/sbt/EvaluateConfigurations.scala index d66c69e1f..81732a98a 100644 --- a/main/src/main/scala/sbt/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/EvaluateConfigurations.scala @@ -11,21 +11,51 @@ package sbt import Scope.GlobalScope import scala.annotation.tailrec + +/** + * This file is responsible for compiling the .sbt files used to configure sbt builds. + * + * Compilation is done in three phases: + * + * 1. Parsing high-level constructs (definitions, settings, imports) + * 2. Compiling scala code into local .class files + * 3. Evaluating the expressions and obtaining in-memory objects of the results (Setting[_] instances, or val references). + * + * + */ 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)]) + /** The keywords we look for when classifying a string as a definition. */ private[this] val DefinitionKeywords = Seq("lazy val ", "def ", "val ") + /** Using an evaluating instance of the scala compiler, a sequence of files and + * the default imports to use, this method will take a ClassLoader of sbt-classes and + * 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]): ClassLoader => 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. + */ def evaluateConfiguration(eval: Eval, src: File, imports: Seq[String]): ClassLoader => Seq[Setting[_]] = evaluateConfiguration(eval, src, IO.readLines(src), imports, 0) + /** + * Parses a sequence of build.sbt lines into a [[ParsedFile]]. The result contains + * a fragmentation of all imports, settings and definitions. + * + * @param buildinImports The set of import statements to add to those parsed in the .sbt file. + */ private[this] def parseConfiguration(lines: Seq[String], builtinImports: Seq[String], offset: Int): ParsedFile = { val (importStatements, settingsAndDefinitions) = splitExpressions(lines) @@ -34,12 +64,33 @@ object EvaluateConfigurations new ParsedFile(allImports, definitions, settings) } + /** + * Evaluates a parsed sbt configuration file. + * + * @param eval The evaluating scala compiler instance we use to handle evaluating scala configuration. + * @param file The file we've parsed + * @param imports The default imports to use in this .sbt configuration + * @param lines The lines of the configurtion we'd like to evaluate. + * + * @return Just the Setting[_] instances defined in the .sbt file. + */ def evaluateConfiguration(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => Seq[Setting[_]] = { val l = evaluateSbtFile(eval, file, lines, imports, offset) loader => l(loader).settings } + /** + * Evaluates a parsed sbt configuration file. + * + * @param eval The evaluating scala compiler instance we use to handle evaluating scala configuration. + * @param file The file we've parsed + * @param lines The lines of the configurtion we'd like to evaluate. + * @param imports The default imports to use in this .sbt configuration. + * + * @return A function which can take an sbt classloader and return the raw types/configuratoin + * which was compiled/parsed for the given file. + */ private[sbt] def evaluateSbtFile(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => LoadedSbtFile = { val name = file.getPath @@ -58,6 +109,7 @@ object EvaluateConfigurations val loadSettings = flatten(settings) loader => new LoadedSbtFile(loadSettings(loader), projects(loader), importDefs) } + /** 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)) def flatten(mksettings: Seq[ClassLoader => Seq[Setting[_]]]): ClassLoader => Seq[Setting[_]] = loader => mksettings.flatMap(_ apply loader) @@ -66,10 +118,26 @@ object EvaluateConfigurations def addOffsetToRange(offset: Int, ranges: Seq[(String,LineRange)]): Seq[(String,LineRange)] = ranges.map { case (s, r) => (s, r shift offset) } + /** + * The name of the class we cast DSL "setting" (vs. definition) lines to. + */ val SettingsDefinitionName = { val _ = classOf[sbt.Def.SettingsDefinition] // this line exists to try to provide a compile-time error when the following line needs to be changed "sbt.Def.SettingsDefinition" } + /** + * This actually compiles a scala expression which represents a Seq[Setting[_]], although the + * expression may be just a single setting. + * + * @param eval The mechanism to compile and evaluate Scala expressions. + * @param name The name for the thing we're compiling + * @param imports The scala imports to have in place when we compile the expression + * @param expression The scala expression we're compiling + * @param range The original position in source of the expression, for error messages. + * + * @return A method that given an sbt classloader, can return the actual Seq[Setting[_]] defined by + * the expression. + */ def evaluateSetting(eval: Eval, name: String, imports: Seq[(String,Int)], expression: String, range: LineRange): ClassLoader => Seq[Setting[_]] = { val result = try { @@ -86,6 +154,10 @@ object EvaluateConfigurations private[this] def fstS(f: String => Boolean): ((String,Int)) => Boolean = { case (s,i) => f(s) } private[this] def firstNonSpaceIs(lit: String) = (_: String).view.dropWhile(isSpace).startsWith(lit) private[this] def or[A](a: A => Boolean, b: A => Boolean): A => Boolean = in => a(in) || b(in) + /** + * Splits a set of lines into (imports, expressions). That is, + * anything on the right of the tuple is a scala expression (definition or setting). + */ def splitExpressions(lines: Seq[String]): (Seq[(String,Int)], Seq[(String,LineRange)]) = { val blank = (_: String).forall(isSpace) From 51ee5048f012092d76864651a65d25b64ae861c6 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 15 Apr 2014 14:38:11 -0400 Subject: [PATCH 36/38] Rename `addPlugins` to `enablePlugins` to better represent what it does. This is to disambiguate between `addSbtPlugin` and `enablePlugins` for autoplugins. --- main/src/main/scala/sbt/Plugins.scala | 4 ++-- main/src/main/scala/sbt/Project.scala | 4 ++-- sbt/src/sbt-test/project/auto-plugins/build.sbt | 8 ++++---- .../sbt-test/project/binary-plugin/changes/define/A.scala | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/main/src/main/scala/sbt/Plugins.scala b/main/src/main/scala/sbt/Plugins.scala index 63795bd6d..9738ed053 100644 --- a/main/src/main/scala/sbt/Plugins.scala +++ b/main/src/main/scala/sbt/Plugins.scala @@ -41,11 +41,11 @@ Steps for users: For example, given plugins Web and Javascript (perhaps provided by plugins added with addSbtPlugin), - .addPlugins( Web && Javascript ) + .enablePlugins( Web && Javascript ) will activate `MyPlugin` defined above and have its settings automatically added. If the user instead defines - .addPlugins( Web && Javascript ).disablePlugins(MyPlugin) + .enablePlugins( Web && Javascript ).disablePlugins(MyPlugin) then the `MyPlugin` settings (and anything that activates only when `MyPlugin` is activated) will not be added. diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index b26cd4088..cd04879fd 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -145,8 +145,8 @@ sealed trait Project extends ProjectDefinition[ProjectReference] def setSbtFiles(files: File*): Project = copy(auto = AddSettings.append( AddSettings.clearSbtFiles(auto), AddSettings.sbtFiles(files: _*)) ) /** Sets the [[AutoPlugin]]s of this project. - A [[AutoPlugin]] is a common label that is used by plugins to determine what settings, if any, to add to a project. */ - def addPlugins(ns: Plugins*): Project = setPlugins(ns.foldLeft(plugins)(Plugins.and)) + A [[AutoPlugin]] is a common label that is used by plugins to determine what settings, if any, to enable on a project. */ + def enablePlugins(ns: Plugins*): Project = setPlugins(ns.foldLeft(plugins)(Plugins.and)) /** Disable the given plugins on this project. */ def disablePlugins(ps: AutoPlugin*): Project = diff --git a/sbt/src/sbt-test/project/auto-plugins/build.sbt b/sbt/src/sbt-test/project/auto-plugins/build.sbt index 7039ed235..bb3ec3b44 100644 --- a/sbt/src/sbt-test/project/auto-plugins/build.sbt +++ b/sbt/src/sbt-test/project/auto-plugins/build.sbt @@ -1,17 +1,17 @@ // disablePlugins(Q) will prevent R from being auto-added -lazy val projA = project.addPlugins(A, B).disablePlugins(Q) +lazy val projA = project.enablePlugins(A, B).disablePlugins(Q) // without B, Q is not added -lazy val projB = project.addPlugins(A) +lazy val projB = project.enablePlugins(A) // with both A and B, Q is selected, which in turn selects R, but not S -lazy val projC = project.addPlugins(A, B) +lazy val projC = project.enablePlugins(A, B) // with no natures defined, nothing is auto-added lazy val projD = project // with S selected, Q is loaded automatically, which in turn selects R -lazy val projE = project.addPlugins(S) +lazy val projE = project.enablePlugins(S) check := { val adel = (del in projA).?.value // should be None diff --git a/sbt/src/sbt-test/project/binary-plugin/changes/define/A.scala b/sbt/src/sbt-test/project/binary-plugin/changes/define/A.scala index dde89c439..9675ce201 100644 --- a/sbt/src/sbt-test/project/binary-plugin/changes/define/A.scala +++ b/sbt/src/sbt-test/project/binary-plugin/changes/define/A.scala @@ -23,5 +23,5 @@ object A extends AutoPlugin { } object B extends Build { - lazy val extra = project.addPlugins(bN) + lazy val extra = project.enablePlugins(bN) } From f92206b785f4e12502a924c415d7490145427f36 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 16 Apr 2014 08:44:03 -0400 Subject: [PATCH 37/38] Fixes issue with deadlocking server discovery on windows. It turns out windows blocks until a process is finished when you try to close the stderr handle, but not stdout. Therefore, we leave stderr open when we shut down, which lets the server continue to run on windows. --- launch/src/main/scala/xsbt/boot/ServerApplication.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/launch/src/main/scala/xsbt/boot/ServerApplication.scala b/launch/src/main/scala/xsbt/boot/ServerApplication.scala index 3f592b151..0b7600b83 100644 --- a/launch/src/main/scala/xsbt/boot/ServerApplication.scala +++ b/launch/src/main/scala/xsbt/boot/ServerApplication.scala @@ -150,7 +150,12 @@ object ServerLauncher { } finally { errorDumper.close() stdout.close() - stderr.close() + // Note: Closing this causes windows to block waiting for the server + // to close, but it ne'er will, as it is obstinate, and not designed + // to close immediately, unlike this process. + // We leave it open because this JVM shold be shut down soon anyway, + // and that will clean up al this memory. + //stderr.close() } } From 75f0438946f802b915f95c20dbe3040d6ef617e6 Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Wed, 16 Apr 2014 17:56:49 +0200 Subject: [PATCH 38/38] Use the correct state in buildPluginDefinition() Within buildPluginDefinition(), the call to setProject() can (and usually will) return a modified structure together with the new state. The subsequent call to evalPluginDef() should use the updated structure, rather the old stucture that was present before the setProject() ("pluginDef"); if that is not the case, the code called by evalPluginDef() will find an inconsistent structure/state combination, and behave in bizarre ways as a result. More in general, it is a bit dangerous to pass to routines in parallel the two separate state and structure, as the two may easily inadvertently fall out of alignment, as in this case. This patch should be applied to both the 0.13 branch as well as to a future 0.12.5 release (the corresponding file there is ./main/Load.scala). --- main/src/main/scala/sbt/Load.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index 672266c94..36d39232a 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -612,7 +612,7 @@ object Load { val (eval,pluginDef) = apply(dir, s, config) val pluginState = Project.setProject(Load.initialSession(pluginDef, eval), pluginDef, s) - config.evalPluginDef(pluginDef, pluginState) + config.evalPluginDef(Project.structure(pluginState), pluginState) } @deprecated("Use ModuleUtilities.getCheckedObjects[Build].", "0.13.2")