Put `util-interface` on classpath when compiling compiler bridge

Also, write tests to make sure that we remain able to compile the
compiler bridge for different versions of Scala.

Fixes sbt/incrementalcompiler#32
This commit is contained in:
Martin Duhem 2015-11-16 22:44:58 +01:00
parent f2ee845280
commit c62f8f4683
3 changed files with 155 additions and 30 deletions

View File

@ -47,8 +47,8 @@ private[inc] class IvyComponentCompiler(compiler: RawCompiler, manager: Componen
import ComponentCompiler._
private val sbtOrg = xsbti.ArtifactInfo.SbtOrganization
private val xsbtiInterfaceModuleName = "compiler-interface"
private val xsbtiInterfaceID = s"interface-$incrementalVersion"
// private val xsbtiInterfaceModuleName = "compiler-interface"
// private val xsbtiInterfaceID = s"interface-$incrementalVersion"
private val sbtOrgTemp = JsonUtil.sbtOrgTemp
private val modulePrefixTemp = "temp-module-"
private val ivySbt: IvySbt = new IvySbt(ivyConfiguration)
@ -69,41 +69,26 @@ private[inc] class IvyComponentCompiler(compiler: RawCompiler, manager: Componen
IO.withTemporaryDirectory { binaryDirectory =>
val targetJar = new File(binaryDirectory, s"$binID.jar")
val xsbtiJars = manager.files(xsbtiInterfaceID)(new IfMissing.Define(true, getXsbtiInterface()))
buffered bufferQuietly {
IO.withTemporaryDirectory { retrieveDirectory =>
(update(getModule(sourcesModule), retrieveDirectory)(_.getName endsWith "-sources.jar")) match {
case Some(sources) =>
update(getModule(sourcesModule), retrieveDirectory) match {
case Seq() =>
throw new InvalidComponent(s"Couldn't retrieve source module: $sourcesModule")
case allArtifacts =>
val (sources, xsbtiJars) = allArtifacts partition (_.getName endsWith "-sources.jar")
AnalyzingCompiler.compileSources(sources, targetJar, xsbtiJars, sourcesModule.name, compiler, log)
manager.define(binID, Seq(targetJar))
case None =>
throw new InvalidComponent(s"Couldn't retrieve source module: $sourcesModule")
}
}
}
}
private def getXsbtiInterface(): Unit =
buffered bufferQuietly {
IO withTemporaryDirectory { retrieveDirectory =>
val module = ModuleID(sbtOrg, xsbtiInterfaceModuleName, incrementalVersion, Some("component"))
val jarName = s"$xsbtiInterfaceModuleName-$incrementalVersion.jar"
(update(getModule(module), retrieveDirectory)(_.getName == jarName)) match {
case Some(interface) =>
manager.define(xsbtiInterfaceID, interface)
case None =>
throw new InvalidComponent(s"Couldn't retrieve xsbti interface module: $xsbtiInterfaceModuleName")
}
}
}
/**
* Returns a dummy module that depends on `moduleID`.
* Note: Sbt's implementation of Ivy requires us to do this, because only the dependencies
@ -139,7 +124,7 @@ private[inc] class IvyComponentCompiler(compiler: RawCompiler, manager: Componen
s"unknown"
}
private def update(module: ivySbt.Module, retrieveDirectory: File)(predicate: File => Boolean): Option[Seq[File]] = {
private def update(module: ivySbt.Module, retrieveDirectory: File): Seq[File] = {
val retrieveConfiguration = new RetrieveConfiguration(retrieveDirectory, Resolver.defaultRetrievePattern, false)
val updateConfiguration = new UpdateConfiguration(Some(retrieveConfiguration), true, UpdateLogging.DownloadOnly)
@ -148,7 +133,7 @@ private[inc] class IvyComponentCompiler(compiler: RawCompiler, manager: Componen
IvyActions.updateEither(module, updateConfiguration, UnresolvedWarningConfiguration(), LogicalClock.unknown, None, buffered) match {
case Left(unresolvedWarning) =>
buffered.debug(s"Couldn't retrieve module ${dependenciesNames(module)}.")
None
Nil
case Right(updateReport) =>
val allFiles =
@ -161,10 +146,7 @@ private[inc] class IvyComponentCompiler(compiler: RawCompiler, manager: Componen
buffered.debug(s"Files retrieved for ${dependenciesNames(module)}:")
buffered.debug(allFiles mkString ", ")
allFiles filter predicate match {
case Seq() => None
case files => Some(files)
}
allFiles
}
}

View File

@ -0,0 +1,106 @@
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.{ ComponentManager, IvySbt, BaseIvySpecification }
import sbt.io.IO
import sbt.io.Path._
import sbt.librarymanagement.{ ModuleID, UpdateOptions, Resolver }
import sbt.util.Logger
import xsbti.{ ComponentProvider, GlobalLock }
abstract class BridgeProviderSpecification extends BaseIvySpecification {
override def resolvers: Seq[Resolver] = Seq(Resolver.mavenLocal, Resolver.jcenterRepo)
val ivyConfiguration = mkIvyConfiguration(UpdateOptions())
val ivySbt = new IvySbt(ivyConfiguration)
val home = new File(sys.props("user.home"))
val ivyCache = home / ".ivy2" / "cache"
def getCompilerBridge(tempDir: File, log: Logger, scalaVersion: String): File = {
val instance = scalaInstanceFromFile(scalaVersion)
val bridgeId = compilerBridgeId(scalaVersion)
val sourceModule = ModuleID(xsbti.ArtifactInfo.SbtOrganization, bridgeId, ComponentCompiler.incrementalVersion, Some("component")).sources()
val raw = new RawCompiler(instance, ClasspathOptions.auto, log)
val manager = new ComponentManager(lock, provider(tempDir), None, log)
val componentCompiler = new IvyComponentCompiler(raw, manager, ivyConfiguration, sourceModule, log)
val bridge = componentCompiler.apply()
val target = tempDir / s"target-bridge-$scalaVersion.jar"
IO.copyFile(bridge, target)
target
}
def scalaInstanceFromFile(scalaVersion: String): ScalaInstance =
scalaInstance(
ivyCache / "org.scala-lang" / "scala-compiler" / "jars" / s"scala-compiler-$scalaVersion.jar",
ivyCache / "org.scala-lang" / "scala-library" / "jars" / s"scala-library-$scalaVersion.jar",
Seq(ivyCache / "org.scala-lang" / "scala-reflect" / "jars" / s"scala-reflect-$scalaVersion.jar")
)
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 compilerBridgeId(scalaVersion: String) =
scalaVersion match {
case sc if sc startsWith "2.11" => "compiler-bridge_2.11"
case _ => "compiler-bridge_2.10"
}
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
}
private val lock: GlobalLock = new GlobalLock {
override def apply[T](file: File, callable: Callable[T]): T = callable.call()
}
private def provider(targetDir: File): ComponentProvider = new ComponentProvider {
override def lockFile(): File = targetDir / "lock"
override def defineComponent(componentID: String, files: Array[File]): Unit =
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
}
}

View File

@ -0,0 +1,37 @@
package sbt.internal.inc
import sbt.io.IO
import sbt.util.Logger
class IvyComponentCompilerSpec extends BridgeProviderSpecification {
val scala282 = "2.8.2"
val scala292 = "2.9.2"
val scala210 = "2.10.5"
val scala211 = "2.11.7"
"IvyComponentCompiler" should "compile the bridge for Scala 2.8" in pendingUntilFixed {
IO.withTemporaryDirectory { tempDir =>
getCompilerBridge(tempDir, Logger.Null, scala282) should exist
}
}
it should "compile the bridge for Scala 2.9" in pendingUntilFixed {
IO.withTemporaryDirectory { tempDir =>
getCompilerBridge(tempDir, Logger.Null, scala292) should exist
}
}
it should "compile the bridge for Scala 2.10" in pendingUntilFixed {
IO.withTemporaryDirectory { tempDir =>
getCompilerBridge(tempDir, Logger.Null, scala210) should exist
}
}
it should "compile the bridge for Scala 2.11" in {
IO.withTemporaryDirectory { tempDir =>
getCompilerBridge(tempDir, Logger.Null, scala211) should exist
}
}
}