mirror of https://github.com/sbt/sbt.git
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:
parent
edca6620e4
commit
12c5f5a0d5
|
|
@ -6,6 +6,15 @@ import scala.reflect.Manifest
|
|||
|
||||
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 =
|
||||
fromFile(file, default)(format, mf)
|
||||
def fromFile[T](file: File, default: => T)(implicit format: Format[T], mf: Manifest[Format[T]]): T =
|
||||
|
|
|
|||
|
|
@ -37,17 +37,17 @@ class AnalyzeCompiler(scalaVersion: String, scalaLoader: ClassLoader, manager: C
|
|||
* and used to load the class that actually interfaces with Global.*/
|
||||
def apply(arguments: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger)
|
||||
{
|
||||
println("Compiling: " + arguments)
|
||||
// this is the instance used to compile the analysis
|
||||
val componentCompiler = new ComponentCompiler(scalaVersion, new RawCompiler(scalaLoader), manager)
|
||||
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 interfaceLoader = new URLClassLoader(Array(interfaceJar.toURI.toURL), dual)
|
||||
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 }]
|
||||
// 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(argsWithPlugin.toArray, callback, maximumErrors, log)
|
||||
runnable.run(arguments.toArray, callback, maximumErrors, log)
|
||||
}
|
||||
private def createDualLoader(scalaLoader: ClassLoader, sbtLoader: ClassLoader): ClassLoader =
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,46 +10,21 @@ import symtab.Flags
|
|||
import scala.collection.mutable.{HashMap, HashSet, Map, Set}
|
||||
|
||||
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._
|
||||
|
||||
val name = "xsbt-analyze"
|
||||
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)
|
||||
}
|
||||
|
||||
def newPhase(prev: Phase): Phase = new AnalyzerPhase(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
|
||||
{
|
||||
val outputDirectory = new File(global.settings.outdir.value)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
*/
|
||||
package xsbt
|
||||
|
||||
import xsbti.{AnalysisCallback,AnalysisCallbackContainer,Logger}
|
||||
import xsbti.{AnalysisCallback,Logger}
|
||||
import scala.tools.nsc.{Phase, SubComponent}
|
||||
|
||||
class CompilerInterface
|
||||
{
|
||||
|
|
@ -15,9 +16,27 @@ class CompilerInterface
|
|||
val settings = new Settings(reporter.error)
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
<plugin>
|
||||
<name>xsbt-analyze</name>
|
||||
<classname>xsbt.Analyzer</classname>
|
||||
</plugin>
|
||||
|
|
@ -3,7 +3,7 @@ package xsbt
|
|||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import xsbti.{Logger, TestCallback, TestLogger}
|
||||
import FileUtilities.{classLocationFile, withTemporaryDirectory, write}
|
||||
import FileUtilities.withTemporaryDirectory
|
||||
|
||||
object TestCompile
|
||||
{
|
||||
|
|
@ -11,15 +11,10 @@ object TestCompile
|
|||
* 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 =
|
||||
{
|
||||
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 i = new CompilerInterface
|
||||
val newArgs = "-Xplugin-require:xsbt-analyze" :: pluginArg ::: arguments.toList
|
||||
TestLogger { log =>
|
||||
i.run(newArgs.toArray, testCallback, 5, log)
|
||||
i.run(arguments.toArray, testCallback, 5, log)
|
||||
f(testCallback, log)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
|
|||
val trackingSub = project(cachePath / "tracking", "Tracking", new Base(_), cacheSub)
|
||||
val compilerSub = project(compilePath, "Compile", new CompileProject(_),
|
||||
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 */
|
||||
|
||||
|
|
@ -56,6 +56,10 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
|
|||
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
|
||||
}
|
||||
class StandardTaskProject(info: ProjectInfo) extends Base(info)
|
||||
{
|
||||
override def testClasspath = super.testClasspath +++ compilerSub.testClasspath
|
||||
}
|
||||
|
||||
class IOProject(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
|
||||
{
|
||||
override def scratch = true
|
||||
override def consoleClasspath = testClasspath
|
||||
}
|
||||
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 compileOrder = CompileOrder.JavaThenScala
|
||||
}
|
||||
class CompilerInterfaceProject(info: ProjectInfo) extends Base(info) with SourceProject
|
||||
{
|
||||
// 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)
|
||||
}
|
||||
class CompilerInterfaceProject(info: ProjectInfo) extends Base(info) with SourceProject with TestWithIO
|
||||
trait TestWithIO extends BasicScalaProject
|
||||
{
|
||||
// use IO from tests
|
||||
|
|
|
|||
|
|
@ -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 sameOpts = (opts: Seq[String]) => (opts, rawSourceChanges, rawClasspathChanges)
|
||||
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
|
||||
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
|
||||
{
|
||||
import Task._
|
||||
import sbinary.{DefaultProtocol, Format, Operations}
|
||||
import DefaultProtocol._
|
||||
import Operations.{fromByteArray, toByteArray}
|
||||
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
|
||||
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 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) =>
|
||||
if(!sources.isEmpty)
|
||||
{
|
||||
val callback = new CompileAnalysisCallback(superClasses.toArray, tracking)
|
||||
val arguments = Seq("-cp", abs(classpath).mkString(File.pathSeparator)) ++ options ++ abs(sources).toSeq
|
||||
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 applicationSet = new HashSet[String]
|
||||
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)
|
||||
{
|
||||
val (applications, subclasses) = fromByteArray[(List[String], List[DetectedSubclass])](tag)
|
||||
val (applications, subclasses) = Tag.fromBytes(tag)
|
||||
allApplications ++= applications
|
||||
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 var applications = List[(File,String)]()
|
||||
private var applications = List[String]()
|
||||
private var subclasses = List[DetectedSubclass]()
|
||||
def superclassNames = superClasses
|
||||
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)
|
||||
{
|
||||
tracking.tag(source, toByteArray( (applications, subclasses) ) )
|
||||
tracking.tag(source, Tag.toBytes(applications, subclasses) )
|
||||
applications = 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 =
|
||||
subclasses ::= DetectedSubclass(source, subclassName, superclassName, isModule)
|
||||
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
|
||||
{
|
||||
def classes: Set[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 }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue