diff --git a/compile/Compiler.scala b/compile/Compiler.scala index 98ae84455..6ba166fe8 100644 --- a/compile/Compiler.scala +++ b/compile/Compiler.scala @@ -4,37 +4,11 @@ import xsbti.{AnalysisCallback, Logger} import java.io.File import java.net.URLClassLoader -/** A component manager provides access to the pieces of xsbt that are distributed as components. -* There are two types of components. The first type is compiled subproject jars with their dependencies. -* The second type is a subproject distributed as a source jar so that it can be compiled against a specific -* version of Scala. -* -* The component manager provides services to install and retrieve components to the local repository. -* This is used for source jars so that the compilation need not be repeated for other projects on the same -* machine. -*/ -trait ComponentManager extends NotNull -{ - def directory(id: String): File = - { - error("TODO: implement") - } - def jars(id: String): Iterable[File] = - { - val dir = directory(id) - if(dir.isDirectory) - FileUtilities.jars(dir) - else - Nil - } -} - - /** 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 bad). Otherwise, binary compatibility issues will ensue!*/ +* (-SNAPSHOT is not acceptable). Otherwise, binary compatibility issues will ensue!*/ class Compiler(scalaLoader: ClassLoader, val scalaVersion: String, private[xsbt] val manager: ComponentManager) { // this is the instance used to compile the analysis @@ -46,8 +20,10 @@ class Compiler(scalaLoader: ClassLoader, val scalaVersion: String, private[xsbt] def apply(arguments: Seq[String]) { // reflection is required for binary compatibility - // The following inputs ensure there is a compile error if the class names change, but they should not actually be used - import scala.tools.nsc.{CompilerCommand, FatalError, Global, Settings, reporters, util} + // The following import ensures there is a compile error if the class name changes, + // but it should not be otherwise directly referenced + import scala.tools.nsc.Main + val mainClass = Class.forName("scala.tools.nsc.Main", true, scalaLoader) val main = mainClass.asInstanceOf[{def process(args: Array[String]): Unit }] main.process(arguments.toArray) @@ -56,82 +32,17 @@ class Compiler(scalaLoader: ClassLoader, val scalaVersion: String, private[xsbt] /** Interface to the compiler that uses the dependency analysis plugin.*/ object analysis { - /** The compiled plugin jar. This will be passed to scalac as a compiler plugin.*/ - private lazy val analyzerJar = - componentCompiler("analyzerPlugin").toList match - { - case x :: Nil => x - case Nil => error("Analyzer plugin component not found") - case xs => error("Analyzer plugin component must be a single jar (was: " + xs.mkString(", ") + ")") - } - /** The compiled interface jar. This is used to configure and call the compiler. It redirects logging and sets up - * the dependency analysis plugin.*/ - private lazy val interfaceJars = componentCompiler("compilerInterface") + /** The jar containing the compiled plugin and the compiler interface code. This will be passed to scalac as a compiler plugin + * and used to load the class that actually interfaces with Global.*/ + private lazy val interfaceJar = componentCompiler(ComponentCompiler.compilerInterfaceID) def apply(arguments: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger) { - val argsWithPlugin = ("-Xplugin:" + analyzerJar.getAbsolutePath) :: arguments.toList - val interfaceLoader = new URLClassLoader(interfaceJars.toSeq.map(_.toURI.toURL).toArray, scalaLoader) + val argsWithPlugin = ("-Xplugin:" + interfaceJar.getAbsolutePath) :: arguments.toList + val interfaceLoader = new URLClassLoader(Array(interfaceJar.toURI.toURL), scalaLoader) 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 }] runnable.run(argsWithPlugin.toArray, callback, maximumErrors, log) // safe to pass across the ClassLoader boundary because the types are defined in Java } - def forceInitialization() {interfaceJars; analyzerJar} - } -} -class ComponentCompiler(compiler: Compiler) -{ - import compiler.{manager, scalaVersion} - - val xsbtiID = "xsbti" - lazy val xsbtiJars = - { - val js = manager.jars(xsbtiID) - if(js.isEmpty) - error("Could not find required xsbti component") - else - js - } - - import FileUtilities.{copy, createDirectory, zip, jars, unzip, withTemporaryDirectory} - def apply(id: String): Iterable[File] = - { - val binID = id + "-bin_" + scalaVersion - val binaryDirectory = manager.directory(binID) - if(binaryDirectory.isDirectory) - jars(binaryDirectory) - else - { - createDirectory(binaryDirectory) - val srcID = id + "-src" - val srcDirectory = manager.directory(srcID) - if(srcDirectory.isDirectory) - { - val targetJar = new File(binaryDirectory, id + ".jar") - compileSources(srcDirectory, compiler, targetJar, id) - Seq(targetJar) - } - else - notFound(id) - } - } - private def notFound(id: String) = error("Couldn't find xsbt source component " + id + " for Scala " + scalaVersion) - private def compileSources(srcDirectory: File, compiler: Compiler, targetJar: File, id: String) - { - val sources = jars(srcDirectory) - if(sources.isEmpty) - notFound(id) - else - { - withTemporaryDirectory { dir => - val extractedSources = (Set[File]() /: sources) { (extracted, sourceJar)=> extracted ++ unzip(sourceJar, dir) } - val (sourceFiles, resources) = extractedSources.partition(_.getName.endsWith(".scala")) - withTemporaryDirectory { outputDirectory => - val arguments = Seq("-d", outputDirectory.getAbsolutePath, "-cp", xsbtiJars.mkString(File.pathSeparator)) ++ sourceFiles.toSeq.map(_.getAbsolutePath) - compiler.raw(arguments) - copy(resources, outputDirectory, PathMapper.relativeTo(dir)) - zip(Seq(outputDirectory), targetJar, true, PathMapper.relativeTo(outputDirectory)) - } - } - } + def forceInitialization() {interfaceJar } } } \ No newline at end of file diff --git a/compile/ComponentCompiler.scala b/compile/ComponentCompiler.scala new file mode 100644 index 000000000..c60c86253 --- /dev/null +++ b/compile/ComponentCompiler.scala @@ -0,0 +1,49 @@ +package xsbt + +import java.io.File + +object ComponentCompiler +{ + val xsbtiID = "xsbti" + val compilerInterfaceID = "compilerInterface" +} +class ComponentCompiler(compiler: Compiler) +{ + import compiler.{manager, scalaVersion} + import ComponentCompiler._ + + lazy val xsbtiJars = manager.files(xsbtiID) + + import FileUtilities.{copy, createDirectory, zip, jars, unzip, withTemporaryDirectory} + def apply(id: String): File = + { + val binID = id + "-bin_" + scalaVersion + try { manager.file(binID) } + catch { case e: Exception => compileAndInstall(id, binID) } + } + private def compileAndInstall(id: String, binID: String): File = + { + val srcID = id + "-src_" + scalaVersion + val binaryDirectory = manager.location(binID) + createDirectory(binaryDirectory) + val targetJar = new File(binaryDirectory, id + ".jar") + compileSources(manager.files(srcID), compiler, targetJar, id) + manager.cache(binID) + 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], compiler: Compiler, targetJar: File, id: String) + { + withTemporaryDirectory { dir => + val extractedSources = (Set[File]() /: sourceJars) { (extracted, sourceJar)=> extracted ++ unzip(sourceJar, dir) } + val (sourceFiles, resources) = extractedSources.partition(_.getName.endsWith(".scala")) + withTemporaryDirectory { outputDirectory => + val arguments = Seq("-d", outputDirectory.getAbsolutePath, "-cp", xsbtiJars.mkString(File.pathSeparator)) ++ sourceFiles.toSeq.map(_.getAbsolutePath) + compiler.raw(arguments) + copy(resources, outputDirectory, PathMapper.relativeTo(dir)) + zip(Seq(outputDirectory), targetJar, true, PathMapper.relativeTo(outputDirectory)) + } + } + } +} \ No newline at end of file diff --git a/compile/plugin/Analyzer.scala b/compile/interface/Analyzer.scala similarity index 99% rename from compile/plugin/Analyzer.scala rename to compile/interface/Analyzer.scala index b9ffb12bc..666656648 100644 --- a/compile/plugin/Analyzer.scala +++ b/compile/interface/Analyzer.scala @@ -18,7 +18,7 @@ class Analyzer(val global: Global) extends Plugin import global._ - val name = "sbt-analyzer" + 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) diff --git a/compile/interface/scalac-plugin.xml b/compile/interface/scalac-plugin.xml new file mode 100644 index 000000000..f5f0e939c --- /dev/null +++ b/compile/interface/scalac-plugin.xml @@ -0,0 +1,4 @@ + + xsbt-analyze + xsbt.Analyzer + diff --git a/compile/interface/src/test/scala/CheckBasic.scala b/compile/interface/src/test/scala/CheckBasic.scala new file mode 100644 index 000000000..066e91403 --- /dev/null +++ b/compile/interface/src/test/scala/CheckBasic.scala @@ -0,0 +1,36 @@ +package xsbt + +import java.io.File +import org.specs.Specification + +object CheckBasic extends Specification +{ + "Compiling basic file should succeed" in { + val name = new File("Basic.scala") + WithFiles( name -> "package org.example { object Basic }" ){ files => + TestCompile(files){ loader => Class.forName("org.example.Basic", false, loader) } + } + } + + "Analysis plugin" should { + "send source begin and end" in { + val name = new File("Basic.scala") + WithFiles(name -> "object Basic" ) { files => + CallbackTest(files) { callback => + (callback.beganSources) must haveTheSameElementsAs(files) + (callback.endedSources) must haveTheSameElementsAs(files) + } + } + } + + "detect applications" in { + val name = new File("Main.scala") + WithFiles(name -> "object Main { def main(args: Array[String]) {} }" ) { files => + CallbackTest(files) { callback => + println(callback.applications) + (callback.applications) must haveTheSameElementsAs(files.map(file => (file, "Main"))) + } + } + } + } +} \ No newline at end of file diff --git a/compile/interface/src/test/scala/TestCompile.scala b/compile/interface/src/test/scala/TestCompile.scala new file mode 100644 index 000000000..29135832b --- /dev/null +++ b/compile/interface/src/test/scala/TestCompile.scala @@ -0,0 +1,55 @@ +package xsbt + +import java.io.File +import java.net.URLClassLoader +import xsbti.{Logger, TestCallback, TestLogger} +import FileUtilities.{classLocationFile, withTemporaryDirectory, write} + +object TestCompile +{ + 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) + f(testCallback, log) + } + } + def apply[T](sources: Seq[File])(f: ClassLoader => T): T = + CallbackTest.apply(sources, Nil){ case (callback, outputDir, log) => f(new URLClassLoader(Array(outputDir.toURI.toURL))) } +} +object CallbackTest +{ + def apply[T](sources: Iterable[File])(f: TestCallback => T): T = + apply(sources.toSeq, Nil){ case (callback, outputDir, log) => f(callback) } + def apply[T](sources: Seq[File], superclassNames: Seq[String])(f: (TestCallback, File, Logger) => T): T = + { + withTemporaryDirectory { outputDir => + val newArgs = "-d" :: outputDir.getAbsolutePath :: sources.map(_.getAbsolutePath).toList + TestCompile(newArgs, superclassNames) { case (callback, log) => f(callback, outputDir, log) } + } + } +} +object WithFiles +{ + def apply[T](sources: (File, String)*)(f: Seq[File] => T): T = + { + withTemporaryDirectory { dir => + val sourceFiles = + for((file, content) <- sources) yield + { + assert(!file.isAbsolute) + val to = new File(dir, file.getPath) + write(to, content) + to + } + f(sourceFiles) + } + } +} \ No newline at end of file diff --git a/interface/src/main/java/xsbti/Versions.java b/interface/src/main/java/xsbti/Versions.java new file mode 100644 index 000000000..8576aeaf5 --- /dev/null +++ b/interface/src/main/java/xsbti/Versions.java @@ -0,0 +1,11 @@ +/* sbt -- Simple Build Tool + * Copyright 2009 Mark Harrah + */ +package xsbti; + +public interface Versions +{ + public static final String Sbt = "0.7"; + public static final int Interface = 1; + public static final int BootInterface = 1; +} diff --git a/interface/src/test/scala/F0.scala b/interface/src/test/scala/F0.scala new file mode 100644 index 000000000..d71458e68 --- /dev/null +++ b/interface/src/test/scala/F0.scala @@ -0,0 +1,6 @@ +package xsbti + +object f0 +{ + def apply[T](s: => T) = new F0[T] { def apply = s } +} \ No newline at end of file diff --git a/interface/src/test/scala/TestCallback.scala b/interface/src/test/scala/TestCallback.scala new file mode 100644 index 000000000..7783a0df5 --- /dev/null +++ b/interface/src/test/scala/TestCallback.scala @@ -0,0 +1,28 @@ +package xsbti + +import java.io.File +import scala.collection.mutable.ArrayBuffer + +class TestCallback(val superclassNames: Array[String]) extends AnalysisCallback +{ + val invalidSuperclasses = new ArrayBuffer[String] + val beganSources = new ArrayBuffer[File] + val endedSources = new ArrayBuffer[File] + val foundSubclasses = new ArrayBuffer[(File, String, String, Boolean)] + val sourceDependencies = new ArrayBuffer[(File, File)] + val jarDependencies = new ArrayBuffer[(File, File)] + val classDependencies = new ArrayBuffer[(File, File)] + val products = new ArrayBuffer[(File, File)] + val applications = new ArrayBuffer[(File, String)] + + def superclassNotFound(superclassName: String) { invalidSuperclasses += superclassName } + def beginSource(source: File) { beganSources += source } + def foundSubclass(source: File, subclassName: String, superclassName: String, isModule: Boolean): Unit = + foundSubclasses += ((source, subclassName, superclassName, isModule)) + def sourceDependency(dependsOn: File, source: File) { sourceDependencies += ((dependsOn, source)) } + def jarDependency(jar: File, source: File) { jarDependencies += ((jar, source)) } + def classDependency(clazz: File, source: File) { classDependencies += ((clazz, source)) } + def generatedClass(source: File, module: File) { products += ((source, module)) } + def endSource(source: File) { endedSources += source } + def foundApplication(source: File, className: String) { applications += ((source, className)) } +} \ No newline at end of file diff --git a/interface/src/test/scala/TestLogger.scala b/interface/src/test/scala/TestLogger.scala new file mode 100644 index 000000000..f78a0f533 --- /dev/null +++ b/interface/src/test/scala/TestLogger.scala @@ -0,0 +1,25 @@ +package xsbti + +class TestLogger extends Logger +{ + private val buffer = new scala.collection.mutable.ArrayBuffer[F0[Unit]] + def info(msg: F0[String]) = buffer("[info] ", msg) + def warn(msg: F0[String]) = buffer("[warn] ", msg) + def debug(msg: F0[String]) = buffer("[debug] ", msg) + def error(msg: F0[String]) = buffer("[error] ", msg) + def verbose(msg: F0[String]) = buffer("[verbose] ", msg) + def show() { buffer.foreach(_()) } + def clear() { buffer.clear() } + def trace(t: F0[Throwable]) { buffer += f0(t().printStackTrace) } + private def buffer(s: String, msg: F0[String]) { buffer += f0(println(s + msg())) } +} +object TestLogger +{ + def apply[T](f: Logger => T): T = + { + val log = new TestLogger + try { f(log) } + catch { case e: Exception => log.show(); throw e } + finally { log.clear() } + } +} \ No newline at end of file diff --git a/ivy/ComponentManager.scala b/ivy/ComponentManager.scala new file mode 100644 index 000000000..f1d441916 --- /dev/null +++ b/ivy/ComponentManager.scala @@ -0,0 +1,45 @@ +package xsbt + +import java.io.File +import xsbti.Versions + +/** A component manager provides access to the pieces of xsbt that are distributed as components. +* There are two types of components. The first type is compiled subproject jars with their dependencies. +* The second type is a subproject distributed as a source jar so that it can be compiled against a specific +* version of Scala. +* +* The component manager provides services to install and retrieve components to the local repository. +* This is used for source jars so that the compilation need not be repeated for other projects on the same +* machine. +*/ +class ComponentManager(baseDirectory: File, log: IvyLogger) extends NotNull +{ + def location(id: String): File = new File(baseDirectory, id) + def directory(id: String): File = + { + val dir = location(id) + if(!dir.exists) + update(id) + dir + } + private def contents(dir: File): Seq[File] = + { + val fs = dir.listFiles + if(fs == null) Nil else fs + } + def files(id: String): Iterable[File] = + { + val fs = contents(directory(id)) + if(!fs.isEmpty) fs else error("Could not find required component '" + id + "'") + } + def file(id: String): File = + files(id).toList match { + case x :: Nil => x + case xs => error("Expected single file for component '" + id + "', found: " + xs.mkString(", ")) + } + + def update(id: String): Unit = + IvyActions.basicRetrieveLocal(sbtModuleID("manager"), Seq(sbtModuleID(id)), location(id), log) + def sbtModuleID(id: String) = ModuleID("org.scala-tools.sbt", id, Versions.Sbt) + def cache(id: String): Unit = IvyActions.basicPublishLocal(sbtModuleID(id), Nil, files(id), log) +} \ No newline at end of file diff --git a/ivy/CustomXmlParser.scala b/ivy/CustomXmlParser.scala index 6688d8ef7..91ca60a79 100644 --- a/ivy/CustomXmlParser.scala +++ b/ivy/CustomXmlParser.scala @@ -14,7 +14,7 @@ import plugins.repository.Resource import plugins.repository.url.URLResource /** Subclasses the default Ivy file parser in order to provide access to protected methods.*/ -private object CustomXmlParser extends XmlModuleDescriptorParser with NotNull +private[xsbt] object CustomXmlParser extends XmlModuleDescriptorParser with NotNull { import XmlModuleDescriptorParser.Parser class CustomParser(settings: IvySettings) extends Parser(CustomXmlParser, settings) with NotNull diff --git a/ivy/IvyActions.scala b/ivy/IvyActions.scala index ae3fcf6d2..cb7f74b62 100644 --- a/ivy/IvyActions.scala +++ b/ivy/IvyActions.scala @@ -18,6 +18,30 @@ final class UpdateConfiguration(val retrieveDirectory: File, val outputPattern: object IvyActions { + def basicPublishLocal(moduleID: ModuleID, dependencies: Iterable[ModuleID], artifactFiles: Iterable[File], log: IvyLogger) + { + val artifacts = artifactFiles.map(Artifact.defaultArtifact) + val (ivy, local) = basicLocalIvy(log) + val module = new ivy.Module(ModuleConfiguration(moduleID, dependencies, artifacts)) + val srcArtifactPatterns = artifactFiles.map(_.getAbsolutePath) + publish(module, local.name, srcArtifactPatterns, None, None) + } + def basicRetrieveLocal(moduleID: ModuleID, dependencies: Iterable[ModuleID], to: File, log: IvyLogger) + { + val (ivy, local) = basicLocalIvy(log) + val module = new ivy.Module(ModuleConfiguration(moduleID, dependencies, Nil)) + val up = new UpdateConfiguration(to, defaultOutputPattern, false, true) + update(module, up) + } + def defaultOutputPattern = "[artifact]-[revision](-[classifier]).[ext]" + private def basicLocalIvy(log: IvyLogger) = + { + val local = Resolver.defaultLocal + val paths = new IvyPaths(new File("."), None) + val conf = new IvyConfiguration(paths, Seq(local), log) + (new IvySbt(conf), local) + } + /** Clears the Ivy cache, as configured by 'config'. */ def cleanCache(ivy: IvySbt) = ivy.withIvy { _.getSettings.getRepositoryCacheManagers.foreach(_.clean()) } diff --git a/ivy/IvyConfigurations.scala b/ivy/IvyConfigurations.scala index 366917c92..39b7948be 100644 --- a/ivy/IvyConfigurations.scala +++ b/ivy/IvyConfigurations.scala @@ -18,6 +18,8 @@ final class ModuleConfiguration(val module: ModuleID, val dependencies: Iterable } object ModuleConfiguration { + def apply(module: ModuleID, dependencies: Iterable[ModuleID], artifacts: Iterable[Artifact]) = + new ModuleConfiguration(module, dependencies, NodeSeq.Empty, Nil, None, None, artifacts, false) def configurations(explicitConfigurations: Iterable[Configuration], defaultConfiguration: Option[Configuration]) = if(explicitConfigurations.isEmpty) { diff --git a/ivy/IvyInterface.scala b/ivy/IvyInterface.scala index ae89d431b..d31ac00c3 100644 --- a/ivy/IvyInterface.scala +++ b/ivy/IvyInterface.scala @@ -311,15 +311,22 @@ object Artifact def apply(name: String, url: URL): Artifact =Artifact(name, extract(url, defaultType), extract(url, defaultExtension), None, Nil, Some(url)) val defaultExtension = "jar" val defaultType = "jar" - private[this] def extract(url: URL, default: String) = + private[this] def extract(url: URL, default: String): String = extract(url.toString, default) + private[this] def extract(name: String, default: String): String = { - val s = url.toString - val i = s.lastIndexOf('.') + val i = name.lastIndexOf('.') if(i >= 0) - s.substring(i+1) + name.substring(i+1) else default } + def defaultArtifact(file: File) = + { + val name = file.getName + val i = name.lastIndexOf('.') + val base = if(i >= 0) name.substring(0, i) else name + Artifact(name, extract(name, defaultType), extract(name, defaultExtension), None, Nil, Some(file.toURI.toURL)) + } } /* object Credentials diff --git a/ivy/IvyLogger.scala b/ivy/IvyLogger.scala index ff3ad2f19..c58ffd054 100644 --- a/ivy/IvyLogger.scala +++ b/ivy/IvyLogger.scala @@ -5,7 +5,7 @@ package xsbt import org.apache.ivy.util.{Message, MessageLogger} -trait IvyLogger +trait IvyLogger extends NotNull { def info(msg: => String) def debug(msg: => String) diff --git a/project/build/XSbt.scala b/project/build/XSbt.scala index 647b27ecf..c6daa57de 100644 --- a/project/build/XSbt.scala +++ b/project/build/XSbt.scala @@ -2,9 +2,6 @@ import sbt._ class XSbt(info: ProjectInfo) extends ParentProject(info) { - def utilPath = path("util") - def compilePath = path("compile") - val commonDeps = project("common", "Dependencies", new CommonDependencies(_)) val interfaceSub = project("interface", "Interface", new InterfaceProject(_)) @@ -13,19 +10,23 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) val ioSub = project(utilPath / "io", "IO", new Base(_), controlSub, commonDeps) val classpathSub = project(utilPath / "classpath", "Classpath", new Base(_)) - val analysisPluginSub = project(compilePath / "plugin", "Analyzer Compiler Plugin", new Base(_), interfaceSub) - val compilerInterfaceSub = project(compilePath / "interface", "Compiler Interface", new Base(_), interfaceSub) + val compilerInterfaceSub = project(compilePath / "interface", "Compiler Interface", new CompilerInterfaceProject(_), interfaceSub) - val ivySub = project("ivy", "Ivy", new IvyProject(_)) + val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub) val logSub = project(utilPath / "log", "Logging", new Base(_)) val taskSub = project("tasks", "Tasks", new TaskProject(_), controlSub, collectionSub, commonDeps) val cacheSub = project("cache", "Cache", new CacheProject(_), taskSub, ioSub) - val compilerSub = project(compilePath, "Compile", new Base(_), interfaceSub, ivySub, analysisPluginSub, ioSub, compilerInterfaceSub) + val compilerSub = project(compilePath, "Compile", new Base(_), interfaceSub, ivySub, ioSub, compilerInterfaceSub) - class CommonDependencies(info: ProjectInfo) extends ParentProject(info) + def utilPath = path("util") + def compilePath = path("compile") + + class CommonDependencies(info: ProjectInfo) extends DefaultProject(info) { val sc = "org.scala-tools.testing" % "scalacheck" % "1.5" % "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 } override def parallelExecution = true @@ -44,7 +45,22 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) } class InterfaceProject(info: ProjectInfo) extends DefaultProject(info) { - override def sourceExtensions: NameFilter = "*.java" + 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 so that the classes and resources are both in the output resource directory + // the main output 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) + 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 SourceProject extends BasicScalaProject +{ + override def packagePaths = packageSourcePaths } \ No newline at end of file diff --git a/util/io/FileUtilities.scala b/util/io/FileUtilities.scala index fad8d374a..ce7f09104 100644 --- a/util/io/FileUtilities.scala +++ b/util/io/FileUtilities.scala @@ -7,9 +7,12 @@ import OpenResource._ import ErrorHandling.translate import java.io.{File, FileInputStream, InputStream, OutputStream} +import java.net.{URISyntaxException, URL} +import java.nio.charset.Charset import java.util.jar.{Attributes, JarEntry, JarFile, JarInputStream, JarOutputStream, Manifest} import java.util.zip.{GZIPOutputStream, ZipEntry, ZipFile, ZipInputStream, ZipOutputStream} import scala.collection.mutable.HashSet +import scala.reflect.{Manifest => SManifest} object FileUtilities { @@ -22,6 +25,31 @@ object FileUtilities private val BufferSize = 8192 private val Newline = System.getProperty("line.separator") + def classLocation(cl: Class[_]): URL = + { + val codeSource = cl.getProtectionDomain.getCodeSource + if(codeSource == null) error("No class location for " + cl) + else codeSource.getLocation + } + def classLocationFile(cl: Class[_]): File = toFile(classLocation(cl)) + def classLocation[T](implicit mf: SManifest[T]): URL = classLocation(mf.erasure) + def classLocationFile[T](implicit mf: SManifest[T]): File = classLocationFile(mf.erasure) + + def toFile(url: URL) = + try { new File(url.toURI) } + catch { case _: URISyntaxException => new File(url.getPath) } + + + // "base.extension" -> (base, extension) + def split(name: String): (String, String) = + { + val lastDot = name.lastIndexOf('.') + if(lastDot >= 0) + (name.substring(0, lastDot), name.substring(lastDot+1)) + else + (name, "") + } + def createDirectory(dir: File): Unit = translate("Could not create directory " + dir + ": ") { @@ -294,4 +322,15 @@ object FileUtilities } } } + def defaultCharset = Charset.forName("UTF-8") + def write(toFile: File, content: String): Unit = write(toFile, content, defaultCharset) + def write(toFile: File, content: String, charset: Charset): Unit = write(toFile, content, charset, false) + def write(file: File, content: String, charset: Charset, append: Boolean) + { + if(charset.newEncoder.canEncode(content)) + fileWriter(charset, append)(file) { w => w.write(content); None } + else + error("String cannot be encoded by charset " + charset.name) + } + }