diff --git a/compile/persist/src/main/scala/sbt/inc/Base64.scala b/compile/persist/src/main/scala/sbt/inc/Base64.scala new file mode 100644 index 000000000..21001a2a4 --- /dev/null +++ b/compile/persist/src/main/scala/sbt/inc/Base64.scala @@ -0,0 +1,61 @@ +package sbt +package inc + +import java.lang.reflect.InvocationTargetException +import javax.xml.bind.DatatypeConverter + +private[sbt] trait Base64 { + def encode(bytes: Array[Byte]): String + + def decode(string: String): Array[Byte] +} + +private[sbt] object Base64 { + lazy val factory: () => Base64 = { + try { + new Java678Encoder().encode(Array[Byte]()) + () => new Java678Encoder() + } catch { + case _: LinkageError => + () => new Java89Encoder + } + } +} + +private[sbt] class Java678Encoder extends Base64 { + def encode(bytes: Array[Byte]): String = DatatypeConverter.printBase64Binary(bytes) + + def decode(string: String): Array[Byte] = DatatypeConverter.parseBase64Binary(string) +} + +private[sbt] object Java89Encoder { + + import scala.runtime.ScalaRunTime.ensureAccessible + + private val Base64_class = Class.forName("java.util.Base64") + private val Base64_getEncoder = ensureAccessible(Base64_class.getMethod("getEncoder")) + private val Base64_getDecoder = ensureAccessible(Base64_class.getMethod("getEncoder")) + private val Base64_Encoder_class = Class.forName("java.util.Base64$Encoder") + private val Base64_Decoder_class = Class.forName("java.util.Base64$Decoder") + private val Base64_Encoder_encodeToString = ensureAccessible(Base64_Encoder_class.getMethod("encodeToString", classOf[Array[Byte]])) + private val Base64_Decoder_decode = ensureAccessible(Base64_Decoder_class.getMethod("decode", classOf[String])) +} + +private[sbt] class Java89Encoder extends Base64 { + + import Java89Encoder._ + + def encode(bytes: Array[Byte]): String = try { + val encoder = Base64_getEncoder.invoke(null) + Base64_Encoder_encodeToString.invoke(encoder, bytes).asInstanceOf[String] + } catch { + case ex: InvocationTargetException => throw ex.getCause + } + + def decode(string: String): Array[Byte] = try { + val decoder = Base64_getDecoder.invoke(null) + Base64_Decoder_decode.invoke(decoder, string).asInstanceOf[Array[Byte]] + } catch { + case ex: InvocationTargetException => throw ex.getCause + } +} \ No newline at end of file diff --git a/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala b/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala index 74221a9c6..a03edcce4 100644 --- a/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala +++ b/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala @@ -5,7 +5,6 @@ import java.io._ import sbt.{ CompileSetup, Relation } import xsbti.api.{ Compilation, Source } import xsbti.compile.{ MultipleOutput, SingleOutput } -import javax.xml.bind.DatatypeConverter // Very simple timer for timing repeated code sections. // TODO: Temporary. Remove once we've milked all available performance gains. @@ -328,11 +327,11 @@ object TextAnalysisFormat { val out = new sbinary.JavaOutput(baos) FormatTimer.aggregate("sbinary write") { try { fmt.writes(out, o) } finally { baos.close() } } val bytes = FormatTimer.aggregate("byte copy") { baos.toByteArray } - FormatTimer.aggregate("bytes -> base64") { DatatypeConverter.printBase64Binary(bytes) } + FormatTimer.aggregate("bytes -> base64") { Base64.factory().encode(bytes) } } def stringToObj[T](s: String)(implicit fmt: sbinary.Format[T]) = { - val bytes = FormatTimer.aggregate("base64 -> bytes") { DatatypeConverter.parseBase64Binary(s) } + val bytes = FormatTimer.aggregate("base64 -> bytes") { Base64.factory().decode(s) } val in = new sbinary.JavaInput(new ByteArrayInputStream(bytes)) FormatTimer.aggregate("sbinary read") { fmt.reads(in) } } diff --git a/compile/src/main/scala/sbt/compiler/CompilerArguments.scala b/compile/src/main/scala/sbt/compiler/CompilerArguments.scala index 295bae5bb..df279bb37 100644 --- a/compile/src/main/scala/sbt/compiler/CompilerArguments.scala +++ b/compile/src/main/scala/sbt/compiler/CompilerArguments.scala @@ -63,7 +63,7 @@ final class CompilerArguments(scalaInstance: xsbti.compile.ScalaInstance, cp: xs def bootClasspathFor(classpath: Seq[File]) = bootClasspath(hasLibrary(classpath)) import Path._ - def extClasspath: Seq[File] = (IO.parseClasspath(System.getProperty("java.ext.dirs")) * "*.jar").get + def extClasspath: Seq[File] = (IO.parseClasspath(System.getProperty("java.ext.dirs", "")) * "*.jar").get } object CompilerArguments { val BootClasspathOption = "-bootclasspath" diff --git a/main/settings/src/main/scala/sbt/Append.scala b/main/settings/src/main/scala/sbt/Append.scala index fafd65f5c..476f12738 100644 --- a/main/settings/src/main/scala/sbt/Append.scala +++ b/main/settings/src/main/scala/sbt/Append.scala @@ -1,8 +1,9 @@ package sbt import java.io.File -import Def.Classpath +import Def.{ Classpath, Initialize } import scala.annotation.implicitNotFound +import reflect.internal.annotations.compileTimeOnly object Append { @implicitNotFound(msg = "No implicit for Append.Value[${A}, ${B}] found,\n so ${B} cannot be appended to ${A}") @@ -23,6 +24,14 @@ object Append { def appendValues(a: Seq[T], b: Seq[V]): Seq[T] = a ++ (b map { x => (x: T) }) def appendValue(a: Seq[T], b: V): Seq[T] = a :+ (b: T) } + @compileTimeOnly("This can be used in += only.") + implicit def appendTaskValueSeq[T, V <: T]: Value[Seq[Task[T]], Initialize[Task[V]]] = new Value[Seq[Task[T]], Initialize[Task[V]]] { + def appendValue(a: Seq[Task[T]], b: Initialize[Task[V]]): Seq[Task[T]] = ??? + } + @compileTimeOnly("This can be used in += only.") + implicit def appendTaskKeySeq[T, V <: T]: Value[Seq[Task[T]], TaskKey[V]] = new Value[Seq[Task[T]], TaskKey[V]] { + def appendValue(a: Seq[Task[T]], b: TaskKey[V]): Seq[Task[T]] = ??? + } implicit def appendList[T, V <: T]: Sequence[List[T], List[V], V] = new Sequence[List[T], List[V], V] { def appendValues(a: List[T], b: List[V]): List[T] = a ::: b def appendValue(a: List[T], b: V): List[T] = a :+ b diff --git a/main/settings/src/main/scala/sbt/std/TaskMacro.scala b/main/settings/src/main/scala/sbt/std/TaskMacro.scala index 76392716b..c15d02f60 100644 --- a/main/settings/src/main/scala/sbt/std/TaskMacro.scala +++ b/main/settings/src/main/scala/sbt/std/TaskMacro.scala @@ -168,9 +168,24 @@ object TaskMacro { /** Implementation of += macro for settings. */ def settingAppend1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: Context)(v: c.Expr[U])(a: c.Expr[Append.Value[T, U]]): c.Expr[Setting[T]] = { - val init = SettingMacro.settingMacroImpl[U](c)(v) - val append = appendMacroImpl(c)(init.tree, a.tree)(Append1InitName) - c.Expr[Setting[T]](append) + import c.universe._ + val util = ContextUtil[c.type](c) + val ttpe = c.weakTypeOf[T] + val typeArgs = util.typeArgs(ttpe) + v.tree.tpe match { + // To allow Initialize[Task[A]] in the position of += RHS, we're going to call "taskValue" automatically. + case tpe if typeArgs.nonEmpty && (tpe weak_<:< c.weakTypeOf[Initialize[_]]) => + c.macroApplication match { + case Apply(Apply(TypeApply(Select(preT, nmeT), targs), _), _) => + val tree = Apply(TypeApply(Select(preT, newTermName("+=").encodedName), TypeTree(typeArgs.head) :: Nil), Select(v.tree, newTermName("taskValue").encodedName) :: Nil) + c.Expr[Setting[T]](tree) + case x => ContextUtil.unexpectedTree(x) + } + case _ => + val init = SettingMacro.settingMacroImpl[U](c)(v) + val append = appendMacroImpl(c)(init.tree, a.tree)(Append1InitName) + c.Expr[Setting[T]](append) + } } /** Implementation of ++= macro for tasks. */ def taskAppendNImpl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: Context)(vs: c.Expr[U])(a: c.Expr[Append.Values[T, U]]): c.Expr[Setting[Task[T]]] = @@ -186,7 +201,6 @@ object TaskMacro { val append = appendMacroImpl(c)(init.tree, a.tree)(AppendNInitName) c.Expr[Setting[T]](append) } - /** Implementation of -= macro for tasks. */ def taskRemove1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: Context)(v: c.Expr[U])(r: c.Expr[Remove.Value[T, U]]): c.Expr[Setting[Task[T]]] = { @@ -221,7 +235,7 @@ object TaskMacro { import c.universe.{ Apply, ApplyTag, newTermName, Select, SelectTag, TypeApply, TypeApplyTag } c.macroApplication match { case Apply(Apply(TypeApply(Select(preT, nmeT), targs), _), a) => - Apply(Apply(TypeApply(Select(preT, newTermName(newName).encodedName), targs), init :: sourcePosition(c).tree :: Nil), a) + Apply(Apply(TypeApply(Select(preT, newTermName(newName).encodedName), targs), init :: sourcePosition(c).tree :: Nil), append :: Nil) case x => ContextUtil.unexpectedTree(x) } } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index a17df6881..878501732 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -208,7 +208,7 @@ object Defaults extends BuildCommon { unmanagedResources := collectFiles(unmanagedResourceDirectories, includeFilter in unmanagedResources, excludeFilter in unmanagedResources).value, watchSources in ConfigGlobal ++= unmanagedResources.value, resourceGenerators :== Nil, - resourceGenerators += ((discoveredSbtPlugins, resourceManaged) map PluginDiscovery.writeDescriptors).taskValue, + resourceGenerators += Def.task { PluginDiscovery.writeDescriptors(discoveredSbtPlugins.value, resourceManaged.value) }, managedResources := generate(resourceGenerators).value, resources := Classpaths.concat(managedResources, unmanagedResources).value ) diff --git a/notes/0.13.14/buildlevelkey.md b/notes/0.13.14/buildlevelkey.md index e5ee44518..4bcb1cff6 100644 --- a/notes/0.13.14/buildlevelkey.md +++ b/notes/0.13.14/buildlevelkey.md @@ -1,6 +1,6 @@ ### Bug fixes -- Fixes regressions in sbt 0.13.11 - 0.13.13 that processed build-level keys incorrectly. [#2851][2851]/[#2460][2460] by [@eed3si9n] +- Fixes regressions in sbt 0.13.11 - 0.13.13 that processed build-level keys incorrectly. [#2851][2851]/[#2460][2460] by [@eed3si9n][] [#2851]: https://github.com/sbt/sbt/issues/2851 [#2460]: https://github.com/sbt/sbt/issues/2460 diff --git a/notes/0.13.14/fix-triggeredBy-storeAs-etc-using-colon-equals.md b/notes/0.13.14/fix-triggeredBy-storeAs-etc-using-colon-equals.md new file mode 100644 index 000000000..9e25af3df --- /dev/null +++ b/notes/0.13.14/fix-triggeredBy-storeAs-etc-using-colon-equals.md @@ -0,0 +1,8 @@ + +### Bug fixes + +- Fix .triggeredBy/.storeAs/etc not working when using `:=` and `.value` macros. [#1444][1444]/[#2908][2908] by [@dwijnand][] + + [1444]: https://github.com/sbt/sbt/issues/1444 + [2908]: https://github.com/sbt/sbt/pull/2908 + [@dwijnand]: https://github.com/dwijnand diff --git a/notes/0.13.14/junit_xml_imp.md b/notes/0.13.14/junit_xml_imp.md index 3fafbe71f..0c9d8bf76 100644 --- a/notes/0.13.14/junit_xml_imp.md +++ b/notes/0.13.14/junit_xml_imp.md @@ -1,7 +1,7 @@ ### Improvements -- XML generated by JUnitXmlTestsListener now correctly flags ignored, skipped and pending tests. [#PULL][PULL]/[#2198][2198] by [@ashleymercer][@ashleymercer] +- XML generated by JUnitXmlTestsListener now correctly flags ignored, skipped and pending tests. [#2198][2198]/[#2854][2854] by [@ashleymercer][@ashleymercer] - [PULL]: https://github.com/sbt/sbt/pull/PULL [2198]: https://github.com/sbt/sbt/issues/2198 + [2854]: https://github.com/sbt/sbt/pull/2854 [@ashleymercer]: https://github.com/ashleymercer diff --git a/notes/0.13.14/preliminary-compatibility-with-jdk9.md b/notes/0.13.14/preliminary-compatibility-with-jdk9.md new file mode 100644 index 000000000..9eddb4ea7 --- /dev/null +++ b/notes/0.13.14/preliminary-compatibility-with-jdk9.md @@ -0,0 +1,6 @@ +### Improvements + +- Added preliminary compatibility with JDK 9. [#2951][2951]/ by [@retronym][] + + [2951]: https://github.com/sbt/sbt/pull/2951 + [@retronym]: https://github.com/retronym diff --git a/sbt/src/sbt-test/actions/generator/build.sbt b/sbt/src/sbt-test/actions/generator/build.sbt new file mode 100644 index 000000000..c347b414e --- /dev/null +++ b/sbt/src/sbt-test/actions/generator/build.sbt @@ -0,0 +1,13 @@ +lazy val buildInfo = taskKey[Seq[File]]("The task that generates the build info.") + +lazy val root = (project in file(".")) + .settings( + scalaVersion := "2.11.8", + buildInfo := { + val x = sourceManaged.value / "BuildInfo.scala" + IO.write(x, """object BuildInfo""") + x :: Nil + }, + sourceGenerators in Compile += buildInfo, + sourceGenerators in Compile += Def.task { Nil } + ) diff --git a/sbt/src/sbt-test/actions/generator/test b/sbt/src/sbt-test/actions/generator/test new file mode 100644 index 000000000..385612f46 --- /dev/null +++ b/sbt/src/sbt-test/actions/generator/test @@ -0,0 +1,2 @@ +> compile +$ exists target/scala-2.11/src_managed/BuildInfo.scala diff --git a/sbt/src/sbt-test/project/auto-plugins-default-requires-jvmplugin/build.sbt b/sbt/src/sbt-test/project/auto-plugins-default-requires-jvmplugin/build.sbt index 601ed7611..bea5471fd 100644 --- a/sbt/src/sbt-test/project/auto-plugins-default-requires-jvmplugin/build.sbt +++ b/sbt/src/sbt-test/project/auto-plugins-default-requires-jvmplugin/build.sbt @@ -1,8 +1,8 @@ val test123 = project in file(".") enablePlugins TestP settings( - resourceGenerators in Compile += (Def.task { + resourceGenerators in Compile += Def.task { streams.value.log info "resource generated in settings" Nil - }).taskValue + } ) TaskKey[Unit]("check") := { diff --git a/util/appmacro/src/main/scala/sbt/appmacro/ContextUtil.scala b/util/appmacro/src/main/scala/sbt/appmacro/ContextUtil.scala index a2f1e4e47..7358a3cc5 100644 --- a/util/appmacro/src/main/scala/sbt/appmacro/ContextUtil.scala +++ b/util/appmacro/src/main/scala/sbt/appmacro/ContextUtil.scala @@ -126,6 +126,9 @@ final class ContextUtil[C <: Context](val ctx: C) { def freshMethodParameter(tpe: Type): ValDef = ValDef(parameterModifiers, freshTermName("p"), TypeTree(tpe), EmptyTree) + def typeArgs(tpe: Type): List[Type] = + tpe.asInstanceOf[global.Type].typeArgs map { _.asInstanceOf[Type] } + /** Constructs a ValDef with local modifiers and a unique name. */ def localValDef(tpt: Tree, rhs: Tree): ValDef = ValDef(localModifiers, freshTermName("q"), tpt, rhs)