Test `getScalaInstance` & `getCompiledBridge`

* Remove unused code in `BridgeProviderSpecification` and test the real
  code in the default provider.
* Don't use temporary directory to download the Scala jars, retrive them
  in a directory specified by the user.
* Use `java.net.ClassLoader` instead of `ScalaClassLoader`.
* Use the `ScalaInstance` interface, not the implementation. Remove any
  reference to the implementation.
This commit is contained in:
jvican 2017-05-19 20:19:25 +02:00
parent ae40c096e3
commit f4c38c8355
2 changed files with 91 additions and 153 deletions

View File

@ -10,6 +10,7 @@ package internal
package inc
import java.io.File
import java.net.URLClassLoader
import sbt.internal.inc.classpath.ClasspathUtilities
import sbt.io.{ Hash, IO }
@ -20,8 +21,6 @@ import sbt.librarymanagement.syntax._
import sbt.util.{ InterfaceUtil, Logger }
import xsbti.compile.CompilerBridgeProvider
import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader
private[sbt] object ZincComponentCompiler {
final val binSeparator = "-bin_"
final val javaClassVersion = System.getProperty("java.class.version")
@ -31,23 +30,57 @@ private[sbt] object ZincComponentCompiler {
private final val ZincVersionPropertyFile = "/incrementalcompiler.version.properties"
private final val ZincVersionProperty = "version"
final lazy val incrementalVersion: String = {
private final lazy val incrementalVersion: String = {
val cl = this.getClass.getClassLoader
ResourceLoader.getPropertiesFor(ZincVersionPropertyFile, cl).getProperty(ZincVersionProperty)
}
private class ZincCompilerBridgeProvider(manager: ZincComponentManager,
ivyConfiguration: IvyConfiguration,
bridgeModule: ModuleID)
extends CompilerBridgeProvider {
/** Defines the internal implementation of a bridge provider. */
private class ZincCompilerBridgeProvider(
manager: ZincComponentManager,
ivyConfiguration: IvyConfiguration,
scalaJarsTarget: File
) extends CompilerBridgeProvider {
private val CompileConf = Some(Configurations.Compile.name)
private def getDefaultBridgeModule(scalaVersion: String): ModuleID = {
def compilerBridgeId(scalaVersion: String) = {
// Defaults to bridge for 2.12 for Scala versions bigger than 2.12.x
scalaVersion match {
case sc if (sc startsWith "2.10.") => "compiler-bridge_2.10"
case sc if (sc startsWith "2.11.") => "compiler-bridge_2.11"
case _ => "compiler-bridge_2.12"
}
}
import xsbti.ArtifactInfo.SbtOrganization
val bridgeId = compilerBridgeId(scalaVersion)
ModuleID(SbtOrganization, bridgeId, incrementalVersion)
.withConfigurations(CompileConf)
.sources()
}
/**
* Defines a richer interface for Scala users that want to pass in an explicit module id.
*
* Note that this method cannot be defined in [[CompilerBridgeProvider]] because [[ModuleID]]
* is a Scala-defined class to which the compiler bridge cannot depend on.
*/
def getCompiledBridge(bridgeSources: ModuleID,
scalaInstance: xsbti.compile.ScalaInstance,
logger: xsbti.Logger): File = {
val autoClasspath = ClasspathOptionsUtil.auto
val raw = new RawCompiler(scalaInstance, autoClasspath, logger)
val zinc = new ZincComponentCompiler(raw, manager, ivyConfiguration, bridgeSources, logger)
logger.debug(InterfaceUtil.f0(s"Getting $bridgeSources for Scala ${scalaInstance.version}"))
zinc.getCompiledBridgeJar
}
override def getCompiledBridge(scalaInstance: xsbti.compile.ScalaInstance,
logger: xsbti.Logger): File = {
val autoClasspath = ClasspathOptionsUtil.auto
val raw = new RawCompiler(scalaInstance, autoClasspath, logger)
val zinc = new ZincComponentCompiler(raw, manager, ivyConfiguration, bridgeModule, logger)
logger.debug(InterfaceUtil.f0(s"Getting $bridgeModule for Scala ${scalaInstance.version}"))
zinc.getCompiledBridgeJar
val scalaVersion = scalaInstance.actualVersion()
val bridgeSources = getDefaultBridgeModule(scalaVersion)
getCompiledBridge(bridgeSources, scalaInstance, logger)
}
private final case class ScalaArtifacts(compiler: File, library: File, others: Vector[File])
@ -56,10 +89,9 @@ private[sbt] object ZincComponentCompiler {
def isPrefixedWith(artifact: File, prefix: String) = artifact.getName.startsWith(prefix)
import xsbti.ArtifactInfo._
import Configurations.Compile
import UnresolvedWarning.unresolvedWarningLines
val fullLogger = new FullLogger(logger)
val CompileConf = Some(Compile.name)
val CompileConf = Some(Configurations.Compile.name)
val dummyModule = ModuleID(JsonUtil.sbtOrgTemp, s"tmp-scala-$scalaVersion", scalaVersion)
val scalaLibrary = ModuleID(ScalaOrganization, ScalaLibraryID, scalaVersion)
val scalaCompiler = ModuleID(ScalaOrganization, ScalaCompilerID, scalaVersion)
@ -67,22 +99,20 @@ private[sbt] object ZincComponentCompiler {
val wrapper = dummyModule.withConfigurations(CompileConf)
val ivySbt: IvySbt = new IvySbt(ivyConfiguration)
val ivyModule = ZincIvyActions.getModule(ivySbt, wrapper, dependencies)
IO.withTemporaryDirectory { retrieveDirectory =>
ZincIvyActions.update(ivyModule, retrieveDirectory, fullLogger) match {
case Left(uw) =>
val unresolvedLines = unresolvedWarningLines.showLines(uw)
val unretrievedMessage = s"The Scala compiler and library could not be retrieved."
throw new InvalidComponent(s"$unretrievedMessage\n$unresolvedLines")
case Right(allArtifacts) =>
val isScalaCompiler = (f: File) => isPrefixedWith(f, "scala-compiler-")
val isScalaLibrary = (f: File) => isPrefixedWith(f, "scala-library-")
val maybeScalaCompiler = allArtifacts.find(isScalaCompiler)
val maybeScalaLibrary = allArtifacts.find(isScalaLibrary)
val others = allArtifacts.filterNot(a => isScalaCompiler(a) || isScalaLibrary(a))
val scalaCompiler = maybeScalaCompiler.getOrElse(throw MissingScalaJar.compiler)
val scalaLibrary = maybeScalaLibrary.getOrElse(throw MissingScalaJar.library)
ScalaArtifacts(scalaCompiler, scalaLibrary, others)
}
ZincIvyActions.update(ivyModule, scalaJarsTarget, noSource = true, fullLogger) match {
case Left(uw) =>
val unresolvedLines = unresolvedWarningLines.showLines(uw)
val unretrievedMessage = s"The Scala compiler and library could not be retrieved."
throw new InvalidComponent(s"$unretrievedMessage\n$unresolvedLines")
case Right(allArtifacts) =>
val isScalaCompiler = (f: File) => isPrefixedWith(f, "scala-compiler-")
val isScalaLibrary = (f: File) => isPrefixedWith(f, "scala-library-")
val maybeScalaCompiler = allArtifacts.find(isScalaCompiler)
val maybeScalaLibrary = allArtifacts.find(isScalaLibrary)
val others = allArtifacts.filterNot(a => isScalaCompiler(a) || isScalaLibrary(a))
val scalaCompiler = maybeScalaCompiler.getOrElse(throw MissingScalaJar.compiler)
val scalaLibrary = maybeScalaLibrary.getOrElse(throw MissingScalaJar.library)
ScalaArtifacts(scalaCompiler, scalaLibrary, others)
}
}
@ -93,6 +123,7 @@ private[sbt] object ZincComponentCompiler {
val scalaCompiler = scalaArtifacts.compiler
val scalaLibrary = scalaArtifacts.library
val jarsToLoad = (scalaCompiler +: scalaLibrary +: scalaArtifacts.others).toArray
assert(jarsToLoad.forall(_.exists), "One or more jar(s) in the Scala instance do not exist.")
val loader = new URLClassLoader(toURLs(jarsToLoad), ClasspathUtilities.rootLoader)
val properties = ResourceLoader.getSafePropertiesFor("compiler.properties", loader)
val loaderVersion = Option(properties.getProperty("version.number"))
@ -103,8 +134,8 @@ private[sbt] object ZincComponentCompiler {
def interfaceProvider(manager: ZincComponentManager,
ivyConfiguration: IvyConfiguration,
sourcesModule: ModuleID): CompilerBridgeProvider =
new ZincCompilerBridgeProvider(manager, ivyConfiguration, sourcesModule)
scalaJarsTarget: File): CompilerBridgeProvider =
new ZincCompilerBridgeProvider(manager, ivyConfiguration, scalaJarsTarget)
}
@ -177,7 +208,7 @@ private[inc] class ZincComponentCompiler(
val target = new File(binaryDirectory, s"$compilerBridgeId.jar")
buffered bufferQuietly {
IO.withTemporaryDirectory { retrieveDirectory =>
ZincIvyActions.update(ivyModuleForBridge, retrieveDirectory, buffered) match {
ZincIvyActions.update(ivyModuleForBridge, retrieveDirectory, false, buffered) match {
case Left(uw) =>
val mod = bridgeSources.toString
val unresolvedLines = unresolvedWarningLines.showLines(uw)
@ -232,33 +263,34 @@ object ZincIvyActions {
module.moduleSettings match {
case ic: InlineConfiguration =>
// Pretty print the module as `ModuleIDExtra.toStringImpl` does.
val dependency =
ic.dependencies.map(m => s"${m.organization}:${m.name}:${m.revision}").headOption
dependency.getOrElse(sys.error("Fatal: more than one dependency in dummy bridge module."))
ic.dependencies.map(m => s"${m.organization}:${m.name}:${m.revision}").mkString(", ")
case _ => sys.error("Fatal: configuration to download was not inline.")
}
}
private final val warningConf = UnresolvedWarningConfiguration()
private final val defaultRetrievePattern = Resolver.defaultRetrievePattern
private def defaultUpdateConfiguration(retrieveDirectory: File): UpdateConfiguration = {
val retrieve = RetrieveConfiguration(retrieveDirectory, defaultRetrievePattern, false, None)
private def defaultUpdateConfiguration(targetDir: File, noSource: Boolean): UpdateConfiguration = {
val retrieve = RetrieveConfiguration(targetDir, defaultRetrievePattern, false, None)
val logLevel = UpdateLogging.DownloadOnly
val noDocs = ArtifactTypeFilter.forbid(Set("doc"))
UpdateConfiguration(Some(retrieve), missingOk = false, logLevel, noDocs)
val defaultExcluded = Set("doc")
val finalExcluded = if (noSource) defaultExcluded + "src" else defaultExcluded
val artifactFilter = ArtifactTypeFilter.forbid(finalExcluded)
UpdateConfiguration(Some(retrieve), missingOk = false, logLevel, artifactFilter)
}
private[inc] def update(module: IvyModule,
retrieveDirectory: File,
noSource: Boolean = false,
logger: Logger): Either[UnresolvedWarning, Vector[File]] = {
import IvyActions.updateEither
val updateConfiguration = defaultUpdateConfiguration(retrieveDirectory)
val updateConfiguration = defaultUpdateConfiguration(retrieveDirectory, noSource)
val dependencies = prettyPrintDependency(module)
logger.info(s"Attempting to fetch $dependencies. This operation may fail.")
logger.info(s"Attempting to fetch $dependencies.")
val clockForCache = LogicalClock.unknown
updateEither(module, updateConfiguration, warningConf, clockForCache, None, logger) match {
case Left(unresolvedWarning) =>
logger.debug(s"Couldn't retrieve module ${prettyPrintDependency(module)}.")
logger.debug(s"Couldn't retrieve module(s) ${prettyPrintDependency(module)}.")
Left(unresolvedWarning)
case Right(updateReport) =>

View File

@ -1,18 +1,13 @@
package sbt.internal.inc
import java.io.File
import java.net.URLClassLoader
import java.util.Properties
import java.util.concurrent.Callable
import sbt.internal.inc.classpath.ClasspathUtilities
import sbt.internal.librarymanagement.JsonUtil
import sbt.io.IO
import sbt.io.syntax._
import sbt.librarymanagement.{
DefaultMavenRepository,
FileRepository,
ModuleID,
Patterns,
Resolver,
UpdateOptions
@ -25,8 +20,6 @@ import xsbti.{ ComponentProvider, GlobalLock }
* Base class for test suites that must be able to fetch and compile the compiler bridge.
*/
abstract class BridgeProviderSpecification extends BaseIvySpecification {
// log.setLevel(Level.Warn)
def realLocal: Resolver = {
val pList = Vector(s"$${user.home}/.ivy2/local/${Resolver.localBasePattern}")
FileRepository(
@ -34,6 +27,7 @@ abstract class BridgeProviderSpecification extends BaseIvySpecification {
Resolver.defaultFileConfiguration,
Patterns().withIvyPatterns(pList).withArtifactPatterns(pList).withIsMavenCompatible(false))
}
override def resolvers: Vector[Resolver] = Vector(realLocal, DefaultMavenRepository)
private val ivyConfiguration = mkIvyConfiguration(UpdateOptions())
@ -41,29 +35,15 @@ abstract class BridgeProviderSpecification extends BaseIvySpecification {
val target = file("target").getAbsoluteFile
target / "zinc-components"
}
def secondaryCacheOpt: Option[File] = Some(secondaryCacheDirectory)
def compilerBridgeId(scalaVersion: String) = {
scalaVersion match {
case sc if (sc startsWith "2.10.") => "compiler-bridge_2.10"
case sc if (sc startsWith "2.11.") => "compiler-bridge_2.11"
case _ => "compiler-bridge_2.12"
}
}
def getZincProvider(targetDir: File, scalaVersion: String, log: Logger): CompilerBridgeProvider = {
import xsbti.ArtifactInfo.SbtOrganization
import ZincComponentCompiler.incrementalVersion
val bridgeId = compilerBridgeId(scalaVersion)
val sourceModule = ModuleID(SbtOrganization, bridgeId, incrementalVersion)
.withConfigurations(Some("component"))
.sources()
val manager = new ZincComponentManager(lock, compProvider(targetDir), secondaryCacheOpt, log)
ZincComponentCompiler.interfaceProvider(manager, ivyConfiguration, sourceModule)
def getZincProvider(targetDir: File, log: Logger): CompilerBridgeProvider = {
val secondaryCache = Some(secondaryCacheDirectory)
val manager = new ZincComponentManager(lock, compProvider(targetDir), secondaryCache, log)
ZincComponentCompiler.interfaceProvider(manager, ivyConfiguration, currentManaged)
}
def getCompilerBridge(targetDir: File, log: Logger, scalaVersion: String): File = {
val provider = getZincProvider(targetDir, scalaVersion, log)
val provider = getZincProvider(targetDir, log)
val scalaInstance = provider.getScalaInstance(scalaVersion, log)
val bridge = provider.getCompiledBridge(scalaInstance, log)
val target = targetDir / s"target-bridge-$scalaVersion.jar"
@ -71,78 +51,11 @@ abstract class BridgeProviderSpecification extends BaseIvySpecification {
target
}
def scalaInstance(scalaVersion: String): ScalaInstance = {
val scalaModule = {
val dummyModule = ModuleID(JsonUtil.sbtOrgTemp, "tmp-scala-" + scalaVersion, scalaVersion)
.withConfigurations(Some("compile"))
val scalaLibrary = ModuleID(xsbti.ArtifactInfo.ScalaOrganization,
xsbti.ArtifactInfo.ScalaLibraryID,
scalaVersion).withConfigurations(Some("compile"))
val scalaCompiler = ModuleID(xsbti.ArtifactInfo.ScalaOrganization,
xsbti.ArtifactInfo.ScalaCompilerID,
scalaVersion).withConfigurations(Some("compile"))
module(dummyModule, Vector(scalaLibrary, scalaCompiler), None)
}
val allArtifacts =
for {
conf <- ivyUpdate(scalaModule).configurations
m <- conf.modules
(_, f) <- m.artifacts
} yield f
def isCompiler(f: File) = f.getName startsWith "scala-compiler-"
def isLibrary(f: File) = f.getName startsWith "scala-library-"
val scalaCompilerJar = allArtifacts find isCompiler getOrElse (throw new RuntimeException(
"Not found: scala-compiler"))
val scalaLibraryJar = allArtifacts find isLibrary getOrElse (throw new RuntimeException(
"Not found: scala-library"))
val others = allArtifacts filterNot (a => isCompiler(a) || isLibrary(a))
scalaInstance(scalaCompilerJar, scalaLibraryJar, others)
}
def scalaInstance(scalaCompiler: File,
scalaLibrary: File,
scalaExtra: Seq[File]): ScalaInstance = {
val loader = scalaLoader(scalaLibrary +: scalaCompiler +: scalaExtra)
val version = scalaVersion(loader)
val allJars = (scalaLibrary +: scalaCompiler +: scalaExtra).toArray
new ScalaInstance(version.getOrElse("unknown"),
loader,
scalaLibrary,
scalaCompiler,
allJars,
version)
}
def scalaLoader(jars: Seq[File]) =
new URLClassLoader(sbt.io.Path.toURLs(jars), ClasspathUtilities.rootLoader)
def scalaVersion(scalaLoader: ClassLoader): Option[String] =
propertyFromResource("compiler.properties", "version.number", scalaLoader)
/**
* Get a property from a properties file resource in the classloader.
*/
def propertyFromResource(resource: String,
property: String,
classLoader: ClassLoader): Option[String] = {
val props = propertiesFromResource(resource, classLoader)
Option(props.getProperty(property))
}
/**
* Get all properties from a properties file resource in the classloader.
*/
def propertiesFromResource(resource: String, classLoader: ClassLoader): Properties = {
val props = new Properties
val stream = classLoader.getResourceAsStream(resource)
try { props.load(stream) } catch { case _: Exception => } finally {
if (stream ne null) stream.close()
}
props
def scalaInstance(scalaVersion: String,
targetDir: File,
logger: Logger): xsbti.compile.ScalaInstance = {
val provider = getZincProvider(targetDir, logger)
provider.getScalaInstance(scalaVersion, logger)
}
private val lock: GlobalLock = new GlobalLock {
@ -150,23 +63,16 @@ abstract class BridgeProviderSpecification extends BaseIvySpecification {
}
private def compProvider(targetDir: File): ComponentProvider = new ComponentProvider {
override def lockFile(): File = targetDir / "lock"
override def componentLocation(id: String): File =
throw new UnsupportedOperationException
override def component(componentID: String): Array[File] =
IO.listFiles(targetDir / componentID)
override def defineComponent(componentID: String, files: Array[File]): Unit =
files foreach { f =>
IO.copyFile(f, targetDir / componentID / f.getName)
}
files.foreach(f => IO.copyFile(f, targetDir / componentID / f.getName))
override def addToComponent(componentID: String, files: Array[File]): Boolean = {
defineComponent(componentID, files)
true
}
override def component(componentID: String): Array[File] =
IO.listFiles(targetDir / componentID)
override def componentLocation(id: String): File = throw new UnsupportedOperationException
}
}