support cross-compiling/bootstrapping

This commit is contained in:
Mark Harrah 2010-03-22 20:42:59 -04:00
parent 54bc694081
commit 7edcc68a92
10 changed files with 128 additions and 78 deletions

View File

@ -9,38 +9,26 @@ package xsbt
* provided by scalaInstance. 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 than the one used for this class, they must
* be compiled for the version of Scala being used.*/
class AnalyzingCompiler(val scalaInstance: ScalaInstance, val manager: ComponentManager, log: CompileLogger) extends NotNull
class AnalyzingCompiler(val scalaInstance: ScalaInstance, val manager: ComponentManager, val autoBootClasspath: Boolean, val compilerOnClasspath: Boolean, log: CompileLogger) 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)
def this(scalaInstance: ScalaInstance, manager: ComponentManager, log: CompileLogger) = this(scalaInstance, manager, true, true, log)
def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: CompileLogger)
{
val arguments = (new CompilerArguments(scalaInstance))(sources, classpath, outputDirectory, options, compilerOnClasspath)
val arguments = (new CompilerArguments(scalaInstance, autoBootClasspath, compilerOnClasspath))(sources, classpath, outputDirectory, options)
call("xsbt.CompilerInterface", log)(
classOf[Array[String]], classOf[AnalysisCallback], classOf[Int], classOf[xLogger] ) (
arguments.toArray[String] : Array[String], callback, maximumErrors: java.lang.Integer, log )
// this is commented out because of Scala ticket #2365
/*
val interface = getInterfaceClass(interface, log).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)*/
}
def doc(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], maximumErrors: Int, log: CompileLogger): Unit =
doc(sources, classpath, outputDirectory, options, false, maximumErrors, log)
def doc(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], compilerOnClasspath: Boolean,
maximumErrors: Int, log: CompileLogger): Unit =
{
val arguments = (new CompilerArguments(scalaInstance))(sources, classpath, outputDirectory, options, compilerOnClasspath)
val arguments = (new CompilerArguments(scalaInstance, autoBootClasspath, compilerOnClasspath))(sources, classpath, outputDirectory, options)
call("xsbt.ScaladocInterface", log) (classOf[Array[String]], classOf[Int], classOf[xLogger]) (arguments.toArray[String] : Array[String], maximumErrors: java.lang.Integer, log)
}
def console(classpath: Set[File], initialCommands: String, log: CompileLogger): Unit =
{
val arguments = new CompilerArguments(scalaInstance)
val classpathString = CompilerArguments.absString(arguments.finishClasspath(classpath, true))
val bootClasspath = arguments.createBootClasspath
val arguments = new CompilerArguments(scalaInstance, autoBootClasspath, compilerOnClasspath)
val classpathString = CompilerArguments.absString(arguments.finishClasspath(classpath))
val bootClasspath = if(autoBootClasspath) arguments.createBootClasspath else ""
call("xsbt.ConsoleInterface", log) (classOf[String], classOf[String], classOf[String], classOf[xLogger]) (bootClasspath, classpathString, initialCommands, log)
}
def force(log: CompileLogger): Unit = getInterfaceJar(log)
@ -66,7 +54,7 @@ class AnalyzingCompiler(val scalaInstance: ScalaInstance, val manager: Component
log.debug("Getting " + ComponentCompiler.compilerInterfaceID + " from component compiler for Scala " + scalaInstance.version)
componentCompiler(ComponentCompiler.compilerInterfaceID)
}
def newComponentCompiler(log: CompileLogger) = new ComponentCompiler(new RawCompiler(scalaInstance, log), manager)
def newComponentCompiler(log: CompileLogger) = new ComponentCompiler(new RawCompiler(scalaInstance, true, true, log), manager)
protected def createDualLoader(scalaLoader: ClassLoader, sbtLoader: ClassLoader): ClassLoader =
{
val xsbtiFilter = (name: String) => name.startsWith("xsbti.")

View File

@ -8,18 +8,18 @@ package xsbt
* 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)
class CompilerArguments(scalaInstance: ScalaInstance, autoBootClasspath: Boolean, compilerOnClasspath: Boolean)
{
def apply(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]): Seq[String] =
{
checkScalaHomeUnset()
val bootClasspathOption = Seq("-bootclasspath", createBootClasspath)
val cpWithCompiler = finishClasspath(classpath, compilerOnClasspath)
val bootClasspathOption = if(autoBootClasspath) Seq("-bootclasspath", createBootClasspath) else Nil
val cpWithCompiler = finishClasspath(classpath)
val classpathOption = Seq("-cp", absString(cpWithCompiler) )
val outputOption = Seq("-d", outputDirectory.getAbsolutePath)
options ++ outputOption ++ bootClasspathOption ++ classpathOption ++ abs(sources)
}
def finishClasspath(classpath: Set[File], compilerOnClasspath: Boolean): Set[File] =
def finishClasspath(classpath: Set[File]): Set[File] =
classpath ++ (if(compilerOnClasspath) scalaInstance.compilerJar :: Nil else Nil)
protected def abs(files: Set[File]) = files.map(_.getAbsolutePath)
protected def checkScalaHomeUnset()

View File

@ -49,7 +49,7 @@ class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager)
val start = System.currentTimeMillis
try
{
compiler(Set() ++ sourceFiles, Set() ++ xsbtiJars ++ sourceJars, outputDirectory, "-nowarn" :: Nil, true)
compiler(Set() ++ sourceFiles, Set() ++ xsbtiJars ++ sourceJars, outputDirectory, "-nowarn" :: Nil)
manager.log.info(" Compilation completed in " + (System.currentTimeMillis - start) / 1000.0 + " s")
}
catch { case e: xsbti.CompileFailed => throw new CompileFailed(e.arguments, "Error compiling sbt component '" + id + "'") }

View File

@ -3,30 +3,29 @@ 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)
* is used, for example, to compile the interface/plugin code.
* If `explicitClasspath` is true, the bootclasspath and classpath are not augmented. If it is false,
* the scala-library.jar from `scalaInstance` is put on bootclasspath and the scala-compiler jar goes on the classpath.*/
class RawCompiler(val scalaInstance: ScalaInstance, autoBootClasspath: Boolean, compilerOnClasspath: Boolean, 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)
def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String])
{
// reflection is required for binary compatibility
// The following imports ensure there is a compile error if the identifiers change,
// The following import ensures 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.Main.{process => _}
val arguments = (new CompilerArguments(scalaInstance))(sources, classpath, outputDirectory, options, compilerOnClasspath)
val arguments = compilerArguments(sources, classpath, outputDirectory, options)
log.debug("Plain 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)
}
def compilerArguments = new CompilerArguments(scalaInstance, autoBootClasspath, compilerOnClasspath)
protected def checkForFailure(mainClass: Class[_], args: Array[String])
{
val reporter = mainClass.getMethod("reporter").invoke(null)
// this is commented out because of Scala ticket #2365
//val failed = reporter.asInstanceOf[{ def hasErrors: Boolean }].hasErrors
val failed = reporter.getClass.getMethod("hasErrors").invoke(reporter).asInstanceOf[Boolean]
if(failed) throw new CompileFailed(args, "Plain compile failed")
}

View File

@ -7,10 +7,10 @@ package xsbt
* 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
final class ScalaInstance(val version: String, val loader: ClassLoader, val libraryJar: File, val compilerJar: File, val extraJars: Seq[File]) extends NotNull
{
require(version.indexOf(' ') < 0, "Version cannot contain spaces (was '" + version + "')")
def jars = libraryJar :: compilerJar :: Nil
def jars = libraryJar :: compilerJar :: extraJars.toList
/** 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 = ScalaInstance.actualVersion(loader)(" version " + version)
override def toString = "Scala instance{version label " + version + ", actual version " + actualVersion + ", library jar: " + libraryJar + ", compiler jar: " + compilerJar + "}"
@ -22,20 +22,20 @@ object ScalaInstance
def apply(version: String, launcher: xsbti.Launcher): ScalaInstance =
apply(version, launcher.getScala(version))
def apply(version: String, provider: xsbti.ScalaProvider): ScalaInstance =
new ScalaInstance(version, provider.loader, provider.libraryJar, provider.compilerJar)
new ScalaInstance(version, provider.loader, provider.libraryJar, provider.compilerJar, Nil)
def apply(scalaHome: File, launcher: xsbti.Launcher): ScalaInstance =
apply(libraryJar(scalaHome), compilerJar(scalaHome), launcher)
def apply(version: String, scalaHome: File, launcher: xsbti.Launcher): ScalaInstance =
apply(version, libraryJar(scalaHome), compilerJar(scalaHome), launcher)
def apply(libraryJar: File, compilerJar: File, launcher: xsbti.Launcher): ScalaInstance =
def apply(libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance =
{
val loader = scalaLoader(launcher, libraryJar, compilerJar)
val loader = scalaLoader(launcher, libraryJar :: compilerJar :: extraJars.toList)
val version = actualVersion(loader)(" (library jar " + libraryJar.getAbsolutePath + ")")
new ScalaInstance(version, loader, libraryJar, compilerJar)
new ScalaInstance(version, loader, libraryJar, compilerJar, extraJars)
}
def apply(version: String, libraryJar: File, compilerJar: File, launcher: xsbti.Launcher): ScalaInstance =
new ScalaInstance(version, scalaLoader(launcher, libraryJar, compilerJar), libraryJar, compilerJar)
def apply(version: String, libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance =
new ScalaInstance(version, scalaLoader(launcher, libraryJar :: compilerJar :: extraJars.toList), libraryJar, compilerJar, extraJars)
private def compilerJar(scalaHome: File) = scalaJar(scalaHome, "scala-compiler.jar")
private def libraryJar(scalaHome: File) = scalaJar(scalaHome, "scala-library.jar")
@ -50,7 +50,7 @@ object ScalaInstance
}
import java.net.{URL, URLClassLoader}
private def scalaLoader(launcher: xsbti.Launcher, jars: File*): ClassLoader =
private def scalaLoader(launcher: xsbti.Launcher, jars: Seq[File]): ClassLoader =
new URLClassLoader(jars.map(_.toURI.toURL).toArray[URL], launcher.topLoader)
}
class InvalidScalaInstance(message: String, cause: Throwable) extends RuntimeException(message, cause)

View File

@ -19,6 +19,7 @@ object Analyzer
final class Analyzer(val global: Global, val callback: AnalysisCallback) extends NotNull
{
import global._
import Compat.{linkedClass, nameString}
def newPhase(prev: Phase): Phase = new AnalyzerPhase(prev)
private class AnalyzerPhase(prev: Phase) extends Phase(prev)
@ -67,9 +68,9 @@ final class Analyzer(val global: Global, val callback: AnalysisCallback) extends
{
val isModule = sym.isModuleClass
for(superclass <- superclasses.filter(sym.isSubClass))
callback.foundSubclass(sourceFile, NameString(sym), NameString(superclass), isModule)
callback.foundSubclass(sourceFile, nameString(sym), nameString(superclass), isModule)
if(isModule && hasMainMethod(sym))
callback.foundApplication(sourceFile, NameString(sym))
callback.foundApplication(sourceFile, nameString(sym))
}
}
@ -85,7 +86,7 @@ final class Analyzer(val global: Global, val callback: AnalysisCallback) extends
}
if(sym.isModuleClass && !sym.isImplClass)
{
if(isTopLevelModule(sym) && sym.linkedClassOfModule == NoSymbol)
if(isTopLevelModule(sym) && linkedClass(sym) == NoSymbol)
addGenerated(false)
addGenerated(true)
}
@ -118,7 +119,7 @@ final class Analyzer(val global: Global, val callback: AnalysisCallback) extends
finder.findClass(name) orElse {
if(isTopLevelModule(sym))
{
val linked = sym.linkedClassOfModule
val linked = linkedClass(sym)
if(linked == NoSymbol)
None
else
@ -133,7 +134,7 @@ final class Analyzer(val global: Global, val callback: AnalysisCallback) extends
private def moduleSuffix(sym: Symbol) =
if (sym.hasFlag(Flags.MODULE) && !sym.isMethod && !sym.isImplClass && !sym.hasFlag(Flags.JAVA)) "$" else "";
private def flatname(s: Symbol, separator: Char) =
atPhase(currentRun.flattenPhase.next) { NameString(s, separator) }
atPhase(currentRun.flattenPhase.next) { nameString(s, separator) }
private def isTopLevelModule(sym: Symbol): Boolean =
atPhase (currentRun.picklerPhase.next) {
@ -204,19 +205,24 @@ final class Analyzer(val global: Global, val callback: AnalysisCallback) extends
if(entry eq null) None else Some(entry.classFile)
}
}
}
private object NameString
{
def apply(s: Global#Symbol): String = s.fullNameString
def apply(s: Global#Symbol, sep: Char): String = s.fullNameString(sep)
/** After 2.8.0.Beta1, fullNameString was renamed fullName.*/
private implicit def symName(sym: Global#Symbol): WithString = new WithString(sym)
private final class WithString(s: Global#Symbol)
private object Compat
{
def fullNameString = s.fullName; def fullName = sourceCompatibilityOnly
def fullNameString(sep: Char) = s.fullName(sep); def fullName(sep: Char) = sourceCompatibilityOnly
private def sourceCompatibilityOnly = error("For source compatibility only: should not get here.")
}
def nameString(s: Symbol): String = s.fullNameString
def nameString(s: Symbol, sep: Char): String = s.fullNameString(sep)
def linkedClass(s: Symbol): Symbol = s.linkedClassOfModule
/** After 2.8.0.Beta1, fullNameString was renamed fullName.
* linkedClassOfModule was renamed companionClass. */
private implicit def symName(sym: Symbol): WithString = new WithString(sym)
private final class WithString(s: Symbol)
{
def fullNameString = s.fullName; def fullName = sourceCompatibilityOnly
def fullNameString(sep: Char) = s.fullName(sep); def fullName(sep: Char) = sourceCompatibilityOnly
private def sourceCompatibilityOnly = error("For source compatibility only: should not get here.")
def linkedClassOfModule = s.companionClass; def companionClass = sourceCompatibilityOnly
}
}
}

View File

@ -11,7 +11,8 @@ class ConsoleInterface
def run(bootClasspathString: String, classpathString: String, initialCommands: String, log: Logger)
{
val settings = Settings(log)
settings.bootclasspath.value = bootClasspathString
if(!bootClasspathString.isEmpty)
settings.bootclasspath.value = bootClasspathString
settings.classpath.value = classpathString
log.info(Message("Starting scala interpreter..."))
log.debug(Message(" Classpath: " + settings.classpath.value))

View File

@ -4,7 +4,6 @@
package xsbt
import xsbti.Logger
import scala.tools.nsc.SubComponent
import Log.debug
class ScaladocInterface

View File

@ -0,0 +1,4 @@
#Project properties
#Mon Mar 22 18:24:15 EDT 2010
project.name=Compile
sbt.version=0.7.1

View File

@ -8,7 +8,7 @@ import org.specs._
object CompileTest extends Specification
{
"Analysis compiler" should {
/*"Analysis compiler" should {
"compile basic sources" in {
WithCompiler( "2.7.2" )(testCompileAnalysis)
WithCompiler( "2.7.3" )(testCompileAnalysis)
@ -18,7 +18,16 @@ object CompileTest extends Specification
WithCompiler( "2.8.0.Beta1" )(testCompileAnalysis)
WithCompiler( "2.8.0-SNAPSHOT" )(testCompileAnalysis)
}
}*/
"Raw compiler" should {
"Properly handle classpaths" in {
testClasspath("2.7.2")
testClasspath("2.7.7")
testClasspath("2.8.0.Beta1")
}
}
private def testCompileAnalysis(compiler: AnalyzingCompiler, log: CompileLogger)
{
WithFiles( new File("Test.scala") -> "object Test" ) { sources =>
@ -29,25 +38,69 @@ object CompileTest extends Specification
}
}
}
val UsingCompiler = "object Test { classOf[scala.tools.nsc.Global] }"
private def shouldFail(act: => Unit) =
{
val success = try { act; true } catch { case e: Exception => false }
if(success) error("Expected exception not thrown")
}
private def isMissingRequirementError(t: Throwable) = t.getClass.getName == "scala.tools.nsc.MissingRequirementError"
private def testClasspath(scalaVersion: String) =
WithCompiler.launcher { (launch, log) =>
def compiler(autoBoot: Boolean, compilerOnClasspath: Boolean): RawCompiler =
new RawCompiler(ScalaInstance(scalaVersion, launch), autoBoot, compilerOnClasspath, log)
val callback = new xsbti.TestCallback(Array())
val standard = compiler(true, true)
val noCompiler = compiler(true, false)
val fullExplicit = compiler(false, false)
val fullBoot = "-bootclasspath" :: fullExplicit.compilerArguments.createBootClasspath :: Nil
val withCompiler = Set() + noCompiler.scalaInstance.compilerJar
WithFiles( new File("Test.scala") -> "object Test", new File("Test2.scala") -> UsingCompiler ) { case Array(plain, useCompiler) =>
val plainSrcs = Set[File](plain)
val compSrcs = Set[File](useCompiler)
FileUtilities.withTemporaryDirectory { out =>
standard(plainSrcs, Set.empty, out, Nil) //success
standard(compSrcs, Set.empty, out, Nil) //success
noCompiler(plainSrcs, Set.empty, out, Nil) //success
shouldFail( noCompiler(compSrcs, Set.empty, out, Nil) )
noCompiler(compSrcs, withCompiler, out, Nil) //success
shouldFail( fullExplicit(plainSrcs, Set.empty, out, Nil) )// failure
shouldFail( fullExplicit(compSrcs, Set.empty, out, Nil) )// failure
fullExplicit(plainSrcs, Set.empty, out, fullBoot) // success
fullExplicit(compSrcs, withCompiler, out, fullBoot) // success
}
}
}
}
object WithCompiler
{
def apply[T](scalaVersion: String)(f: (AnalyzingCompiler, CompileLogger) => T): T =
{
System.setProperty("scala.home", "") // need to make sure scala.home is unset
launcher { (launch, log) =>
FileUtilities.withTemporaryDirectory { componentDirectory =>
val manager = new ComponentManager(xsbt.boot.Locks, new boot.ComponentProvider(componentDirectory), log)
val compiler = new AnalyzingCompiler(ScalaInstance(scalaVersion, launch), manager, log)
compiler.newComponentCompiler(log).clearCache(ComponentCompiler.compilerInterfaceID)
define(manager, ComponentCompiler.compilerInterfaceSrcID, getResource("CompilerInterface.scala"), getClassResource(classOf[jline.Completor]))
define(manager, ComponentCompiler.xsbtiID, getClassResource(classOf[xsbti.AnalysisCallback]))
f(compiler, log)
}
}
}
def launcher[T](f: (xsbti.Launcher, TestIvyLogger with CompileLogger) => T): T =
{
val log = new TestIvyLogger with CompileLogger
log.setLevel(Level.Debug)
log.bufferQuietly {
boot.LaunchTest.withLauncher { launch =>
FileUtilities.withTemporaryDirectory { componentDirectory =>
val manager = new ComponentManager(xsbt.boot.Locks, new boot.ComponentProvider(componentDirectory), log)
val compiler = new AnalyzingCompiler(ScalaInstance(scalaVersion, launch), manager, log)
compiler.newComponentCompiler(log).clearCache(ComponentCompiler.compilerInterfaceID)
define(manager, ComponentCompiler.compilerInterfaceSrcID, getResource("CompilerInterface.scala"), getClassResource(classOf[jline.Completor]))
define(manager, ComponentCompiler.xsbtiID, getClassResource(classOf[xsbti.AnalysisCallback]))
f(compiler, log)
}
}
boot.LaunchTest.withLauncher { launch => f(launch, log) }
}
}
def getClassResource(resource: Class[_]): File = FileUtilities.classLocationFile(resource)