From ca3acc4e52668c77a1f2dfa0cc247a45936cf7bd Mon Sep 17 00:00:00 2001 From: jvican Date: Thu, 25 May 2017 15:19:35 +0200 Subject: [PATCH] Improve if check and prohibit value inside anon This commit does the following things: * Removes the boolean from the instance context passes to the linter. * Prohibits the use of value inside anonymous functions. * Improves the previous check of `value` inside if. The improvements have occurred thanks to the fix of an oversight in the traverser. As a result, several implementation of tasks have been rewritten because of new compilation failures by both checks. Note that the new check that prohibits the use of value inside anonymous functions ignores all the functions whose parameters have been synthesized by scalac (that can happen in a number of different scenarios, like for comprehensions). Other scripted tests have also been fixed. Running `.value` inside an anonymous function yields the following error: ``` [error] /data/rw/code/scala/sbt/main-settings/src/test/scala/sbt/std/TaskPosSpec.scala:50:24: The evaluation of `foo` inside an anonymous function is prohibited. [error] [error] Problem: Task invocations inside anonymous functions are evaluated independently of whether the anonymous function is invoked or not. [error] [error] Solution: [error] 1. Make `foo` evaluation explicit outside of the function body if you don't care about its evaluation. [error] 2. Use a dynamic task to evaluate `foo` and pass that value as a parameter to an anonymous function. [error] [error] val anon = () => foo.value + " " [error] ^ ``` --- .../sbt/internal/util/appmacro/Instance.scala | 2 +- .../internal/util/appmacro/LinterDSL.scala | 7 +- .../main/scala/sbt/std/TaskLinterDSL.scala | 225 ++++++++++++++---- .../src/main/scala/sbt/std/TaskMacro.scala | 6 +- .../src/test/scala/sbt/std/TaskPosSpec.scala | 28 +++ .../test/scala/sbt/std/neg/TaskNegSpec.scala | 88 ++++++- main/src/main/scala/sbt/Defaults.scala | 86 ++++--- .../sbt-test/actions/external-doc/build.sbt | 6 +- .../unstable-existential-names/build.sbt | 3 +- .../inc-pickled-existential/build.sbt | 3 +- .../inc-pickled-refinement/build.sbt | 3 +- .../pom-parent-pom/build.sbt | 3 +- .../snapshot-resolution/build.sbt | 5 +- .../abstract-type-override/build.sbt | 3 +- .../source-dependencies/canon/build.sbt | 3 +- .../source-dependencies/ext/build.sbt | 3 +- .../less-inter-inv-java/build.sbt | 3 +- .../less-inter-inv/build.sbt | 3 +- .../restore-classes/build.sbt | 3 +- .../sbt-test/tests/fork-parallel/build.sbt | 3 +- .../tests/fork-test-group-parallel/build.sbt | 30 ++- .../tests/setup-cleanup/changes/setup.sbt | 8 +- 22 files changed, 399 insertions(+), 125 deletions(-) diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/Instance.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/Instance.scala index 3ffbb7d48..7917d5255 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/Instance.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/Instance.scala @@ -188,7 +188,7 @@ object Instance { } // applies the transformation - linter.runLinter(c)(tree, t.isLeft) + linter.runLinter(c)(tree) val tx = util.transformWrappers(tree, (n, tpe, t, replace) => sub(n, tpe, t, replace)) // resetting attributes must be: a) local b) done here and not wider or else there are obscure errors val tr = makeApp(inner(tx)) diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/LinterDSL.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/LinterDSL.scala index fc87e7316..0ac846e9e 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/LinterDSL.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/LinterDSL.scala @@ -3,14 +3,11 @@ package sbt.internal.util.appmacro import scala.reflect.macros.blackbox trait LinterDSL { - def runLinter(ctx: blackbox.Context)(tree: ctx.Tree, isApplicableFromContext: => Boolean): Unit + def runLinter(ctx: blackbox.Context)(tree: ctx.Tree): Unit } object LinterDSL { object Empty extends LinterDSL { - override def runLinter(ctx: blackbox.Context)( - tree: ctx.Tree, - isApplicableFromContext: => Boolean - ): Unit = () + override def runLinter(ctx: blackbox.Context)(tree: ctx.Tree): Unit = () } } diff --git a/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala b/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala index 0f861d363..7cd64f42a 100644 --- a/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala +++ b/main-settings/src/main/scala/sbt/std/TaskLinterDSL.scala @@ -1,85 +1,206 @@ package sbt.std import sbt.internal.util.ConsoleAppender -import sbt.internal.util.appmacro.LinterDSL +import sbt.internal.util.appmacro.{ Convert, Converted, LinterDSL } import scala.collection.mutable.{ HashSet => MutableSet } import scala.io.AnsiColor +import scala.reflect.internal.util.Position import scala.reflect.macros.blackbox -object TaskLinterDSL extends LinterDSL { - override def runLinter(ctx: blackbox.Context)( - tree: ctx.Tree, - isApplicableFromContext: => Boolean - ): Unit = { +abstract class BaseTaskLinterDSL extends LinterDSL { + def isDynamicTask: Boolean + def convert: Convert + + override def runLinter(ctx: blackbox.Context)(tree: ctx.Tree): Unit = { import ctx.universe._ - val isTask = FullConvert.asPredicate(ctx) - object traverser extends Traverser { + val isTask = convert.asPredicate(ctx) + class traverser extends Traverser { private val unchecked = symbolOf[sbt.unchecked].asClass private val uncheckedWrappers = MutableSet.empty[Tree] var insideIf: Boolean = false - override def traverse(tree: ctx.universe.Tree): Unit = { - tree match { - case If(condition, thenp, elsep) => - super.traverse(condition) - insideIf = true - super.traverse(thenp) - super.traverse(elsep) - insideIf = false - case s @ Apply(TypeApply(Select(_, nme), tpe :: Nil), qual :: Nil) => - val shouldIgnore = uncheckedWrappers.contains(s) - val wrapperName = nme.decodedName.toString - if (insideIf && !shouldIgnore && isTask(wrapperName, tpe.tpe, qual)) { - val qualName = qual.symbol.name.decodedName.toString - ctx.error(s.pos, TaskLinterDSLFeedback.useOfValueInsideIfExpression(qualName)) - } - // Don't remove, makes the whole analysis work - case Typed(expr, tt) => - super.traverse(expr) - super.traverse(tt) - case Typed(expr, tt: TypeTree) if tt.original != null => - tt.original match { - case Annotated(annot, arg) => - Option(annot.tpe) match { - case Some(AnnotatedType(annotations, _)) => - val symAnnotations = annotations.map(_.tree.tpe.typeSymbol) - val isUnchecked = symAnnotations.contains(unchecked) - if (isUnchecked) { - // Unwrap the expression behind the typed that sbt macro adds - val toAdd = arg match { - case Typed(`expr`, _) => `expr` - case `tree` => `tree` - } - uncheckedWrappers.add(toAdd) - } - case _ => + var insideAnon: Boolean = false + + def handleUncheckedAnnotation(exprAtUseSite: Tree, tt: TypeTree): Unit = { + tt.original match { + case Annotated(annot, arg) => + Option(annot.tpe) match { + case Some(AnnotatedType(annotations, _)) => + val tpeAnnotations = annotations.flatMap(ann => Option(ann.tree.tpe).toList) + val symAnnotations = tpeAnnotations.map(_.typeSymbol) + val isUnchecked = symAnnotations.contains(unchecked) + if (isUnchecked) { + // Use expression at use site, arg contains the old expr + // Referential equality between the two doesn't hold + val removedSbtWrapper = exprAtUseSite match { + case Typed(t, _) => t + case _ => exprAtUseSite + } + uncheckedWrappers.add(removedSbtWrapper) } case _ => } - super.traverse(expr) - case tree => super.traverse(tree) + case _ => + } + } + + override def traverse(tree: ctx.universe.Tree): Unit = { + tree match { + case s @ Apply(TypeApply(Select(selectQual, nme), tpe :: Nil), qual :: Nil) => + val shouldIgnore = uncheckedWrappers.contains(s) + val wrapperName = nme.decodedName.toString + if (!shouldIgnore && isTask(wrapperName, tpe.tpe, qual)) { + val qualName = + if (qual.symbol != null) qual.symbol.name.decodedName.toString + else s.pos.lineContent + if (insideIf && !isDynamicTask) { + // Error on the use of value inside the if of a regular task (dyn task is ok) + ctx.error(s.pos, TaskLinterDSLFeedback.useOfValueInsideIfExpression(qualName)) + } + if (insideAnon) { + // Error on the use of anonymous functions in any task or dynamic task + ctx.error(s.pos, TaskLinterDSLFeedback.useOfValueInsideAnon(qualName)) + } + } else traverse(selectQual) + traverse(qual) + case If(condition, thenp, elsep) => + traverse(condition) + insideIf = true + traverse(thenp) + traverse(elsep) + insideIf = false + case Typed(expr, tpt: TypeTree) if tpt.original != null => + handleUncheckedAnnotation(expr, tpt) + traverse(expr) + traverse(tpt) + case f @ Function(vparams, body) => + super.traverseTrees(vparams) + if (!vparams.exists(_.mods.hasFlag(Flag.SYNTHETIC))) { + insideAnon = true + traverse(body) + insideAnon = false + } else traverse(body) + case _ => super.traverse(tree) } } } - if (!isApplicableFromContext) () - else traverser.traverse(tree) + (new traverser).traverse(tree) } } +object TaskLinterDSL extends BaseTaskLinterDSL { + override val isDynamicTask: Boolean = false + override def convert: Convert = FullConvert +} + +object OnlyTaskLinterDSL extends BaseTaskLinterDSL { + override val isDynamicTask: Boolean = false + override def convert: Convert = TaskConvert +} + +object TaskDynLinterDSL extends BaseTaskLinterDSL { + override val isDynamicTask: Boolean = true + override def convert: Convert = FullConvert +} + +object OnlyTaskDynLinterDSL extends BaseTaskLinterDSL { + override val isDynamicTask: Boolean = true + override def convert: Convert = TaskConvert +} + object TaskLinterDSLFeedback { private final val startBold = if (ConsoleAppender.formatEnabled) AnsiColor.BOLD else "" private final val startRed = if (ConsoleAppender.formatEnabled) AnsiColor.RED else "" private final val startGreen = if (ConsoleAppender.formatEnabled) AnsiColor.GREEN else "" private final val reset = if (ConsoleAppender.formatEnabled) AnsiColor.RESET else "" - def useOfValueInsideIfExpression(offendingValue: String) = - s"""${startBold}The evaluation of `${offendingValue}` happens always inside a regular task.$reset + private final val ProblemHeader = s"${startRed}Problem${reset}" + private final val SolutionHeader = s"${startGreen}Solution${reset}" + + def useOfValueInsideAnon(task: String) = + s"""${startBold}The evaluation of `$task` inside an anonymous function is prohibited.$reset | - |${startRed}PROBLEM${reset}: `${offendingValue}` is inside the if expression of a regular task. + |${ProblemHeader}: Task invocations inside anonymous functions are evaluated independently of whether the anonymous function is invoked or not. + | + |${SolutionHeader}: + | 1. Make `$task` evaluation explicit outside of the function body if you don't care about its evaluation. + | 2. Use a dynamic task to evaluate `$task` and pass that value as a parameter to an anonymous function. + """.stripMargin + + def useOfValueInsideIfExpression(task: String) = + s"""${startBold}The evaluation of `$task` happens always inside a regular task.$reset + | + |${ProblemHeader}: `$task` is inside the if expression of a regular task. | Regular tasks always evaluate task inside the bodies of if expressions. | - |${startGreen}SOLUTION${reset}: + |${SolutionHeader}: | 1. If you only want to evaluate it when the if predicate is true or false, use a dynamic task. - | 2. Otherwise, make the static evaluation explicit by evaluating `${offendingValue}` outside the if expression. + | 2. Otherwise, make the static evaluation explicit by evaluating `$task` outside the if expression. """.stripMargin + + /* If( + Ident(TermName("condition")), + Typed( + Typed(Apply(TypeApply(Select(Ident(sbt.std.InputWrapper), + TermName("wrapInitTask_$u2603$u2603")), + List(TypeTree())), + List(Ident(TermName("foo")))), + TypeTree()), + TypeTree().setOriginal( + Annotated( + Apply(Select(New(Ident(TypeName("unchecked"))), termNames.CONSTRUCTOR), List()), + Typed(Apply(TypeApply(Select(Ident(sbt.std.InputWrapper), + TermName("wrapInitTask_$u2603$u2603")), + List(TypeTree())), + List(Ident(TermName("foo")))), + TypeTree()) + )) + ), + Typed( + Typed(Apply(TypeApply(Select(Ident(sbt.std.InputWrapper), + TermName("wrapInitTask_$u2603$u2603")), + List(TypeTree())), + List(Ident(TermName("bar")))), + TypeTree()), + TypeTree().setOriginal( + Annotated( + Apply(Select(New(Ident(TypeName("unchecked"))), termNames.CONSTRUCTOR), List()), + Typed(Apply(TypeApply(Select(Ident(sbt.std.InputWrapper), + TermName("wrapInitTask_$u2603$u2603")), + List(TypeTree())), + List(Ident(TermName("bar")))), + TypeTree()) + )) + ) + )*/ + + /* Block( + List( + ValDef( + Modifiers(), + TermName("anon"), + TypeTree(), + Function( + List(), + Apply( + Select( + Typed( + Apply(TypeApply(Select(Ident(sbt.std.InputWrapper), + TermName("wrapInitTask_$u2603$u2603")), + List(TypeTree())), + List(Ident(TermName("fooNeg")))), + TypeTree() + ), + TermName("$plus") + ), + List(Literal(Constant(""))) + ) + ) + )), + If( + Ident(TermName("condition")), + Apply(Select(Ident(TermName("anon")), TermName("apply")), List()), + Apply(Select(Ident(TermName("anon")), TermName("apply")), List()) + ) + )*/ } diff --git a/main-settings/src/main/scala/sbt/std/TaskMacro.scala b/main-settings/src/main/scala/sbt/std/TaskMacro.scala index 5b6adfecf..690e7bfd7 100644 --- a/main-settings/src/main/scala/sbt/std/TaskMacro.scala +++ b/main-settings/src/main/scala/sbt/std/TaskMacro.scala @@ -99,7 +99,7 @@ object TaskMacro { def taskDynMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)( t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[Task[T]]] = - Instance.contImpl[T, Id](c, FullInstance, FullConvert, MixedBuilder, EmptyLinter)( + Instance.contImpl[T, Id](c, FullInstance, FullConvert, MixedBuilder, TaskDynLinterDSL)( Right(t), Instance.idTransform[c.type]) @@ -503,13 +503,13 @@ object TaskMacro { object PlainTaskMacro { def task[T](t: T): Task[T] = macro taskImpl[T] def taskImpl[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[Task[T]] = - Instance.contImpl[T, Id](c, TaskInstance, TaskConvert, MixedBuilder, TaskLinterDSL)( + Instance.contImpl[T, Id](c, TaskInstance, TaskConvert, MixedBuilder, OnlyTaskLinterDSL)( Left(t), Instance.idTransform[c.type]) def taskDyn[T](t: Task[T]): Task[T] = macro taskDynImpl[T] def taskDynImpl[T: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[Task[T]]): c.Expr[Task[T]] = - Instance.contImpl[T, Id](c, TaskInstance, TaskConvert, MixedBuilder, LinterDSL.Empty)( + Instance.contImpl[T, Id](c, TaskInstance, TaskConvert, MixedBuilder, OnlyTaskDynLinterDSL)( Right(t), Instance.idTransform[c.type]) } diff --git a/main-settings/src/test/scala/sbt/std/TaskPosSpec.scala b/main-settings/src/test/scala/sbt/std/TaskPosSpec.scala index 9cf91e080..1d5406f0c 100644 --- a/main-settings/src/test/scala/sbt/std/TaskPosSpec.scala +++ b/main-settings/src/test/scala/sbt/std/TaskPosSpec.scala @@ -38,4 +38,32 @@ class TaskPosSpec { else bar.value: @unchecked } } + + locally { + // This is fix 1 for appearance of tasks inside anons + import sbt._ + import sbt.Def._ + val foo = taskKey[String]("") + var condition = true + val baz = Def.task[String] { + val fooResult = foo.value + val anon = () => fooResult + " " + if (condition) anon() + else "" + } + } + + locally { + // This is fix 2 for appearance of tasks inside anons + import sbt._ + import sbt.Def._ + val foo = taskKey[String]("") + var condition = true + val baz = Def.taskDyn[String] { + val anon1 = (value: String) => value + " " + if (condition) { + Def.task(anon1(foo.value)) + } else Def.task("") + } + } } diff --git a/main-settings/src/test/scala/sbt/std/neg/TaskNegSpec.scala b/main-settings/src/test/scala/sbt/std/neg/TaskNegSpec.scala index 1d238af45..d63d8cb5b 100644 --- a/main-settings/src/test/scala/sbt/std/neg/TaskNegSpec.scala +++ b/main-settings/src/test/scala/sbt/std/neg/TaskNegSpec.scala @@ -11,12 +11,15 @@ class TaskNegSpec extends FunSuite { baseCompileOptions: String = s"-cp $toolboxClasspath")(code: String) = { val errorMessage = intercept[ToolBoxError] { eval(code, s"$compileOptions $baseCompileOptions") + println("SUCCESS") }.getMessage + println(errorMessage) val userMessage = s""" |FOUND: $errorMessage |EXPECTED: $errorSnippet """.stripMargin + println(userMessage) assert(errorMessage.contains(errorSnippet), userMessage) } @@ -40,7 +43,28 @@ class TaskNegSpec extends FunSuite { } } - test("Fail on task invocation inside inside if of task returned by dynamic task") { + test("Fail on task invocation inside `if` if it is used inside a regular task") { + val fooNegError = TaskLinterDSLFeedback.useOfValueInsideIfExpression("fooNeg") + val barNegError = TaskLinterDSLFeedback.useOfValueInsideIfExpression("barNeg") + expectError(List(fooNegError, barNegError).mkString("\n")) { + """ + |import sbt._ + |import sbt.Def._ + | + |val fooNeg = taskKey[String]("") + |val barNeg = taskKey[String]("") + |var condition = true + |def bi(s: String) = s + " " + | + |val bazNeg = Def.task[String] { + | if (condition) "" + fooNeg.value + | else bi(barNeg.value) + |} + """.stripMargin + } + } + + test("Fail on task invocation inside inside `if` of task returned by dynamic task") { expectError(TaskLinterDSLFeedback.useOfValueInsideIfExpression("fooNeg")) { """ |import sbt._ @@ -63,7 +87,7 @@ class TaskNegSpec extends FunSuite { } } - test("Fail on task invocation inside inside else of task returned by dynamic task") { + test("Fail on task invocation inside else of task returned by dynamic task") { expectError(TaskLinterDSLFeedback.useOfValueInsideIfExpression("barNeg")) { """ |import sbt._ @@ -84,4 +108,64 @@ class TaskNegSpec extends FunSuite { """.stripMargin } } + + test("Fail on task invocation inside anonymous function returned by regular task") { + val fooNegError = TaskLinterDSLFeedback.useOfValueInsideAnon("fooNeg") + expectError(fooNegError) { + """ + |import sbt._ + |import sbt.Def._ + | + |val fooNeg = taskKey[String]("") + |val barNeg = taskKey[String]("") + |var condition = true + | + |val bazNeg = Def.task[String] { + | val anon = () => fooNeg.value + | if (condition) anon() + | else anon() + |} + """.stripMargin + } + } + + test("Fail on task invocation inside complex anonymous function returned by regular task") { + val fooNegError = TaskLinterDSLFeedback.useOfValueInsideAnon("fooNeg") + expectError(fooNegError) { + """ + |import sbt._ + |import sbt.Def._ + | + |val fooNeg = taskKey[String]("") + |var condition = true + | + |val bazNeg = Def.task[String] { + | val anon = () => fooNeg.value + "" + | if (condition) anon() + | else anon() + |} + """.stripMargin + } + } + + test("Fail on task invocation inside anonymous function returned by dynamic task") { + val fooNegError = TaskLinterDSLFeedback.useOfValueInsideAnon("fooNeg") + expectError(fooNegError) { + """ + |import sbt._ + |import sbt.Def._ + | + |val fooNeg = taskKey[String]("") + |val barNeg = taskKey[String]("") + |var condition = true + | + |val bazNeg = Def.taskDyn[String] { + | if (condition) { + | val anon = () => fooNeg.value + | Def.task(anon()) + | } else Def.task("") + |} + """.stripMargin + } + } } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index ff0f1879a..ce673117c 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -337,7 +337,8 @@ object Defaults extends BuildCommon { val srcs = unmanagedSources.value val f = (includeFilter in unmanagedSources).value val excl = (excludeFilter in unmanagedSources).value - if (sourcesInBase.value) (srcs +++ baseDirectory.value * (f -- excl)).get else srcs + val baseDir = baseDirectory.value + if (sourcesInBase.value) (srcs +++ baseDir * (f -- excl)).get else srcs } ) @@ -405,11 +406,12 @@ object Defaults extends BuildCommon { bootIvyConfiguration.value, scalaCompilerBridgeSource.value )(appConfiguration.value, streams.value.log) + val classLoaderCache = state.value.classLoaderCache if (java.lang.Boolean.getBoolean("sbt.disable.interface.classloader.cache")) compilers else { compilers.withScalac( compilers.scalac match { - case x: AnalyzingCompiler => x.withClassLoaderCache(state.value.classLoaderCache) + case x: AnalyzingCompiler => x.withClassLoaderCache(classLoaderCache) case x => x } ) @@ -426,8 +428,9 @@ object Defaults extends BuildCommon { compileAnalysisFilename := { // Here, if the user wants cross-scala-versioning, we also append it // to the analysis cache, so we keep the scala versions separated. + val binVersion = scalaBinaryVersion.value val extra = - if (crossPaths.value) s"_${scalaBinaryVersion.value}" + if (crossPaths.value) s"_$binVersion" else "" s"inc_compile${extra}.zip" }, @@ -553,11 +556,11 @@ object Defaults extends BuildCommon { def file(id: String) = files(id).headOption getOrElse sys.error(s"Missing ${id}.jar") val allFiles = toolReport.modules.flatMap(_.artifacts.map(_._2)) val libraryJar = file(ScalaArtifacts.LibraryID) + val binVersion = scalaBinaryVersion.value val compilerJar = if (ScalaInstance.isDotty(scalaVersion.value)) - file(ScalaArtifacts.dottyID(scalaBinaryVersion.value)) - else - file(ScalaArtifacts.CompilerID) + file(ScalaArtifacts.dottyID(binVersion)) + else file(ScalaArtifacts.CompilerID) val otherJars = allFiles.filterNot(x => x == libraryJar || x == compilerJar) new ScalaInstance(scalaVersion.value, makeClassLoader(state.value)(libraryJar :: compilerJar :: otherJars.toList), @@ -590,9 +593,11 @@ object Defaults extends BuildCommon { data(fullClasspath.value), scalaInstance.value, IO.createUniqueDirectory(taskTemporaryDirectory.value)), - loadedTestFrameworks := testFrameworks.value - .flatMap(f => f.create(testLoader.value, streams.value.log).map(x => (f, x)).toIterable) - .toMap, + loadedTestFrameworks := { + val loader = testLoader.value + val log = streams.value.log + testFrameworks.value.flatMap(f => f.create(loader, log).map(x => (f, x)).toIterable).toMap + }, definedTests := detectTests.value, definedTestNames := (definedTests map (_.map(_.name).distinct) storeAs definedTestNames triggeredBy compile).value, testFilter in testQuick := testQuickFilter.value, @@ -1193,8 +1198,9 @@ object Defaults extends BuildCommon { inTask(key)( Seq( apiMappings ++= { - if (autoAPIMappings.value) - APIMappings.extract(dependencyClasspath.value, streams.value.log).toMap + val dependencyCp = dependencyClasspath.value + val log = streams.value.log + if (autoAPIMappings.value) APIMappings.extract(dependencyCp, log).toMap else Map.empty[File, URL] }, fileInputOptions := Seq("-doc-root-content", "-diagrams-dot-path"), @@ -1393,9 +1399,9 @@ object Defaults extends BuildCommon { PomExtraDependencyAttributes.ScalaVersionKey -> scalaV) .withCrossVersion(Disabled()) - def discoverSbtPluginNames: Initialize[Task[PluginDiscovery.DiscoveredNames]] = Def.task { - if (sbtPlugin.value) PluginDiscovery.discoverSourceAll(compile.value) - else PluginDiscovery.emptyDiscoveredNames + def discoverSbtPluginNames: Initialize[Task[PluginDiscovery.DiscoveredNames]] = Def.taskDyn { + if (sbtPlugin.value) Def.task(PluginDiscovery.discoverSourceAll(compile.value)) + else Def.task(PluginDiscovery.emptyDiscoveredNames) } def copyResourcesTask = @@ -1686,7 +1692,8 @@ object Classpaths { bootResolvers.value match { case Some(repos) if overrideBuildResolvers.value => proj +: repos case _ => - val base = if (sbtPlugin.value) sbtResolver.value +: sbtPluginReleases +: rs else rs + val sbtResolverValue = sbtResolver.value + val base = if (sbtPlugin.value) sbtResolverValue +: sbtPluginReleases +: rs else rs proj +: base } }).value, @@ -1769,14 +1776,18 @@ object Classpaths { else "release", logging = ivyLoggingLevel.value), deliverConfiguration := deliverLocalConfiguration.value, - publishConfiguration := publishConfig( - packagedArtifacts.in(publish).value, - if (publishMavenStyle.value) None else Some(deliver.value), - resolverName = getPublishTo(publishTo.value).name, - checksums = checksums.in(publish).value, - logging = ivyLoggingLevel.value, - overwrite = isSnapshot.value - ), + publishConfiguration := { + // TODO(jvican): I think this is a bug. + val delivered = deliver.value + publishConfig( + packagedArtifacts.in(publish).value, + if (publishMavenStyle.value) None else Some(delivered), + resolverName = getPublishTo(publishTo.value).name, + checksums = checksums.in(publish).value, + logging = ivyLoggingLevel.value, + overwrite = isSnapshot.value + ) + }, publishLocalConfiguration := publishConfig( packagedArtifacts.in(publishLocal).value, Some(deliverLocal.value), @@ -1795,8 +1806,11 @@ object Classpaths { ivySbt := ivySbt0.value, ivyModule := { val is = ivySbt.value; new is.Module(moduleSettings.value) }, transitiveUpdate := transitiveUpdateTask.value, - updateCacheName := "update_cache" + (if (crossPaths.value) s"_${scalaBinaryVersion.value}" - else ""), + updateCacheName := { + val binVersion = scalaBinaryVersion.value + val suffix = if (crossPaths.value) s"_$binVersion" else "" + s"update_cache$suffix" + }, evictionWarningOptions in update := EvictionWarningOptions.default, dependencyPositions := dependencyPositionsTask.value, unresolvedWarningConfiguration in update := UnresolvedWarningConfiguration( @@ -1839,17 +1853,19 @@ object Classpaths { val out = is.withIvy(s.log)(_.getSettings.getDefaultIvyUserDir) val uwConfig = (unresolvedWarningConfiguration in update).value val depDir = dependencyCacheDirectory.value + val ivy = ivyScala.value + val st = state.value withExcludes(out, mod.classifiers, lock(app)) { excludes => IvyActions.updateClassifiers( is, GetClassifiersConfiguration(mod, excludes, c.withArtifactFilter(c.artifactFilter.invert), - ivyScala.value, + ivy, srcTypes, docTypes), uwConfig, - LogicalClock(state.value.hashCode), + LogicalClock(st.hashCode), Some(depDir), Vector.empty, s.log @@ -1871,12 +1887,13 @@ object Classpaths { val pluginAdjust = if (sbtPlugin.value) dependency.withConfigurations(Some(Provided.name)) +: base else base + val sbtOrg = scalaOrganization.value + val version = scalaVersion.value if (scalaHome.value.isDefined || ivyScala.value.isEmpty || !managedScalaInstance.value) pluginAdjust else { - val version = scalaVersion.value val isDotty = ScalaInstance.isDotty(version) - ScalaArtifacts.toolDependencies(scalaOrganization.value, version, isDotty) ++ pluginAdjust + ScalaArtifacts.toolDependencies(sbtOrg, version, isDotty) ++ pluginAdjust } } ) @@ -1979,11 +1996,14 @@ object Classpaths { val app = appConfiguration.value val srcTypes = sourceArtifactTypes.value val docTypes = docArtifactTypes.value - val out = is.withIvy(s.log)(_.getSettings.getDefaultIvyUserDir) + val log = s.log + val out = is.withIvy(log)(_.getSettings.getDefaultIvyUserDir) val uwConfig = (unresolvedWarningConfiguration in update).value val depDir = dependencyCacheDirectory.value + val ivy = ivyScala.value + val st = state.value withExcludes(out, mod.classifiers, lock(app)) { excludes => - val noExplicitCheck = ivyScala.value.map(_.withCheckExplicit(false)) + val noExplicitCheck = ivy.map(_.withCheckExplicit(false)) IvyActions.transitiveScratch( is, "sbt", @@ -1994,9 +2014,9 @@ object Classpaths { srcTypes, docTypes), uwConfig, - LogicalClock(state.value.hashCode), + LogicalClock(st.hashCode), Some(depDir), - s.log + log ) } } tag (Tags.Update, Tags.Network)).value diff --git a/sbt/src/sbt-test/actions/external-doc/build.sbt b/sbt/src/sbt-test/actions/external-doc/build.sbt index 1d7e24672..1cabf1333 100644 --- a/sbt/src/sbt-test/actions/external-doc/build.sbt +++ b/sbt/src/sbt-test/actions/external-doc/build.sbt @@ -29,12 +29,14 @@ def addDep(projectName: String) = val checkApiMappings = taskKey[Unit]("Verifies that the API mappings are collected as expected.") def expectedMappings = Def.task { + val version = scalaVersion.value + val binVersion = scalaBinaryVersion.value val ms = update.value.configuration(Compile.name).get.modules.flatMap { mod => mod.artifacts.flatMap { case (a,f) => - val n = a.name.stripSuffix("_" + scalaBinaryVersion.value) + val n = a.name.stripSuffix("_" + binVersion) n match { case "a" | "b" | "c" => (f, apiBase(n)) :: Nil - case "scala-library" => (f, scalaLibraryBase(scalaVersion.value)) :: Nil + case "scala-library" => (f, scalaLibraryBase(version)) :: Nil case _ => Nil } } diff --git a/sbt/src/sbt-test/apiinfo/unstable-existential-names/build.sbt b/sbt/src/sbt-test/apiinfo/unstable-existential-names/build.sbt index ae689f73d..b611e2c11 100644 --- a/sbt/src/sbt-test/apiinfo/unstable-existential-names/build.sbt +++ b/sbt/src/sbt-test/apiinfo/unstable-existential-names/build.sbt @@ -4,10 +4,11 @@ import complete.DefaultParsers._ // Reset compiler iterations, necessary because tests run in batch mode val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") recordPreviousIterations := { + val log = streams.value.log CompileState.previousIterations = { val previousAnalysis = (previousCompile in Compile).value.analysis if (previousAnalysis.isEmpty) { - streams.value.log.info("No previous analysis detected") + log.info("No previous analysis detected") 0 } else { previousAnalysis.get match { diff --git a/sbt/src/sbt-test/compiler-project/inc-pickled-existential/build.sbt b/sbt/src/sbt-test/compiler-project/inc-pickled-existential/build.sbt index c1e5d8839..efe22f689 100644 --- a/sbt/src/sbt-test/compiler-project/inc-pickled-existential/build.sbt +++ b/sbt/src/sbt-test/compiler-project/inc-pickled-existential/build.sbt @@ -6,10 +6,11 @@ logLevel := Level.Debug // Reset compiler iterations, necessary because tests run in batch mode val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") recordPreviousIterations := { + val log = streams.value.log CompileState.previousIterations = { val previousAnalysis = (previousCompile in Compile).value.analysis if (previousAnalysis.isEmpty) { - streams.value.log.info("No previous analysis detected") + log.info("No previous analysis detected") 0 } else { previousAnalysis.get match { diff --git a/sbt/src/sbt-test/compiler-project/inc-pickled-refinement/build.sbt b/sbt/src/sbt-test/compiler-project/inc-pickled-refinement/build.sbt index ae689f73d..b611e2c11 100644 --- a/sbt/src/sbt-test/compiler-project/inc-pickled-refinement/build.sbt +++ b/sbt/src/sbt-test/compiler-project/inc-pickled-refinement/build.sbt @@ -4,10 +4,11 @@ import complete.DefaultParsers._ // Reset compiler iterations, necessary because tests run in batch mode val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") recordPreviousIterations := { + val log = streams.value.log CompileState.previousIterations = { val previousAnalysis = (previousCompile in Compile).value.analysis if (previousAnalysis.isEmpty) { - streams.value.log.info("No previous analysis detected") + log.info("No previous analysis detected") 0 } else { previousAnalysis.get match { diff --git a/sbt/src/sbt-test/dependency-management/pom-parent-pom/build.sbt b/sbt/src/sbt-test/dependency-management/pom-parent-pom/build.sbt index 76548462d..458581d12 100644 --- a/sbt/src/sbt-test/dependency-management/pom-parent-pom/build.sbt +++ b/sbt/src/sbt-test/dependency-management/pom-parent-pom/build.sbt @@ -10,6 +10,7 @@ lazy val root = (project in file(".")). version := "1.0-SNAPSHOT", autoScalaLibrary := false, checkIvyXml := { + val resolverConverter = updateOptions.value.resolverConverter ivySbt.value.withIvy(streams.value.log) { ivy => val cacheDir = ivy.getSettings.getDefaultRepositoryCacheBasedir val xmlFile = @@ -18,7 +19,7 @@ lazy val root = (project in file(".")). if(lines.isEmpty) sys.error(s"Unable to read $xmlFile, could not resolve geronimo...") // Note: We do not do this if the maven plugin is enabled, because there is no rewrite of ivy.xml, extra attributes // are handled in a different mechanism. This is a hacky mechanism to detect that. - val isMavenResolver = updateOptions.value.resolverConverter != PartialFunction.empty + val isMavenResolver = resolverConverter != PartialFunction.empty if(!isMavenResolver) assert(lines contains "xmlns:e", s"Failed to appropriately modify ivy.xml file for sbt extra attributes!\n$lines") val xmlFile2 = cacheDir / "com.example" / "example-child" / "ivy-1.0-SNAPSHOT.xml" diff --git a/sbt/src/sbt-test/dependency-management/snapshot-resolution/build.sbt b/sbt/src/sbt-test/dependency-management/snapshot-resolution/build.sbt index 90b3c2812..31db52a0f 100644 --- a/sbt/src/sbt-test/dependency-management/snapshot-resolution/build.sbt +++ b/sbt/src/sbt-test/dependency-management/snapshot-resolution/build.sbt @@ -42,8 +42,9 @@ lazy val dependent = project. ) TaskKey[Unit]("dumpResolvers") := { - streams.value.log.info(s" -- dependent/fullResolvers -- ") + val log = streams.value.log + log.info(s" -- dependent/fullResolvers -- ") (fullResolvers in dependent).value foreach { r => - streams.value.log.info(s" * ${r}") + log.info(s" * ${r}") } } diff --git a/sbt/src/sbt-test/source-dependencies/abstract-type-override/build.sbt b/sbt/src/sbt-test/source-dependencies/abstract-type-override/build.sbt index ae689f73d..b611e2c11 100644 --- a/sbt/src/sbt-test/source-dependencies/abstract-type-override/build.sbt +++ b/sbt/src/sbt-test/source-dependencies/abstract-type-override/build.sbt @@ -4,10 +4,11 @@ import complete.DefaultParsers._ // Reset compiler iterations, necessary because tests run in batch mode val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") recordPreviousIterations := { + val log = streams.value.log CompileState.previousIterations = { val previousAnalysis = (previousCompile in Compile).value.analysis if (previousAnalysis.isEmpty) { - streams.value.log.info("No previous analysis detected") + log.info("No previous analysis detected") 0 } else { previousAnalysis.get match { diff --git a/sbt/src/sbt-test/source-dependencies/canon/build.sbt b/sbt/src/sbt-test/source-dependencies/canon/build.sbt index ae689f73d..b611e2c11 100644 --- a/sbt/src/sbt-test/source-dependencies/canon/build.sbt +++ b/sbt/src/sbt-test/source-dependencies/canon/build.sbt @@ -4,10 +4,11 @@ import complete.DefaultParsers._ // Reset compiler iterations, necessary because tests run in batch mode val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") recordPreviousIterations := { + val log = streams.value.log CompileState.previousIterations = { val previousAnalysis = (previousCompile in Compile).value.analysis if (previousAnalysis.isEmpty) { - streams.value.log.info("No previous analysis detected") + log.info("No previous analysis detected") 0 } else { previousAnalysis.get match { diff --git a/sbt/src/sbt-test/source-dependencies/ext/build.sbt b/sbt/src/sbt-test/source-dependencies/ext/build.sbt index ae689f73d..b611e2c11 100644 --- a/sbt/src/sbt-test/source-dependencies/ext/build.sbt +++ b/sbt/src/sbt-test/source-dependencies/ext/build.sbt @@ -4,10 +4,11 @@ import complete.DefaultParsers._ // Reset compiler iterations, necessary because tests run in batch mode val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") recordPreviousIterations := { + val log = streams.value.log CompileState.previousIterations = { val previousAnalysis = (previousCompile in Compile).value.analysis if (previousAnalysis.isEmpty) { - streams.value.log.info("No previous analysis detected") + log.info("No previous analysis detected") 0 } else { previousAnalysis.get match { diff --git a/sbt/src/sbt-test/source-dependencies/less-inter-inv-java/build.sbt b/sbt/src/sbt-test/source-dependencies/less-inter-inv-java/build.sbt index ae689f73d..b611e2c11 100644 --- a/sbt/src/sbt-test/source-dependencies/less-inter-inv-java/build.sbt +++ b/sbt/src/sbt-test/source-dependencies/less-inter-inv-java/build.sbt @@ -4,10 +4,11 @@ import complete.DefaultParsers._ // Reset compiler iterations, necessary because tests run in batch mode val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") recordPreviousIterations := { + val log = streams.value.log CompileState.previousIterations = { val previousAnalysis = (previousCompile in Compile).value.analysis if (previousAnalysis.isEmpty) { - streams.value.log.info("No previous analysis detected") + log.info("No previous analysis detected") 0 } else { previousAnalysis.get match { diff --git a/sbt/src/sbt-test/source-dependencies/less-inter-inv/build.sbt b/sbt/src/sbt-test/source-dependencies/less-inter-inv/build.sbt index ae689f73d..b611e2c11 100644 --- a/sbt/src/sbt-test/source-dependencies/less-inter-inv/build.sbt +++ b/sbt/src/sbt-test/source-dependencies/less-inter-inv/build.sbt @@ -4,10 +4,11 @@ import complete.DefaultParsers._ // Reset compiler iterations, necessary because tests run in batch mode val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") recordPreviousIterations := { + val log = streams.value.log CompileState.previousIterations = { val previousAnalysis = (previousCompile in Compile).value.analysis if (previousAnalysis.isEmpty) { - streams.value.log.info("No previous analysis detected") + log.info("No previous analysis detected") 0 } else { previousAnalysis.get match { diff --git a/sbt/src/sbt-test/source-dependencies/restore-classes/build.sbt b/sbt/src/sbt-test/source-dependencies/restore-classes/build.sbt index ca1fd1b5d..b0028823a 100644 --- a/sbt/src/sbt-test/source-dependencies/restore-classes/build.sbt +++ b/sbt/src/sbt-test/source-dependencies/restore-classes/build.sbt @@ -6,10 +6,11 @@ crossTarget in Compile := target.value // Reset compiler iterations, necessary because tests run in batch mode val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") recordPreviousIterations := { + val log = streams.value.log CompileState.previousIterations = { val previousAnalysis = (previousCompile in Compile).value.analysis if (previousAnalysis.isEmpty) { - streams.value.log.info("No previous analysis detected") + log.info("No previous analysis detected") 0 } else { previousAnalysis.get match { diff --git a/sbt/src/sbt-test/tests/fork-parallel/build.sbt b/sbt/src/sbt-test/tests/fork-parallel/build.sbt index d46339550..e28535d49 100644 --- a/sbt/src/sbt-test/tests/fork-parallel/build.sbt +++ b/sbt/src/sbt-test/tests/fork-parallel/build.sbt @@ -10,8 +10,9 @@ lazy val root = (project in file(".")). fork in Test := true, check := { val nbProc = java.lang.Runtime.getRuntime().availableProcessors() + val log = streams.value.log if( nbProc < 4 ) { - streams.value.log.warn("With fewer than 4 processors this test is meaningless") + log.warn("With fewer than 4 processors this test is meaningless") } else { // we've got at least 4 processors, we'll check the upper end but also 3 and 4 as the upper might not // be reached if the system is under heavy load. diff --git a/sbt/src/sbt-test/tests/fork-test-group-parallel/build.sbt b/sbt/src/sbt-test/tests/fork-test-group-parallel/build.sbt index 4c8f89643..761d141ee 100644 --- a/sbt/src/sbt-test/tests/fork-test-group-parallel/build.sbt +++ b/sbt/src/sbt-test/tests/fork-test-group-parallel/build.sbt @@ -2,16 +2,24 @@ scalaVersion in ThisBuild := "2.11.8" concurrentRestrictions in Global := Seq(Tags.limitAll(4)) libraryDependencies += "org.specs2" %% "specs2-core" % "3.8.4" % Test inConfig(Test)(Seq( - testGrouping := definedTests.value.map { test => new Tests.Group(test.name, Seq(test), Tests.SubProcess( - ForkOptions( - javaHome = javaHome.value, - outputStrategy = outputStrategy.value, - bootJars = Vector(), - workingDirectory = Some(baseDirectory.value), - runJVMOptions = javaOptions.value.toVector, - connectInput = connectInput.value, - envVars = envVars.value - ) - ))}, + testGrouping := { + val home = javaHome.value + val strategy = outputStrategy.value + val baseDir = baseDirectory.value + val options = javaOptions.value + val connect = connectInput.value + val vars = envVars.value + definedTests.value.map { test => new Tests.Group(test.name, Seq(test), Tests.SubProcess( + ForkOptions( + javaHome = home, + outputStrategy = strategy, + bootJars = Vector(), + workingDirectory = Some(baseDir), + runJVMOptions = options.toVector, + connectInput = connect, + envVars = vars + ) + ))} + }, TaskKey[Unit]("test-failure") := test.failure.value )) diff --git a/sbt/src/sbt-test/tests/setup-cleanup/changes/setup.sbt b/sbt/src/sbt-test/tests/setup-cleanup/changes/setup.sbt index d9abc7285..5b3634567 100644 --- a/sbt/src/sbt-test/tests/setup-cleanup/changes/setup.sbt +++ b/sbt/src/sbt-test/tests/setup-cleanup/changes/setup.sbt @@ -1,7 +1,9 @@ -testOptions in Test += +testOptions in Test += { + val baseDir = baseDirectory.value Tests.Setup { () => - IO.touch(baseDirectory.value / "setup") + IO.touch(baseDir / "setup") } +} testOptions in Test += { val t = baseDirectory.value / "tested" @@ -11,4 +13,4 @@ testOptions in Test += { IO.delete(t) IO.touch(c) } -} \ No newline at end of file +}