Mostly working cross-compile task.

Analyzer plugin is now a proper internal phase to get around bootstrapping issues.
Correctly handle source tags.
This commit is contained in:
Mark Harrah 2009-09-03 23:40:47 -04:00
parent edca6620e4
commit 12c5f5a0d5
10 changed files with 135 additions and 92 deletions

9
cache/CacheIO.scala vendored
View File

@ -6,6 +6,15 @@ import scala.reflect.Manifest
object CacheIO object CacheIO
{ {
def toBytes[T](format: Format[T])(value: T)(implicit mf: Manifest[Format[T]]): Array[Byte] =
toBytes[T](value)(format, mf)
def toBytes[T](value: T)(implicit format: Format[T], mf: Manifest[Format[T]]): Array[Byte] =
Operations.toByteArray(value)(stampedFormat(format))
def fromBytes[T](format: Format[T], default: => T)(bytes: Array[Byte])(implicit mf: Manifest[Format[T]]): T =
fromBytes(default)(bytes)(format, mf)
def fromBytes[T](default: => T)(bytes: Array[Byte])(implicit format: Format[T], mf: Manifest[Format[T]]): T =
if(bytes.isEmpty) default else Operations.fromByteArray(bytes)(stampedFormat(format))
def fromFile[T](format: Format[T], default: => T)(file: File)(implicit mf: Manifest[Format[T]]): T = def fromFile[T](format: Format[T], default: => T)(file: File)(implicit mf: Manifest[Format[T]]): T =
fromFile(file, default)(format, mf) fromFile(file, default)(format, mf)
def fromFile[T](file: File, default: => T)(implicit format: Format[T], mf: Manifest[Format[T]]): T = def fromFile[T](file: File, default: => T)(implicit format: Format[T], mf: Manifest[Format[T]]): T =

View File

@ -37,17 +37,17 @@ class AnalyzeCompiler(scalaVersion: String, scalaLoader: ClassLoader, manager: C
* and used to load the class that actually interfaces with Global.*/ * and used to load the class that actually interfaces with Global.*/
def apply(arguments: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger) def apply(arguments: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger)
{ {
println("Compiling: " + arguments)
// this is the instance used to compile the analysis // this is the instance used to compile the analysis
val componentCompiler = new ComponentCompiler(scalaVersion, new RawCompiler(scalaLoader), manager) val componentCompiler = new ComponentCompiler(scalaVersion, new RawCompiler(scalaLoader), manager)
val interfaceJar = componentCompiler(ComponentCompiler.compilerInterfaceID) val interfaceJar = componentCompiler(ComponentCompiler.compilerInterfaceID)
val argsWithPlugin = ("-Xplugin:" + interfaceJar.getAbsolutePath) :: arguments.toList
val dual = createDualLoader(scalaLoader, getClass.getClassLoader) // this goes to scalaLoader for scala classes and sbtLoader for xsbti classes 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 interfaceLoader = new URLClassLoader(Array(interfaceJar.toURI.toURL), dual)
val interface = Class.forName("xsbt.CompilerInterface", true, interfaceLoader).newInstance val interface = Class.forName("xsbt.CompilerInterface", true, interfaceLoader).newInstance
val runnable = interface.asInstanceOf[{ def run(args: Array[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger): Unit }] val runnable = interface.asInstanceOf[{ def run(args: Array[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger): Unit }]
// these arguments are safe to pass across the ClassLoader boundary because the types are defined in Java // 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 // so they will be binary compatible across all versions of Scala
runnable.run(argsWithPlugin.toArray, callback, maximumErrors, log) runnable.run(arguments.toArray, callback, maximumErrors, log)
} }
private def createDualLoader(scalaLoader: ClassLoader, sbtLoader: ClassLoader): ClassLoader = private def createDualLoader(scalaLoader: ClassLoader, sbtLoader: ClassLoader): ClassLoader =
{ {

View File

@ -10,46 +10,21 @@ import symtab.Flags
import scala.collection.mutable.{HashMap, HashSet, Map, Set} import scala.collection.mutable.{HashMap, HashSet, Map, Set}
import java.io.File import java.io.File
import xsbti.{AnalysisCallback, AnalysisCallbackContainer} import xsbti.AnalysisCallback
class Analyzer(val global: Global) extends Plugin object Analyzer
{
def name = "xsbt-analyzer"
}
final class Analyzer(val global: Global, val callback: AnalysisCallback) extends NotNull
{ {
val callback = global.asInstanceOf[AnalysisCallbackContainer].analysisCallback
import global._ import global._
val name = "xsbt-analyze" def newPhase(prev: Phase): Phase = new AnalyzerPhase(prev)
val description = "A plugin to find all concrete instances of a given class and extract dependency information."
val components = List[PluginComponent](Component)
/* ================================================== */
// These two templates abuse scope for source compatibility between Scala 2.7.x and 2.8.x so that a single
// sbt codebase compiles with both series of versions.
// In 2.8.x, PluginComponent.runsAfter has type List[String] and the method runsBefore is defined on
// PluginComponent with default value Nil.
// In 2.7.x, runsBefore does not exist on PluginComponent and PluginComponent.runsAfter has type String.
//
// Therefore, in 2.8.x, object runsBefore is shadowed by PluginComponent.runsBefore (which is Nil) and so
// afterPhase :: runsBefore
// is equivalent to List[String](afterPhase)
// In 2.7.x, object runsBefore is not shadowed and so runsAfter has type String.
private object runsBefore { def :: (s: String) = s }
private abstract class CompatiblePluginComponent(afterPhase: String) extends PluginComponent
{
override val runsAfter = afterPhase :: runsBefore
}
/* ================================================== */
private object Component extends CompatiblePluginComponent("jvm")
{
val global = Analyzer.this.global
val phaseName = Analyzer.this.name
def newPhase(prev: Phase) = new AnalyzerPhase(prev)
}
private class AnalyzerPhase(prev: Phase) extends Phase(prev) private class AnalyzerPhase(prev: Phase) extends Phase(prev)
{ {
def name = Analyzer.this.name override def description = "A plugin to find all concrete instances of a given class and extract dependency information."
def name = Analyzer.name
def run def run
{ {
val outputDirectory = new File(global.settings.outdir.value) val outputDirectory = new File(global.settings.outdir.value)

View File

@ -3,7 +3,8 @@
*/ */
package xsbt package xsbt
import xsbti.{AnalysisCallback,AnalysisCallbackContainer,Logger} import xsbti.{AnalysisCallback,Logger}
import scala.tools.nsc.{Phase, SubComponent}
class CompilerInterface class CompilerInterface
{ {
@ -14,10 +15,28 @@ class CompilerInterface
val reporter = new LoggerReporter(maximumErrors, log) val reporter = new LoggerReporter(maximumErrors, log)
val settings = new Settings(reporter.error) val settings = new Settings(reporter.error)
val command = new CompilerCommand(args.toList, settings, error, false) val command = new CompilerCommand(args.toList, settings, error, false)
object compiler extends Global(command.settings, reporter) with AnalysisCallbackContainer object compiler extends Global(command.settings, reporter)
{ {
def analysisCallback = callback object sbtAnalyzer extends
{
val global: compiler.type = compiler
val phaseName = Analyzer.name
val runsAfter = List("jvm")
val runsRightAfter = None
}
with SubComponent
{
val analyzer = new Analyzer(global, callback)
def newPhase(prev: Phase) = analyzer.newPhase(prev)
def name = phaseName
}
override protected def builtInPhaseDescriptors() = (super.builtInPhaseDescriptors ++ Seq(sbtAnalyzer))
/*override protected def computeInternalPhases()
{
super.computeInternalPhases()
phasesSet += sbtAnalyzer
}*/
} }
if(!reporter.hasErrors) if(!reporter.hasErrors)
{ {

View File

@ -1,4 +0,0 @@
<plugin>
<name>xsbt-analyze</name>
<classname>xsbt.Analyzer</classname>
</plugin>

View File

@ -3,7 +3,7 @@ package xsbt
import java.io.File import java.io.File
import java.net.URLClassLoader import java.net.URLClassLoader
import xsbti.{Logger, TestCallback, TestLogger} import xsbti.{Logger, TestCallback, TestLogger}
import FileUtilities.{classLocationFile, withTemporaryDirectory, write} import FileUtilities.withTemporaryDirectory
object TestCompile object TestCompile
{ {
@ -11,15 +11,10 @@ object TestCompile
* that the plugin sends it for post-compile analysis by the provided function.*/ * that the plugin sends it for post-compile analysis by the provided function.*/
def apply[T](arguments: Seq[String], superclassNames: Seq[String])(f: (TestCallback, Logger) => T): T = def apply[T](arguments: Seq[String], superclassNames: Seq[String])(f: (TestCallback, Logger) => T): T =
{ {
val pluginLocation = classLocationFile[Analyzer]
assert(pluginLocation.exists)
val path = pluginLocation.getAbsolutePath
val pluginArg = if(pluginLocation.getName.endsWith(".jar")) List("-Xplugin:" + path) else List("-Xpluginsdir", path)
val testCallback = new TestCallback(superclassNames.toArray) val testCallback = new TestCallback(superclassNames.toArray)
val i = new CompilerInterface val i = new CompilerInterface
val newArgs = "-Xplugin-require:xsbt-analyze" :: pluginArg ::: arguments.toList
TestLogger { log => TestLogger { log =>
i.run(newArgs.toArray, testCallback, 5, log) i.run(arguments.toArray, testCallback, 5, log)
f(testCallback, log) f(testCallback, log)
} }
} }

View File

@ -1,12 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package xsbti;
/** Provides access to an AnalysisCallback. This is used by the plugin to
* get the callback to use. The scalac Global instance it is passed must
* implement this interface. */
public interface AnalysisCallbackContainer
{
public AnalysisCallback analysisCallback();
}

View File

@ -24,7 +24,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
val trackingSub = project(cachePath / "tracking", "Tracking", new Base(_), cacheSub) val trackingSub = project(cachePath / "tracking", "Tracking", new Base(_), cacheSub)
val compilerSub = project(compilePath, "Compile", new CompileProject(_), val compilerSub = project(compilePath, "Compile", new CompileProject(_),
launchInterfaceSub, interfaceSub, ivySub, ioSub, classpathSub, compileInterfaceSub) launchInterfaceSub, interfaceSub, ivySub, ioSub, classpathSub, compileInterfaceSub)
val stdTaskSub = project(tasksPath / "standard", "Standard Tasks", new Base(_), trackingSub, compilerSub) val stdTaskSub = project(tasksPath / "standard", "Standard Tasks", new StandardTaskProject(_), trackingSub, compilerSub)
/* Multi-subproject paths */ /* Multi-subproject paths */
@ -56,6 +56,10 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
val sp = "org.scala-tools.testing" % "specs" % "1.5.0" % "test->default" val sp = "org.scala-tools.testing" % "specs" % "1.5.0" % "test->default"
val ju = "junit" % "junit" % "4.5" % "test->default" // required by specs to compile properly val ju = "junit" % "junit" % "4.5" % "test->default" // required by specs to compile properly
} }
class StandardTaskProject(info: ProjectInfo) extends Base(info)
{
override def testClasspath = super.testClasspath +++ compilerSub.testClasspath
}
class IOProject(info: ProjectInfo) extends Base(info) with TestDependencies class IOProject(info: ProjectInfo) extends Base(info) with TestDependencies
class TaskProject(info: ProjectInfo) extends Base(info) with TestDependencies class TaskProject(info: ProjectInfo) extends Base(info) with TestDependencies
@ -67,6 +71,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
class Base(info: ProjectInfo) extends DefaultProject(info) with ManagedBase class Base(info: ProjectInfo) extends DefaultProject(info) with ManagedBase
{ {
override def scratch = true override def scratch = true
override def consoleClasspath = testClasspath
} }
class CompileProject(info: ProjectInfo) extends Base(info) class CompileProject(info: ProjectInfo) extends Base(info)
{ {
@ -86,17 +91,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
override def mainSources = descendents(mainSourceRoots, "*.java") override def mainSources = descendents(mainSourceRoots, "*.java")
override def compileOrder = CompileOrder.JavaThenScala override def compileOrder = CompileOrder.JavaThenScala
} }
class CompilerInterfaceProject(info: ProjectInfo) extends Base(info) with SourceProject class CompilerInterfaceProject(info: ProjectInfo) extends Base(info) with SourceProject with TestWithIO
{
// these set up the test environment so that the classes and resources are both in the output resource directory
// the main compile path is removed so that the plugin (xsbt.Analyzer) is found in the output resource directory so that
// the tests can configure that directory as -Xpluginsdir (which requires the scalac-plugin.xml and the classes to be together)
override def testCompileAction = super.testCompileAction dependsOn(packageForTest, ioSub.testCompile)
override def mainResources = super.mainResources +++ "scalac-plugin.xml"
override def testClasspath = (super.testClasspath --- super.mainCompilePath) +++ ioSub.testClasspath +++ testPackagePath
def testPackagePath = outputPath / "test.jar"
lazy val packageForTest = packageTask(mainClasses +++ mainResources, testPackagePath, packageOptions).dependsOn(compile)
}
trait TestWithIO extends BasicScalaProject trait TestWithIO extends BasicScalaProject
{ {
// use IO from tests // use IO from tests

View File

@ -24,7 +24,7 @@ trait Compile extends TrackedTaskDefinition[CompileReport]
val newOpts = (opts: Seq[String]) => (opts, rawSourceChanges.markAllModified, rawClasspathChanges.markAllModified) // if options changed, mark everything changed val newOpts = (opts: Seq[String]) => (opts, rawSourceChanges.markAllModified, rawClasspathChanges.markAllModified) // if options changed, mark everything changed
val sameOpts = (opts: Seq[String]) => (opts, rawSourceChanges, rawClasspathChanges) val sameOpts = (opts: Seq[String]) => (opts, rawSourceChanges, rawClasspathChanges)
trackedOptions(newOpts, sameOpts) bind { // detect changes to options trackedOptions(newOpts, sameOpts) bind { // detect changes to options
case (options, classpathChanges, sourceChanges) => case (options, sourceChanges, classpathChanges) =>
invalidation( classpathChanges +++ sourceChanges ) { (report, tracking) => // invalidation based on changes invalidation( classpathChanges +++ sourceChanges ) { (report, tracking) => // invalidation based on changes
compile(sourceChanges, classpathChanges, options, report, tracking) compile(sourceChanges, classpathChanges, options, report, tracking)
} }
@ -40,26 +40,27 @@ class StandardCompile(val sources: Task[Set[File]], val classpath: Task[Set[File
val superclassNames: Task[Set[String]], val compilerTask: Task[AnalyzeCompiler], val cacheDirectory: File, val log: xsbti.Logger) extends Compile val superclassNames: Task[Set[String]], val compilerTask: Task[AnalyzeCompiler], val cacheDirectory: File, val log: xsbti.Logger) extends Compile
{ {
import Task._ import Task._
import sbinary.{DefaultProtocol, Format, Operations}
import DefaultProtocol._
import Operations.{fromByteArray, toByteArray}
import scala.collection.mutable.{ArrayBuffer, Buffer, HashMap, HashSet, Map, Set => mSet} import scala.collection.mutable.{ArrayBuffer, Buffer, HashMap, HashSet, Map, Set => mSet}
private implicit val subclassFormat: Format[DetectedSubclass] =
asProduct4(DetectedSubclass.apply)( ds => Some(ds.source, ds.subclassName, ds.superclassName, ds.isModule))
override def create = super.create dependsOn(superclassNames, compilerTask) // raise these dependencies to the top for parallelism override def create = super.create dependsOn(superclassNames, compilerTask) // raise these dependencies to the top for parallelism
def compile(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[File], options: Seq[String], report: InvalidationReport[File], tracking: UpdateTracking[File]): Task[CompileReport] = def compile(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[File], options: Seq[String], report: InvalidationReport[File], tracking: UpdateTracking[File]): Task[CompileReport] =
{ {
val sources = report.invalid ** sourceChanges.checked // determine the sources that need recompiling (report.invalid also contains classes and libraries) val sources = report.invalid ** sourceChanges.checked // determine the sources that need recompiling (report.invalid also contains classes and libraries)
val classpath = classpathChanges.checked val classpath = classpathChanges.checked
compile(sources, classpath, options, tracking)
}
def compile(sources: Set[File], classpath: Set[File], options: Seq[String], tracking: UpdateTracking[File]): Task[CompileReport] =
{
(compilerTask, superclassNames) map { (compiler, superClasses) => (compilerTask, superclassNames) map { (compiler, superClasses) =>
val callback = new CompileAnalysisCallback(superClasses.toArray, tracking) if(!sources.isEmpty)
val arguments = Seq("-cp", abs(classpath).mkString(File.pathSeparator)) ++ options ++ abs(sources).toSeq {
val callback = new CompileAnalysisCallback(superClasses.toArray, tracking)
compiler(arguments, callback, 100, log) val classpathString = abs(classpath).mkString(File.pathSeparator)
val classpathOption = if(classpathString.isEmpty) Seq.empty else Seq("-cp", classpathString)
val arguments = classpathOption ++ options ++ abs(sources).toSeq
compiler(arguments, callback, 100, log)
}
val readTracking = tracking.read val readTracking = tracking.read
val applicationSet = new HashSet[String] val applicationSet = new HashSet[String]
val subclassMap = new HashMap[String, Buffer[DetectedSubclass]] val subclassMap = new HashMap[String, Buffer[DetectedSubclass]]
@ -78,14 +79,14 @@ class StandardCompile(val sources: Task[Set[File]], val classpath: Task[Set[File
{ {
for((source, tag) <- readTracking.allTags) if(tag.length > 0) for((source, tag) <- readTracking.allTags) if(tag.length > 0)
{ {
val (applications, subclasses) = fromByteArray[(List[String], List[DetectedSubclass])](tag) val (applications, subclasses) = Tag.fromBytes(tag)
allApplications ++= applications allApplications ++= applications
subclasses.foreach(subclass => subclassMap.getOrElseUpdate(subclass.superclassName, new ArrayBuffer[DetectedSubclass]) += subclass) subclasses.foreach(subclass => subclassMap.getOrElseUpdate(subclass.superclassName, new ArrayBuffer[DetectedSubclass]) += subclass)
} }
} }
private final class CompileAnalysisCallback(superClasses: Array[String], tracking: UpdateTracking[File]) extends xsbti.AnalysisCallback private final class CompileAnalysisCallback(superClasses: Array[String], tracking: UpdateTracking[File]) extends xsbti.AnalysisCallback
{ {
private var applications = List[(File,String)]() private var applications = List[String]()
private var subclasses = List[DetectedSubclass]() private var subclasses = List[DetectedSubclass]()
def superclassNames = superClasses def superclassNames = superClasses
def superclassNotFound(superclassName: String) = error("Superclass not found: " + superclassName) def superclassNotFound(superclassName: String) = error("Superclass not found: " + superclassName)
@ -94,12 +95,12 @@ class StandardCompile(val sources: Task[Set[File]], val classpath: Task[Set[File
{ {
if(!applications.isEmpty || !subclasses.isEmpty) if(!applications.isEmpty || !subclasses.isEmpty)
{ {
tracking.tag(source, toByteArray( (applications, subclasses) ) ) tracking.tag(source, Tag.toBytes(applications, subclasses) )
applications = Nil applications = Nil
subclasses = Nil subclasses = Nil
} }
} }
def foundApplication(source: File, className: String) { applications ::= ( (source, className) ) } def foundApplication(source: File, className: String) { applications ::= className }
def foundSubclass(source: File, subclassName: String, superclassName: String, isModule: Boolean): Unit = def foundSubclass(source: File, subclassName: String, superclassName: String, isModule: Boolean): Unit =
subclasses ::= DetectedSubclass(source, subclassName, superclassName, isModule) subclasses ::= DetectedSubclass(source, subclassName, superclassName, isModule)
def sourceDependency(dependsOn: File, source: File) { tracking.dependency(source, dependsOn) } def sourceDependency(dependsOn: File, source: File) { tracking.dependency(source, dependsOn) }
@ -109,6 +110,15 @@ class StandardCompile(val sources: Task[Set[File]], val classpath: Task[Set[File
} }
} }
object Tag
{
import sbinary.{DefaultProtocol, Format, Operations}
import DefaultProtocol._
private implicit val subclassFormat: Format[DetectedSubclass] =
asProduct4(DetectedSubclass.apply)( ds => Some(ds.source, ds.subclassName, ds.superclassName, ds.isModule))
def toBytes(applications: List[String], subclasses: List[DetectedSubclass]) = CacheIO.toBytes((applications, subclasses))
def fromBytes(bytes: Array[Byte]) = CacheIO.fromBytes( ( List[String](), List[DetectedSubclass]() ) )(bytes)
}
trait CompileReport extends NotNull trait CompileReport extends NotNull
{ {
def classes: Set[File] def classes: Set[File]

View File

@ -0,0 +1,56 @@
package xsbt
import FileUtilities.{read, withTemporaryDirectory => temp, write}
object SyncTest
{
import Paths._
def apply(content: String)
{
try { test(content) }
catch { case e: TasksFailed => e.failures.foreach(_.exception.printStackTrace) }
}
def test(content: String)
{
temp { fromDir => temp { toDir => temp { cacheDir =>
val from = fromDir / "test"
val to = toDir / "test-2"
write(from, content)
val sync = Sync(cacheDir)( Task( (from, to) :: Nil ))
val result = TaskRunner(sync.task)
println(result + " ::: " +read(to) + "\n\n")
to.delete
val result2 = TaskRunner(sync.task)
println(result2 + " ::: " +read(to) + "\n\n")
write(from, content.reverse)
TaskRunner(sync.clean)
println(from.exists + " " + fromDir.exists + " " + to.exists + " " + toDir.exists)
} } }
}
}
object CompileTest
{
def apply(dir: String, scalaVersion: String, opts: Seq[String], supers: Set[String])
{
def test()
{
import Paths._
import GlobFilter._
val base = new java.io.File(dir)
val sources = Task(((base / "src" / "main" / "scala") ** "*.scala") ++ (base * "*.scala"))
val classpath = Task( dir / "lib" * "*.jar" )
WithCompiler(scalaVersion) { (compiler, log) =>
temp { cacheDir => temp { outDir =>
val options = Task(opts ++ Seq("-d", outDir.getAbsolutePath) )
val compile = new StandardCompile(sources, classpath, options, Task(supers), Task(compiler), cacheDir, log)
TaskRunner(compile.task)
readLine("Press enter to continue...")
TaskRunner(compile.task)
readLine("Press enter to continue...")
} }
}
}
try { test() }
catch { case e: TasksFailed => e.failures.foreach(_.exception.printStackTrace); case e: Exception => e.printStackTrace }
}
}