implement embedded interface

This commit is contained in:
Mark Harrah 2012-04-18 16:01:45 -04:00
parent d837f869bd
commit 2bd103f1fa
10 changed files with 84 additions and 47 deletions

View File

@ -12,7 +12,7 @@ package compiler
* 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 provider: CompilerInterfaceProvider, val cp: ClasspathOptions, log: Logger)
class AnalyzingCompiler(val scalaInstance: xsbti.compile.ScalaInstance, val provider: CompilerInterfaceProvider, val cp: xsbti.compile.ClasspathOptions, log: Logger)
{
def this(scalaInstance: ScalaInstance, provider: CompilerInterfaceProvider, log: Logger) = this(scalaInstance, provider, ClasspathOptions.auto, log)
def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger)
@ -73,3 +73,34 @@ class AnalyzingCompiler(val scalaInstance: ScalaInstance, val provider: Compiler
}
override def toString = "Analyzing compiler (Scala " + scalaInstance.actualVersion + ")"
}
object AnalyzingCompiler
{
import sbt.IO.{copy, createDirectory, zip, jars, unzip, withTemporaryDirectory}
/** Extract sources from source jars, compile them with the xsbti interfaces on the classpath, and package the compiled classes and
* any resources from the source jars into a final jar.*/
def compileSources(sourceJars: Iterable[File], targetJar: File, xsbtiJars: Iterable[File], id: String, compiler: RawCompiler, log: Logger)
{
val isSource = (f: File) => isSourceName(f.getName)
def keepIfSource(files: Set[File]): Set[File] = if(files.exists(isSource)) files else Set()
withTemporaryDirectory { dir =>
val extractedSources = (Set[File]() /: sourceJars) { (extracted, sourceJar)=> extracted ++ keepIfSource(unzip(sourceJar, dir)) }
val (sourceFiles, resources) = extractedSources.partition(isSource)
withTemporaryDirectory { outputDirectory =>
log.info("'" + id + "' not yet compiled for Scala " + compiler.scalaInstance.actualVersion + ". Compiling...")
val start = System.currentTimeMillis
try
{
compiler(sourceFiles.toSeq, xsbtiJars.toSeq ++ sourceJars, outputDirectory, "-nowarn" :: Nil)
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 + "'") }
import sbt.Path._
copy(resources x rebase(dir, outputDirectory))
zip((outputDirectory ***) x_! relativeTo(outputDirectory), targetJar)
}
}
}
private def isSourceName(name: String): Boolean = name.endsWith(".scala") || name.endsWith(".java")
}

View File

@ -14,7 +14,7 @@ package compiler
* 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.*/
final class CompilerArguments(scalaInstance: ScalaInstance, cp: ClasspathOptions)
final class CompilerArguments(scalaInstance: xsbti.compile.ScalaInstance, cp: xsbti.compile.ClasspathOptions)
{
def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String]): Seq[String] =
{
@ -28,7 +28,7 @@ final class CompilerArguments(scalaInstance: ScalaInstance, cp: ClasspathOptions
options ++ outputOption ++ bootClasspathOption ++ classpathOption ++ abs(sources)
}
def finishClasspath(classpath: Seq[File]): Seq[File] =
filterLibrary(classpath) ++ include(cp.compiler, scalaInstance.compilerJar) ++ include(cp.extra, scalaInstance.extraJars : _*)
filterLibrary(classpath) ++ include(cp.compiler, scalaInstance.compilerJar) ++ include(cp.extra, scalaInstance.otherJars : _*)
private def include(flag: Boolean, jars: File*) = if(flag) jars else Nil
protected def abs(files: Seq[File]) = files.map(_.getAbsolutePath).sortWith(_ < _)
protected def checkScalaHomeUnset()

View File

@ -5,11 +5,11 @@ package compiler
trait CompilerInterfaceProvider
{
def apply(scalaInstance: ScalaInstance, log: Logger): File
def apply(scalaInstance: xsbti.compile.ScalaInstance, log: Logger): File
}
object CompilerInterfaceProvider
{
def constant(file: File): CompilerInterfaceProvider = new CompilerInterfaceProvider {
def apply(scalaInstance: ScalaInstance, log: Logger): File = file
def apply(scalaInstance: xsbti.compile.ScalaInstance, log: Logger): File = file
}
}

View File

@ -10,7 +10,7 @@ package compiler
* 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, cp: ClasspathOptions, log: Logger)
class RawCompiler(val scalaInstance: xsbti.compile.ScalaInstance, cp: ClasspathOptions, log: Logger)
{
def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])
{

View File

@ -20,11 +20,11 @@ import inc._
final class CompileConfiguration(val sources: Seq[File], val classpath: Seq[File],
val previousAnalysis: Analysis, val previousSetup: Option[CompileSetup], val currentSetup: CompileSetup, val getAnalysis: File => Option[Analysis], val definesClass: DefinesClass,
val maxErrors: Int, val compiler: AnalyzingCompiler, val javac: JavaCompiler)
val maxErrors: Int, val compiler: AnalyzingCompiler, val javac: xsbti.compile.JavaCompiler)
class AggressiveCompile(cacheFile: File)
{
def apply(compiler: AnalyzingCompiler, javac: JavaCompiler, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String] = Nil, javacOptions: Seq[String] = Nil, analysisMap: File => Option[Analysis] = const(None), definesClass: DefinesClass = Locate.definesClass _, maxErrors: Int = 100, compileOrder: CompileOrder = Mixed, skip: Boolean = false)(implicit log: Logger): Analysis =
def apply(compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String] = Nil, javacOptions: Seq[String] = Nil, analysisMap: File => Option[Analysis] = const(None), definesClass: DefinesClass = Locate.definesClass _, maxErrors: Int = 100, compileOrder: CompileOrder = Mixed, skip: Boolean = false)(implicit log: Logger): Analysis =
{
val setup = new CompileSetup(outputDirectory, new CompileOptions(options, javacOptions), compiler.scalaInstance.actualVersion, compileOrder)
compile1(sources, classpath, setup, store, analysisMap, definesClass, compiler, javac, maxErrors, skip)
@ -33,7 +33,7 @@ class AggressiveCompile(cacheFile: File)
def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] =
args.bootClasspath ++ args.finishClasspath(classpath)
def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: File => Option[Analysis], definesClass: DefinesClass, compiler: AnalyzingCompiler, javac: JavaCompiler, maxErrors: Int, skip: Boolean)(implicit log: Logger): Analysis =
def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: File => Option[Analysis], definesClass: DefinesClass, compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, maxErrors: Int, skip: Boolean)(implicit log: Logger): Analysis =
{
val (previousAnalysis, previousSetup) = extract(store.get())
if(skip)
@ -75,7 +75,7 @@ class AggressiveCompile(cacheFile: File)
val loader = ClasspathUtilities.toLoader(searchClasspath)
def readAPI(source: File, classes: Seq[Class[_]]) { callback.api(source, ClassToAPI(classes)) }
Analyze(outputDirectory, javaSrcs, log)(callback, loader, readAPI) {
javac(javaSrcs, absClasspath, outputDirectory, options.javacOptions)
javac.compile(javaSrcs.toArray, absClasspath.toArray, outputDirectory, options.javacOptions.toArray, maxErrors, log)
}
}
if(order == JavaThenScala) { compileJava(); compileScala() } else { compileScala(); compileJava() }

View File

@ -0,0 +1,32 @@
package sbt.compiler
import sbt.inc.Analysis
import xsbti.{Logger, Maybe}
import xsbti.compile._
import java.io.File
object IC extends IncrementalCompiler[Analysis, AnalyzingCompiler]
{
def compile(in: Inputs[Analysis, AnalyzingCompiler], log: Logger): Analysis =
{
val setup = in.setup; import setup._
val options = in.options; import options.{options => scalacOptions, _}
val compilers = in.compilers; import compilers._
val agg = new AggressiveCompile(setup.cacheFile)
val aMap = (f: File) => m2o(analysisMap(f))
val defClass = (f: File) => { val dc = definesClass(f); (name: String) => dc.apply(name) }
agg(scalac, javac, sources, classpath, classesDirectory, scalacOptions, javacOptions, aMap, defClass, maxErrors, order, skip)(log)
}
private[this] def m2o[S](opt: Maybe[S]): Option[S] = if(opt.isEmpty) None else Some(opt.get)
def newScalaCompiler(instance: ScalaInstance, interfaceJar: File, options: ClasspathOptions, log: Logger): AnalyzingCompiler =
new AnalyzingCompiler(instance, CompilerInterfaceProvider.constant(interfaceJar), options, log)
def compileInterfaceJar(label: String, sourceJar: File, targetJar: File, interfaceJar: File, instance: ScalaInstance, log: Logger)
{
val raw = new RawCompiler(instance, sbt.ClasspathOptions.auto, log)
AnalyzingCompiler.compileSources(sourceJar :: Nil, targetJar, interfaceJar :: Nil, label, raw, log)
}
}

View File

@ -17,7 +17,7 @@ object ComponentCompiler
def interfaceProvider(manager: ComponentManager): CompilerInterfaceProvider = new CompilerInterfaceProvider
{
def apply(scalaInstance: ScalaInstance, log: Logger): File =
def apply(scalaInstance: xsbti.compile.ScalaInstance, log: Logger): File =
{
// this is the instance used to compile the interface component
val componentCompiler = new ComponentCompiler(new RawCompiler(scalaInstance, ClasspathOptions.auto, log), manager)
@ -32,7 +32,6 @@ object ComponentCompiler
class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager)
{
import ComponentCompiler._
import sbt.IO.{copy, createDirectory, zip, jars, unzip, withTemporaryDirectory}
def apply(id: String): File =
try { getPrecompiled(id) }
catch { case _: InvalidComponent => getLocallyCompiled(id) }
@ -56,37 +55,11 @@ class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager)
protected def compileAndInstall(id: String, binID: String)
{
val srcID = id + srcExtension
withTemporaryDirectory { binaryDirectory =>
IO.withTemporaryDirectory { binaryDirectory =>
val targetJar = new File(binaryDirectory, id + ".jar")
compileSources(manager.files(srcID)(IfMissing.Fail), targetJar, id)
val xsbtiJars = manager.files(xsbtiID)(IfMissing.Fail)
AnalyzingCompiler.compileSources(manager.files(srcID)(IfMissing.Fail), targetJar, xsbtiJars, id, compiler, manager.log)
manager.define(binID, Seq(targetJar))
}
}
/** Extract sources from source jars, compile them with the xsbti interfaces on the classpath, and package the compiled classes and
* any resources from the source jars into a final jar.*/
private def compileSources(sourceJars: Iterable[File], targetJar: File, id: String)
{
val isSource = (f: File) => isSourceName(f.getName)
def keepIfSource(files: Set[File]): Set[File] = if(files.exists(isSource)) files else Set()
withTemporaryDirectory { dir =>
val extractedSources = (Set[File]() /: sourceJars) { (extracted, sourceJar)=> extracted ++ keepIfSource(unzip(sourceJar, dir)) }
val (sourceFiles, resources) = extractedSources.partition(isSource)
withTemporaryDirectory { outputDirectory =>
val xsbtiJars = manager.files(xsbtiID)(IfMissing.Fail)
manager.log.info("'" + id + "' not yet compiled for Scala " + compiler.scalaInstance.actualVersion + ". Compiling...")
val start = System.currentTimeMillis
try
{
compiler(sourceFiles.toSeq, xsbtiJars.toSeq ++ 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 + "'") }
import sbt.Path._
copy(resources x rebase(dir, outputDirectory))
zip((outputDirectory ***) x_! relativeTo(outputDirectory), targetJar)
}
}
}
private def isSourceName(name: String): Boolean = name.endsWith(".scala") || name.endsWith(".java")
}

View File

@ -52,9 +52,10 @@ public interface IncrementalCompiler<Analysis, ScalaCompiler>
* to create a ScalaCompiler for incremental compilation. It is the client's responsibility to manage compiled jars for
* different Scala versions.
*
* @param label A brief name describing the source component for use in error messages
* @param sourceJar The jar file containing the compiler interface sources. These are published as sbt's compiler-interface-src module.
* @param targetJar Where to create the output jar file containing the compiled classes.
* @param instance The ScalaInstance to compile the compiler interface for.
* @param log The logger to use during compilation. */
void compileInterfaceJar(File sourceJar, File targetJar, ScalaInstance instance, Logger log);
void compileInterfaceJar(String label, File sourceJar, File targetJar, File interfaceJar, ScalaInstance instance, Logger log);
}

View File

@ -17,8 +17,8 @@ public interface Setup<Analysis>
/** If true, no sources are actually compiled and the Analysis from the previous compilation is returned.*/
boolean skip();
/** The directory used to cache information across compilations.
* This directory can be removed to force a full recompilation.
* The directory should be unique and not shared between compilations. */
File cacheDirectory();
/** The file used to cache information across compilations.
* This file can be removed to force a full recompilation.
* The file should be unique and not shared between compilations. */
File cacheFile();
}

View File

@ -285,5 +285,5 @@ object BuildPaths
final val GlobalPluginsProperty = "sbt.global.plugins"
final val GlobalSettingsProperty = "sbt.global.settings"
def crossPath(base: File, instance: ScalaInstance): File = base / ("scala_" + instance.version)
def crossPath(base: File, instance: xsbti.compile.ScalaInstance): File = base / ("scala_" + instance.version)
}