mirror of https://github.com/sbt/sbt.git
Some comments and better organization of compile-related code.
This commit is contained in:
parent
8bfb2802fb
commit
39546077ee
|
|
@ -0,0 +1,45 @@
|
|||
package xsbt
|
||||
|
||||
import xsbti.{AnalysisCallback, Logger => xLogger}
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
|
||||
/** Interface to the Scala compiler that uses the dependency analysis plugin. This class uses the Scala library and compiler
|
||||
* obtained through the 'scalaLoader' class loader. This class requires a ComponentManager in order to obtain the
|
||||
* interface code to scalac and the analysis plugin. Because these call Scala code for a different Scala version, they must
|
||||
* be compiled for the version of Scala being used. It is essential that the provided 'scalaVersion' be a 1:1 mapping to the
|
||||
* actual version of Scala being used for compilation (-SNAPSHOT is not acceptable). Otherwise, binary compatibility
|
||||
* issues will ensue!*/
|
||||
class AnalyzingCompiler(scalaInstance: ScalaInstance, manager: ComponentManager) extends NotNull
|
||||
{
|
||||
def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: CompileLogger): Unit =
|
||||
apply(sources, classpath, outputDirectory, options, false, callback, maximumErrors, log)
|
||||
def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], compilerOnClasspath: Boolean,
|
||||
callback: AnalysisCallback, maximumErrors: Int, log: CompileLogger)
|
||||
{
|
||||
val arguments = (new CompilerArguments(scalaInstance))(sources, classpath, outputDirectory, options, compilerOnClasspath)
|
||||
// this is the instance used to compile the analysis
|
||||
val componentCompiler = new ComponentCompiler(new RawCompiler(scalaInstance, log), manager)
|
||||
log.debug("Getting " + ComponentCompiler.compilerInterfaceID + " from component compiler for Scala " + scalaInstance.version)
|
||||
val interfaceJar = componentCompiler(ComponentCompiler.compilerInterfaceID)
|
||||
val dual = createDualLoader(scalaInstance.loader, getClass.getClassLoader) // this goes to scalaLoader for scala classes and sbtLoader for xsbti classes
|
||||
val interfaceLoader = new URLClassLoader(Array(interfaceJar.toURI.toURL), dual)
|
||||
val interface = Class.forName("xsbt.CompilerInterface", true, interfaceLoader).newInstance.asInstanceOf[AnyRef]
|
||||
val runnable = interface.asInstanceOf[{ def run(args: Array[String], callback: AnalysisCallback, maximumErrors: Int, log: xLogger): Unit }]
|
||||
// these arguments are safe to pass across the ClassLoader boundary because the types are defined in Java
|
||||
// so they will be binary compatible across all versions of Scala
|
||||
runnable.run(arguments.toArray, callback, maximumErrors, log)
|
||||
}
|
||||
protected def createDualLoader(scalaLoader: ClassLoader, sbtLoader: ClassLoader): ClassLoader =
|
||||
{
|
||||
val xsbtiFilter = (name: String) => name.startsWith("xsbti.")
|
||||
val notXsbtiFilter = (name: String) => !xsbtiFilter(name)
|
||||
new DualLoader(scalaLoader, notXsbtiFilter, x => true, sbtLoader, xsbtiFilter, x => false)
|
||||
}
|
||||
override def toString = "Analyzing compiler (Scala " + scalaInstance.actualVersion + ")"
|
||||
}
|
||||
object AnalyzingCompiler
|
||||
{
|
||||
def apply(scalaVersion: String, provider: xsbti.ScalaProvider, manager: ComponentManager): AnalyzingCompiler =
|
||||
new AnalyzingCompiler(ScalaInstance(scalaVersion, provider), manager)
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
package xsbt
|
||||
|
||||
import xsbti.{AnalysisCallback, Logger => xLogger}
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
|
||||
/** Interface to the Scala compiler. This class uses the Scala library and compiler obtained through the 'scalaLoader' class
|
||||
* loader. This class requires a ComponentManager in order to obtain the interface code to scalac and the analysis plugin. Because
|
||||
* these call Scala code for a different Scala version, they must be compiled for the version of Scala being used.
|
||||
* It is essential that the provided 'scalaVersion' be a 1:1 mapping to the actual version of Scala being used for compilation
|
||||
* (-SNAPSHOT is not acceptable). Otherwise, binary compatibility issues will ensue!*/
|
||||
|
||||
/** A basic interface to the compiler. It is called in the same virtual machine, but no dependency analysis is done. This
|
||||
* is used, for example, to compile the interface/plugin code.*/
|
||||
class RawCompiler(scalaLoader: ClassLoader, scalaLibDirectory: File, log: CompileLogger)
|
||||
{
|
||||
lazy val scalaVersion = Class.forName("scala.tools.nsc.Properties", true, scalaLoader).getMethod("versionString").invoke(null)
|
||||
def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String]): Unit =
|
||||
apply(sources, classpath, outputDirectory, options, false)
|
||||
def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], compilerOnClasspath: Boolean)
|
||||
{
|
||||
// reflection is required for binary compatibility
|
||||
// The following imports ensure there is a compile error if the identifiers change,
|
||||
// but should not be otherwise directly referenced
|
||||
import scala.tools.nsc.Main
|
||||
import scala.tools.nsc.Properties
|
||||
|
||||
val arguments = CompilerArguments(scalaLibDirectory)(sources, classpath, outputDirectory, options, compilerOnClasspath)
|
||||
log.debug("Vanilla interface to Scala compiler " + scalaVersion + " with arguments: " + arguments.mkString("\n\t", "\n\t", ""))
|
||||
val mainClass = Class.forName("scala.tools.nsc.Main", true, scalaLoader)
|
||||
val process = mainClass.getMethod("process", classOf[Array[String]])
|
||||
val realArray: Array[String] = arguments.toArray
|
||||
assert(realArray.getClass eq classOf[Array[String]])
|
||||
process.invoke(null, realArray)
|
||||
checkForFailure(mainClass, arguments.toArray)
|
||||
}
|
||||
protected def checkForFailure(mainClass: Class[_], args: Array[String])
|
||||
{
|
||||
val reporter = mainClass.getMethod("reporter").invoke(null)
|
||||
val failed = reporter.asInstanceOf[{ def hasErrors: Boolean }].hasErrors
|
||||
if(failed) throw new xsbti.CompileFailed { val arguments = args; override def toString = "Vanilla compile failed" }
|
||||
}
|
||||
}
|
||||
/** Interface to the compiler that uses the dependency analysis plugin.*/
|
||||
class AnalyzeCompiler(scalaVersion: String, scalaLoader: ClassLoader, scalaLibDirectory: File, manager: ComponentManager) extends NotNull
|
||||
{
|
||||
def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: CompileLogger): Unit =
|
||||
apply(sources, classpath, outputDirectory, options, false, callback, maximumErrors, log)
|
||||
def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], compilerOnClasspath: Boolean,
|
||||
callback: AnalysisCallback, maximumErrors: Int, log: CompileLogger)
|
||||
{
|
||||
val arguments = CompilerArguments(scalaLibDirectory)(sources, classpath, outputDirectory, options, compilerOnClasspath)
|
||||
// this is the instance used to compile the analysis
|
||||
val componentCompiler = new ComponentCompiler(scalaVersion, new RawCompiler(scalaLoader, scalaLibDirectory, log), manager)
|
||||
log.debug("Getting " + ComponentCompiler.compilerInterfaceID + " from component compiler for Scala " + scalaVersion + " (loader=" + scalaLoader + ")")
|
||||
val interfaceJar = componentCompiler(ComponentCompiler.compilerInterfaceID)
|
||||
val dual = createDualLoader(scalaLoader, getClass.getClassLoader) // this goes to scalaLoader for scala classes and sbtLoader for xsbti classes
|
||||
val interfaceLoader = new URLClassLoader(Array(interfaceJar.toURI.toURL), dual)
|
||||
val interface = Class.forName("xsbt.CompilerInterface", true, interfaceLoader).newInstance.asInstanceOf[AnyRef]
|
||||
val runnable = interface.asInstanceOf[{ def run(args: Array[String], callback: AnalysisCallback, maximumErrors: Int, log: xLogger): Unit }]
|
||||
// these arguments are safe to pass across the ClassLoader boundary because the types are defined in Java
|
||||
// so they will be binary compatible across all versions of Scala
|
||||
runnable.run(arguments.toArray, callback, maximumErrors, log)
|
||||
}
|
||||
private def createDualLoader(scalaLoader: ClassLoader, sbtLoader: ClassLoader): ClassLoader =
|
||||
{
|
||||
val xsbtiFilter = (name: String) => name.startsWith("xsbti.")
|
||||
val notXsbtiFilter = (name: String) => !xsbtiFilter(name)
|
||||
new DualLoader(scalaLoader, notXsbtiFilter, x => true, sbtLoader, xsbtiFilter, x => false)
|
||||
}
|
||||
override def toString = "Analyzing compiler (Scala " + scalaVersion + ")"
|
||||
}
|
||||
object AnalyzeCompiler
|
||||
{
|
||||
def apply(scalaVersion: String, provider: xsbti.ScalaProvider, manager: ComponentManager): AnalyzeCompiler =
|
||||
new AnalyzeCompiler(scalaVersion, provider.getScalaLoader(scalaVersion), provider.getScalaHome(scalaVersion), manager)
|
||||
}
|
||||
|
|
@ -2,21 +2,33 @@ package xsbt
|
|||
|
||||
import java.io.File
|
||||
|
||||
object CompilerArguments
|
||||
/** Forms the list of options that is passed to the compiler from the required inputs and other options.
|
||||
* The directory containing scala-library.jar and scala-compiler.jar (scalaLibDirectory) is required in
|
||||
* order to add these jars to the boot classpath. The 'scala.home' property must be unset because Scala
|
||||
* puts jars in that directory on the bootclasspath. Because we use multiple Scala versions,
|
||||
* this would lead to compiling against the wrong library jar.*/
|
||||
class CompilerArguments(scalaInstance: ScalaInstance)
|
||||
{
|
||||
def apply(scalaLibDirectory: File)(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], compilerOnClasspath: Boolean): Seq[String] =
|
||||
def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], compilerOnClasspath: Boolean): Seq[String] =
|
||||
{
|
||||
val scalaHome = System.getProperty("scala.home")
|
||||
assert((scalaHome eq null) || scalaHome.isEmpty, "'scala.home' should not be set (was " + scalaHome + ")")
|
||||
def abs(files: Set[File]) = files.map(_.getAbsolutePath)
|
||||
val originalBoot = System.getProperty("sun.boot.class.path", "")
|
||||
val newBootPrefix = if(originalBoot.isEmpty) "" else originalBoot + File.pathSeparator
|
||||
val bootClasspathOption = Seq("-bootclasspath", newBootPrefix + scalaLibraryJar(scalaLibDirectory).getAbsolutePath)
|
||||
val cp2 = classpath ++ (if(compilerOnClasspath) scalaCompilerJar(scalaLibDirectory):: Nil else Nil)
|
||||
val classpathOption = Seq("-cp", abs(cp2).mkString(File.pathSeparator) )
|
||||
checkScalaHomeUnset()
|
||||
val bootClasspathOption = Seq("-bootclasspath", createBootClasspath)
|
||||
val cpWithCompiler = classpath ++ (if(compilerOnClasspath) scalaInstance.compilerJar :: Nil else Nil)
|
||||
val classpathOption = Seq("-cp", abs(cpWithCompiler).mkString(File.pathSeparator) )
|
||||
val outputOption = Seq("-d", outputDirectory.getAbsolutePath)
|
||||
options ++ outputOption ++ bootClasspathOption ++ classpathOption ++ abs(sources)
|
||||
}
|
||||
private def scalaLibraryJar(scalaLibDirectory: File): File = new File(scalaLibDirectory, "scala-library.jar")
|
||||
private def scalaCompilerJar(scalaLibDirectory: File): File = new File(scalaLibDirectory, "scala-compiler.jar")
|
||||
protected def abs(files: Set[File]) = files.map(_.getAbsolutePath)
|
||||
protected def checkScalaHomeUnset()
|
||||
{
|
||||
val scalaHome = System.getProperty("scala.home")
|
||||
assert((scalaHome eq null) || scalaHome.isEmpty, "'scala.home' should not be set (was " + scalaHome + ")")
|
||||
}
|
||||
/** Add the correct Scala library jar to the boot classpath.*/
|
||||
protected def createBootClasspath =
|
||||
{
|
||||
val originalBoot = System.getProperty("sun.boot.class.path", "")
|
||||
val newBootPrefix = if(originalBoot.isEmpty) "" else originalBoot + File.pathSeparator
|
||||
newBootPrefix + scalaInstance.libraryJar.getAbsolutePath
|
||||
}
|
||||
}
|
||||
|
|
@ -10,18 +10,21 @@ object ComponentCompiler
|
|||
val compilerInterfaceID = "compiler-interface"
|
||||
val compilerInterfaceSrcID = compilerInterfaceID + srcExtension
|
||||
}
|
||||
class ComponentCompiler(scalaVersion: String, compiler: RawCompiler, manager: ComponentManager)
|
||||
/** This class provides source components compiled with the provided RawCompiler.
|
||||
* The compiled classes are cached using the provided component manager according
|
||||
* to the actualVersion field of the RawCompiler.*/
|
||||
class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager)
|
||||
{
|
||||
import ComponentCompiler._
|
||||
import FileUtilities.{copy, createDirectory, zip, jars, unzip, withTemporaryDirectory}
|
||||
def apply(id: String): File =
|
||||
{
|
||||
val binID = binaryID(id, scalaVersion)
|
||||
val binID = binaryID(id)
|
||||
try { manager.file(binID) }
|
||||
catch { case e: InvalidComponent => compileAndInstall(id, binID) }
|
||||
}
|
||||
private def binaryID(id: String, scalaVersion: String) = id + binSeparator + scalaVersion
|
||||
private def compileAndInstall(id: String, binID: String): File =
|
||||
protected def binaryID(id: String) = id + binSeparator + compiler.scalaInstance.actualVersion
|
||||
protected def compileAndInstall(id: String, binID: String): File =
|
||||
{
|
||||
val srcID = id + srcExtension
|
||||
val binaryDirectory = manager.location(binID)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package xsbt
|
||||
|
||||
import java.io.File
|
||||
|
||||
/** A basic interface to the compiler. It is called in the same virtual machine, but no dependency analysis is done. This
|
||||
* is used, for example, to compile the interface/plugin code.*/
|
||||
class RawCompiler(val scalaInstance: ScalaInstance, log: CompileLogger)
|
||||
{
|
||||
def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String]): Unit =
|
||||
apply(sources, classpath, outputDirectory, options, false)
|
||||
def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], compilerOnClasspath: Boolean)
|
||||
{
|
||||
// reflection is required for binary compatibility
|
||||
// The following imports ensure there is a compile error if the identifiers change,
|
||||
// but should not be otherwise directly referenced
|
||||
import scala.tools.nsc.Main
|
||||
import scala.tools.nsc.Properties
|
||||
|
||||
val arguments = (new CompilerArguments(scalaInstance))(sources, classpath, outputDirectory, options, compilerOnClasspath)
|
||||
log.debug("Vanilla interface to Scala compiler " + scalaInstance.actualVersion + " with arguments: " + arguments.mkString("\n\t", "\n\t", ""))
|
||||
val mainClass = Class.forName("scala.tools.nsc.Main", true, scalaInstance.loader)
|
||||
val process = mainClass.getMethod("process", classOf[Array[String]])
|
||||
process.invoke(null, toJavaArray(arguments))
|
||||
checkForFailure(mainClass, arguments.toArray)
|
||||
}
|
||||
protected def checkForFailure(mainClass: Class[_], args: Array[String])
|
||||
{
|
||||
val reporter = mainClass.getMethod("reporter").invoke(null)
|
||||
val failed = reporter.asInstanceOf[{ def hasErrors: Boolean }].hasErrors
|
||||
if(failed) throw new xsbti.CompileFailed { val arguments = args; override def toString = "Vanilla compile failed" }
|
||||
}
|
||||
protected def toJavaArray(arguments: Seq[String]): Array[String] =
|
||||
{
|
||||
val realArray: Array[String] = arguments.toArray
|
||||
assert(realArray.getClass eq classOf[Array[String]])
|
||||
realArray
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package xsbt
|
||||
|
||||
import java.io.File
|
||||
|
||||
/** Represents the source for Scala classes for a given version. The reason both a ClassLoader and the jars are required
|
||||
* is that the compiler requires the location of the library/compiler jars on the (boot)classpath and the loader is used
|
||||
* for the compiler itself.
|
||||
* The 'version' field is the version used to obtain the Scala classes. This is typically the version for the maven repository.
|
||||
* The 'actualVersion' field should be used to uniquely identify the compiler. It is obtained from the compiler.properties file.*/
|
||||
final class ScalaInstance(val version: String, val loader: ClassLoader, val libraryJar: File, val compilerJar: File) extends NotNull
|
||||
{
|
||||
/** Gets the version of Scala in the compiler.properties file from the loader. This version may be different than that given by 'version'*/
|
||||
lazy val actualVersion =
|
||||
{
|
||||
import ScalaInstance.VersionPrefix
|
||||
val v = Class.forName("scala.tools.nsc.Properties", true, loader).getMethod("versionString").invoke(null).toString
|
||||
if(v.startsWith(VersionPrefix)) v.substring(VersionPrefix.length) else v
|
||||
}
|
||||
}
|
||||
object ScalaInstance
|
||||
{
|
||||
val VersionPrefix = "version "
|
||||
/** Creates a ScalaInstance using the given provider to obtain the jars and loader.*/
|
||||
def apply(version: String, provider: xsbti.ScalaProvider) =
|
||||
{
|
||||
val scalaLibDirectory = provider.getScalaHome(version)
|
||||
// these get the locations of the scala jars given the location of the lib/ directory
|
||||
val libraryJar = new File(scalaLibDirectory, "scala-library.jar")
|
||||
val compilerJar = new File(scalaLibDirectory, "scala-compiler.jar")
|
||||
new ScalaInstance(version, provider.getScalaLoader(version), libraryJar, compilerJar)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ object CompileTest extends Specification
|
|||
WithCompiler( "2.7.2" )(testCompileAnalysis)
|
||||
}
|
||||
}
|
||||
private def testCompileAnalysis(compiler: AnalyzeCompiler, log: CompileLogger)
|
||||
private def testCompileAnalysis(compiler: AnalyzingCompiler, log: CompileLogger)
|
||||
{
|
||||
WithFiles( new File("Test.scala") -> "object Test" ) { sources =>
|
||||
withTemporaryDirectory { temp =>
|
||||
|
|
@ -26,7 +26,7 @@ object CompileTest extends Specification
|
|||
}
|
||||
object WithCompiler
|
||||
{
|
||||
def apply[T](scalaVersion: String)(f: (AnalyzeCompiler, CompileLogger) => T): T =
|
||||
def apply[T](scalaVersion: String)(f: (AnalyzingCompiler, CompileLogger) => T): T =
|
||||
{
|
||||
System.setProperty("scala.home", "") // need to make sure scala.home is unset
|
||||
val log = new TestIvyLogger with CompileLogger
|
||||
|
|
@ -38,7 +38,7 @@ object WithCompiler
|
|||
val manager = new ComponentManager(launch.getSbtHome(sbtVersion, scalaVersion), log)
|
||||
prepare(manager, ComponentCompiler.compilerInterfaceSrcID, "CompilerInterface.scala")
|
||||
prepare(manager, ComponentCompiler.xsbtiID, classOf[xsbti.AnalysisCallback])
|
||||
f(AnalyzeCompiler(scalaVersion, launch, manager), log)
|
||||
f(AnalyzingCompiler(scalaVersion, launch, manager), log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ trait Compile extends TrackedTaskDefinition[CompileReport]
|
|||
protected def getTracked = Seq(trackedClasspath, trackedSource, trackedOptions, invalidation)
|
||||
}
|
||||
class StandardCompile(val sources: Task[Set[File]], val classpath: Task[Set[File]], val outputDirectory: Task[File], val options: Task[Seq[String]],
|
||||
val superclassNames: Task[Set[String]], val compilerTask: Task[AnalyzeCompiler], val cacheDirectory: File, val log: CompileLogger) extends Compile
|
||||
val superclassNames: Task[Set[String]], val compilerTask: Task[AnalyzingCompiler], val cacheDirectory: File, val log: CompileLogger) extends Compile
|
||||
{
|
||||
import Task._
|
||||
import scala.collection.mutable.{ArrayBuffer, Buffer, HashMap, HashSet, Map, Set => mSet}
|
||||
|
|
|
|||
Loading…
Reference in New Issue