From 3ad3f87212b04cec81f841bddef557b58967c2e6 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 4 Feb 2017 22:14:51 -0700 Subject: [PATCH 1/2] 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 2/2] 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) } }