From 70a2878dbcf185caeeabe9aa4eb6a3910a53ddb8 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 22 Jan 2017 22:51:01 -0500 Subject: [PATCH 1/6] Adds an Append instance that extracts taskValue This adds a macro-level hack to support += op for sourceGenerators and resourceGenerators using RHS of Initialize[Task[Seq[File]]]. When the types match up, the macro now calls `.taskValue` automatically. --- main/settings/src/main/scala/sbt/Append.scala | 11 ++++++++- .../src/main/scala/sbt/std/TaskMacro.scala | 24 +++++++++++++++---- main/src/main/scala/sbt/Defaults.scala | 2 +- sbt/src/sbt-test/actions/generator/build.sbt | 13 ++++++++++ sbt/src/sbt-test/actions/generator/test | 2 ++ .../build.sbt | 4 ++-- .../main/scala/sbt/appmacro/ContextUtil.scala | 3 +++ 7 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 sbt/src/sbt-test/actions/generator/build.sbt create mode 100644 sbt/src/sbt-test/actions/generator/test 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/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) From 3ad3f87212b04cec81f841bddef557b58967c2e6 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 4 Feb 2017 22:14:51 -0700 Subject: [PATCH 2/6] Future proof against removal of java.ext.dirs in Java 9 This is the start of an effort to make SBT 0.13.x compatible with JDK 9. The system property java.ext.dirs no longer exists now that JEP-220 has removed the extension and endorsed classpath facilities. It is also forbidden to manually set this to an empty string from the command line. This commit treats the absense of this property as an empty extension classpath. --- compile/src/main/scala/sbt/compiler/CompilerArguments.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From 265285e8928c8f7680caa96239847c13246b437a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 4 Feb 2017 22:23:02 -0700 Subject: [PATCH 3/6] Future proof for java.xml.bind removal from Java 9 default classpath JEP-201 describes the new modularized Java Standard Library in Java 9. By default, java.xml.bind is no longer on the default classpath; it needs to be added explicitly with a JVM option `--add-modules java.xml.bind`, or with a dependency declaration in the module-info.java file if you package your own code up as a Jigsaw module. This commit traps the linkage error and (reflectively) uses the java.util.Base64, which is the recommended way to encode/decode since 1.8. --- .../src/main/scala/sbt/inc/Base64.scala | 61 +++++++++++++++++++ .../scala/sbt/inc/TextAnalysisFormat.scala | 5 +- 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 compile/persist/src/main/scala/sbt/inc/Base64.scala 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) } } From 3ff04f987c2e0cba81aad848f81a175312e026c3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 6 Feb 2017 10:24:10 +0000 Subject: [PATCH 4/6] Touch up exisiting 0.13.14 notes --- notes/0.13.14/buildlevelkey.md | 2 +- notes/0.13.14/junit_xml_imp.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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/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 From c50f7b5c6b65c1b682e3babc0e035bb6c0d7d654 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 6 Feb 2017 10:24:12 +0000 Subject: [PATCH 5/6] Add notes for preliminary compatbility with jdk 9 --- notes/0.13.14/preliminary-compatibility-with-jdk9.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 notes/0.13.14/preliminary-compatibility-with-jdk9.md 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 From 9e67e0afba371b284cbadcdc566b1d8e27938d7f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 6 Feb 2017 10:23:30 +0000 Subject: [PATCH 6/6] Add notes for .triggeredBy/.storeAs/etc fix --- .../fix-triggeredBy-storeAs-etc-using-colon-equals.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 notes/0.13.14/fix-triggeredBy-storeAs-etc-using-colon-equals.md 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