diff --git a/compile/AnalyzingCompiler.scala b/compile/AnalyzingCompiler.scala index 142712d6e..083fae02a 100644 --- a/compile/AnalyzingCompiler.scala +++ b/compile/AnalyzingCompiler.scala @@ -1,6 +1,9 @@ -package xsbt +/* sbt -- Simple Build Tool + * Copyright 2009, 2010 Mark Harrah + */ +package sbt +package compile - import sbt.ComponentManager import xsbti.{AnalysisCallback, Logger => xLogger} import java.io.File import java.net.{URL, URLClassLoader} @@ -9,34 +12,34 @@ 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, val cp: ClasspathOptions, log: CompileLogger) extends NotNull +class AnalyzingCompiler(val scalaInstance: ScalaInstance, val manager: ComponentManager, val cp: ClasspathOptions, log: Logger) extends NotNull { - def this(scalaInstance: ScalaInstance, manager: ComponentManager, log: CompileLogger) = this(scalaInstance, manager, ClasspathOptions.auto, log) - def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: CompileLogger) + def this(scalaInstance: ScalaInstance, manager: ComponentManager, log: Logger) = this(scalaInstance, manager, ClasspathOptions.auto, log) + def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger) { val arguments = (new CompilerArguments(scalaInstance, cp))(sources, classpath, outputDirectory, options) compile(arguments, callback, maximumErrors, log) } - def compile(arguments: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: CompileLogger) + def compile(arguments: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger) { call("xsbt.CompilerInterface", log)( classOf[Array[String]], classOf[AnalysisCallback], classOf[Int], classOf[xLogger] ) ( arguments.toArray[String] : Array[String], callback, maximumErrors: java.lang.Integer, log ) } - def doc(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], maximumErrors: Int, log: CompileLogger): Unit = + def doc(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], maximumErrors: Int, log: Logger): Unit = { val arguments = (new CompilerArguments(scalaInstance, cp))(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: Seq[File], options: Seq[String], initialCommands: String, log: CompileLogger): Unit = + def console(classpath: Seq[File], options: Seq[String], initialCommands: String, log: Logger): Unit = { val arguments = new CompilerArguments(scalaInstance, cp) val classpathString = CompilerArguments.absString(arguments.finishClasspath(classpath)) val bootClasspath = if(cp.autoBoot) arguments.createBootClasspath else "" call("xsbt.ConsoleInterface", log) (classOf[Array[String]], classOf[String], classOf[String], classOf[String], classOf[xLogger]) (options.toArray[String]: Array[String], bootClasspath, classpathString, initialCommands, log) } - def force(log: CompileLogger): Unit = getInterfaceJar(log) - private def call(interfaceClassName: String, log: CompileLogger)(argTypes: Class[_]*)(args: AnyRef*) + def force(log: Logger): Unit = getInterfaceJar(log) + private def call(interfaceClassName: String, log: Logger)(argTypes: Class[_]*)(args: AnyRef*) { val interfaceClass = getInterfaceClass(interfaceClassName, log) val interface = interfaceClass.newInstance.asInstanceOf[AnyRef] @@ -50,20 +53,20 @@ class AnalyzingCompiler(val scalaInstance: ScalaInstance, val manager: Component val dual = createDualLoader(scalaInstance.loader, getClass.getClassLoader) // this goes to scalaLoader for scala classes and sbtLoader for xsbti classes new URLClassLoader(Array(interfaceJar.toURI.toURL), dual) } - private def getInterfaceClass(name: String, log: CompileLogger) = Class.forName(name, true, loader) - private def getInterfaceJar(log: CompileLogger) = + private def getInterfaceClass(name: String, log: Logger) = Class.forName(name, true, loader) + private def getInterfaceJar(log: Logger) = { // this is the instance used to compile the interface component val componentCompiler = newComponentCompiler(log) log.debug("Getting " + ComponentCompiler.compilerInterfaceID + " from component compiler for Scala " + scalaInstance.version) componentCompiler(ComponentCompiler.compilerInterfaceID) } - def newComponentCompiler(log: CompileLogger) = new ComponentCompiler(new RawCompiler(scalaInstance, ClasspathOptions.auto, log), manager) + def newComponentCompiler(log: Logger) = new ComponentCompiler(new RawCompiler(scalaInstance, ClasspathOptions.auto, log), manager) protected def createDualLoader(scalaLoader: ClassLoader, sbtLoader: ClassLoader): ClassLoader = { val xsbtiFilter = (name: String) => name.startsWith("xsbti.") val notXsbtiFilter = (name: String) => !xsbtiFilter(name) - new DualLoader(scalaLoader, notXsbtiFilter, x => true, sbtLoader, xsbtiFilter, x => false) + new classpath.DualLoader(scalaLoader, notXsbtiFilter, x => true, sbtLoader, xsbtiFilter, x => false) } override def toString = "Analyzing compiler (Scala " + scalaInstance.actualVersion + ")" } \ No newline at end of file diff --git a/compile/CompileLogger.scala b/compile/CompileLogger.scala deleted file mode 100644 index d07381232..000000000 --- a/compile/CompileLogger.scala +++ /dev/null @@ -1,10 +0,0 @@ -package xsbt - -trait CompileLogger extends xsbti.Logger with NotNull -{ - def info(msg: => String) - def debug(msg: => String) - def warn(msg: => String) - def error(msg: => String) - def trace(t: => Throwable) -} diff --git a/compile/CompilerArguments.scala b/compile/CompilerArguments.scala index 6e1bc5451..db2a94159 100644 --- a/compile/CompilerArguments.scala +++ b/compile/CompilerArguments.scala @@ -1,4 +1,8 @@ -package xsbt +/* sbt -- Simple Build Tool + * Copyright 2009, 2010 Mark Harrah + */ +package sbt +package compile import java.io.File import CompilerArguments.{abs, absString} diff --git a/compile/ComponentCompiler.scala b/compile/ComponentCompiler.scala index 069561d20..de908fef1 100644 --- a/compile/ComponentCompiler.scala +++ b/compile/ComponentCompiler.scala @@ -1,7 +1,10 @@ -package xsbt +/* sbt -- Simple Build Tool + * Copyright 2009, 2010 Mark Harrah + */ +package sbt +package compile import java.io.File -import sbt.{ComponentManager, IfMissing, InvalidComponent} object ComponentCompiler { diff --git a/compile/JavaCompiler.scala b/compile/JavaCompiler.scala new file mode 100644 index 000000000..d9065f49e --- /dev/null +++ b/compile/JavaCompiler.scala @@ -0,0 +1,66 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009, 2010 Mark Harrah, Seth Tisue + */ +package sbt +package compile + + import java.io.File + +trait JavaCompiler +{ + def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])(implicit log: Logger): Unit +} +object JavaCompiler +{ + def construct(f: (Seq[String], Logger) => Int, cp: ClasspathOptions, scalaInstance: ScalaInstance): JavaCompiler = + new JavaCompiler { + def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])(implicit log: Logger) { + val augmentedClasspath = if(cp.autoBoot) classpath ++ Seq(scalaInstance.libraryJar) else classpath + val javaCp = new ClasspathOptions(false, cp.compiler, false) + val arguments = (new CompilerArguments(scalaInstance, javaCp))(sources, augmentedClasspath, outputDirectory, options) + log.debug("running javac with arguments:\n\t" + arguments.mkString("\n\t")) + val code: Int = f(arguments, log) + log.debug("javac returned exit code: " + code) + if( code != 0 ) throw new CompileFailed(arguments.toArray, "javac returned nonzero exit code") + } + } + def directOrFork(cp: ClasspathOptions, scalaInstance: ScalaInstance): JavaCompiler = construct(directOrForkJavac, cp, scalaInstance) + def direct(cp: ClasspathOptions, scalaInstance: ScalaInstance): JavaCompiler = construct(directJavac, cp, scalaInstance) + def fork(cp: ClasspathOptions, scalaInstance: ScalaInstance): JavaCompiler = construct(forkJavac, cp, scalaInstance) + + val directOrForkJavac = (arguments: Seq[String], log: Logger) => + try { directJavac(arguments, log) } + catch { case e: ClassNotFoundException => + log.debug("com.sun.tools.javac.Main not found; forking javac instead") + forkJavac(arguments, log) + } + + val forkJavac = (arguments: Seq[String], log: Logger) => + { + def externalJavac(argFile: File) = Process("javac", ("@" + normalizeSlash(argFile.getAbsolutePath)) :: Nil) ! log + withArgumentFile(arguments)(externalJavac) + } + val directJavac = (arguments: Seq[String], log: Logger) => + { + val writer = new java.io.PrintWriter(new LoggerWriter(log, Level.Error)) + val argsArray = arguments.toArray + val javac = Class.forName("com.sun.tools.javac.Main") + log.debug("Calling javac directly.") + javac.getDeclaredMethod("compile", classOf[Array[String]], classOf[java.io.PrintWriter]) + .invoke(null, argsArray, writer) + .asInstanceOf[java.lang.Integer] + .intValue + } + def withArgumentFile[T](args: Seq[String])(f: File => T): T = + { + import IO.{Newline, withTemporaryDirectory, write} + withTemporaryDirectory { tmp => + val argFile = new File(tmp, "argfile") + write(argFile, args.map(escapeSpaces).mkString(Newline)) + f(argFile) + } + } + // javac's argument file seems to allow naive space escaping with quotes. escaping a quote with a backslash does not work + def escapeSpaces(s: String): String = '\"' + normalizeSlash(s) + '\"' + def normalizeSlash(s: String) = s.replace(File.separatorChar, '/') +} \ No newline at end of file diff --git a/compile/NOTICE b/compile/NOTICE index 8b92ad19e..daddd2fdb 100644 --- a/compile/NOTICE +++ b/compile/NOTICE @@ -1,3 +1,3 @@ Simple Build Tool: Compile Component -Copyright 2009, 2010 Mark Harrah, Jason Zaugg +Copyright 2009, 2010 Mark Harrah, Seth Tisue, Jason Zaugg Licensed under BSD-style license (see LICENSE) \ No newline at end of file diff --git a/compile/RawCompiler.scala b/compile/RawCompiler.scala index 5b78a445b..c0241da86 100644 --- a/compile/RawCompiler.scala +++ b/compile/RawCompiler.scala @@ -1,4 +1,8 @@ -package xsbt +/* sbt -- Simple Build Tool + * Copyright 2009, 2010 Mark Harrah + */ +package sbt +package compile import java.io.File @@ -6,7 +10,7 @@ package xsbt * 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: CompileLogger) +class RawCompiler(val scalaInstance: ScalaInstance, cp: ClasspathOptions, log: Logger) { def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String]) { diff --git a/compile/discover/NOTICE b/compile/discover/NOTICE new file mode 100644 index 000000000..69f5cae67 --- /dev/null +++ b/compile/discover/NOTICE @@ -0,0 +1,3 @@ +Simple Build Tool: Discovery Component +Copyright 2010 Mark Harrah +Licensed under BSD-style license (see LICENSE) \ No newline at end of file diff --git a/compile/inc/NOTICE b/compile/inc/NOTICE new file mode 100644 index 000000000..28a4004a0 --- /dev/null +++ b/compile/inc/NOTICE @@ -0,0 +1,3 @@ +Simple Build Tool: Incremental Logic Component +Copyright 2010 Mark Harrah +Licensed under BSD-style license (see LICENSE) \ No newline at end of file diff --git a/compile/inc/notes b/compile/inc/notes index a58e57aff..b198bfc2f 100644 --- a/compile/inc/notes +++ b/compile/inc/notes @@ -4,8 +4,18 @@ Tradiationally, there has been a main group and a test group. Each Analysis is associated with one or more classpath entries. Typically, it will be associated with the output directory and/or any artifacts produced from that output directory. -Need to be able to process Source and extract classes {C} s.t. C <: D for D in {D} or C or one of its non-private methods is annotated with annotation A in {A} or C.main(args: Array[String]) exists - For Java sources, need to write a (File, Set[File]) => Source function that reads an API from a class file. The compile function passed to IncrementalCompile needs to handle compiling Java sources in the proper order Need to handle entries removed from classpath. Could be done similarly to how Locate is used for getting the API for a dependency. In this case, we'd get the Stamp for a binary dependency. + +Post-analysis + - need to handle inherited definitions + +Need builder component: + Processor[D] + def process(command: String, arg: String, current: State[D]): Result[State[D]] + Initial[D] + def init: State[D] + State[D] + value: D + processors: Map[String, Processor[D]] \ No newline at end of file diff --git a/compile/interface/API.scala b/compile/interface/API.scala index ceecac514..f3d46b8d7 100644 --- a/compile/interface/API.scala +++ b/compile/interface/API.scala @@ -300,7 +300,7 @@ final class API(val global: Global, val callback: xsbti.AnalysisCallback) extend } def isTopLevel(sym: Symbol): Boolean = (sym ne null) && (sym != NoSymbol) && !sym.isImplClass && !sym.isNestedClass && sym.isStatic && - !sym.hasFlag(Flags.SYNTHETIC) && !sym.hasFlag(Flags.JAVA) + !sym.hasFlag(Flags.SYNTHETIC)// && !sym.hasFlag(Flags.JAVA) } // In 2.8, attributes is renamed to annotations diff --git a/compile/persist/NOTICE b/compile/persist/NOTICE new file mode 100644 index 000000000..bf0a7de5e --- /dev/null +++ b/compile/persist/NOTICE @@ -0,0 +1,3 @@ +Simple Build Tool: Analysis Store Component +Copyright 2010 Mark Harrah +Licensed under BSD-style license (see LICENSE) \ No newline at end of file diff --git a/ivy/src/test/scala/ComponentManagerTest.scala b/ivy/src/test/scala/ComponentManagerTest.scala index ec29ae476..8ad4c0999 100644 --- a/ivy/src/test/scala/ComponentManagerTest.scala +++ b/ivy/src/test/scala/ComponentManagerTest.scala @@ -1,10 +1,8 @@ package sbt -import xsbt.{BufferedLogger, FileUtilities} - import java.io.File import org.specs._ -import FileUtilities.{createDirectory, delete, touch, withTemporaryDirectory} +import IO.{createDirectory, delete, touch, withTemporaryDirectory} import org.apache.ivy.util.ChecksumHelper import IfMissing.Fail @@ -76,7 +74,7 @@ object ComponentManagerTest extends Specification manager.define(id, files) f(files) } - private def writeRandomContent(file: File) = FileUtilities.write(file, randomString) + private def writeRandomContent(file: File) = IO.write(file, randomString) private def randomString = "asdf" private def withManager[T](f: ComponentManager => T): T = TestIvyLogger( logger => withTemporaryDirectory { temp => f(new ComponentManager(xsbt.boot.Locks, new xsbt.boot.ComponentProvider(temp), logger)) } ) diff --git a/ivy/src/test/scala/TestIvyLogger.scala b/ivy/src/test/scala/TestIvyLogger.scala index bf4386d7f..c173b43fd 100644 --- a/ivy/src/test/scala/TestIvyLogger.scala +++ b/ivy/src/test/scala/TestIvyLogger.scala @@ -1,8 +1,6 @@ package sbt -import xsbt.{BufferedLogger, ConsoleLogger, Level} - -class TestIvyLogger extends BufferedLogger(new ConsoleLogger) with IvyLogger { def verbose(msg: => String) = info(msg) } +class TestIvyLogger extends BufferedLogger(new ConsoleLogger) with IvyLogger object TestIvyLogger { def apply[T](f: IvyLogger => T): T = diff --git a/main/AggressiveCompile.scala b/main/AggressiveCompile.scala index 9e47897e4..e907f9436 100644 --- a/main/AggressiveCompile.scala +++ b/main/AggressiveCompile.scala @@ -6,7 +6,7 @@ package sbt import inc._ import java.io.File - import xsbt.{AnalyzingCompiler, CompileLogger, CompilerArguments} + import sbt.compile.{AnalyzingCompiler, CompilerArguments} import xsbti.api.Source import xsbti.AnalysisCallback import CompileSetup._ @@ -18,7 +18,7 @@ final class CompileConfiguration(val sources: Seq[File], val classpath: Seq[File class AggressiveCompile(cacheDirectory: File) { - def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], compiler: AnalyzingCompiler, log: CompileLogger): Analysis = + def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], compiler: AnalyzingCompiler, log: Logger): Analysis = { val setup = new CompileSetup(outputDirectory, new CompileOptions(options), compiler.scalaInstance.actualVersion, CompileOrder.Mixed) compile1(sources, classpath, setup, store, Map.empty, compiler, log) @@ -27,7 +27,7 @@ class AggressiveCompile(cacheDirectory: File) def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] = args.bootClasspath ++ classpath - def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: Map[File, Analysis], compiler: AnalyzingCompiler, log: CompileLogger): Analysis = + def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: Map[File, Analysis], compiler: AnalyzingCompiler, log: Logger): Analysis = { val (previousAnalysis, previousSetup) = extract(store.get()) val config = new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup, analysis.get _, 100, compiler) @@ -35,7 +35,7 @@ class AggressiveCompile(cacheDirectory: File) store.set(result, setup) result } - def compile2(config: CompileConfiguration, log: CompileLogger)(implicit equiv: Equiv[CompileSetup]): Analysis = + def compile2(config: CompileConfiguration, log: Logger)(implicit equiv: Equiv[CompileSetup]): Analysis = { import config._ import currentSetup._ diff --git a/main/AggressiveCompiler.scala b/main/AggressiveCompiler.scala index aecc4f94e..b745b3052 100644 --- a/main/AggressiveCompiler.scala +++ b/main/AggressiveCompiler.scala @@ -3,7 +3,7 @@ */ package sbt - import xsbt.{AnalyzingCompiler, CompileLogger, ScalaInstance} + import sbt.compile.AnalyzingCompiler import java.io.File import System.{currentTimeMillis => now} import Path._ @@ -23,13 +23,13 @@ class AggressiveCompiler extends xsbti.AppMain def run(command: String, args: List[String], cwd: Path, app: xsbti.AppProvider): Boolean = { val launcher = app.scalaProvider.launcher - val sources = cwd ** "*.scala" + val sources = cwd ** ("*.scala" | "*.java") val target = cwd / "target" val outputDirectory = target / "classes" val classpath = outputDirectory +++ (cwd * "*.jar") +++(cwd * (-"project")).descendentsExcept( "*.jar", "project" || HiddenFileFilter) val cacheDirectory = target / "cache" val options = args.tail.toSeq - val log = new ConsoleLogger with CompileLogger with sbt.IvyLogger + val log = new ConsoleLogger with Logger with sbt.IvyLogger val componentManager = new ComponentManager(launcher.globalLock, app.components, log) val compiler = new AnalyzingCompiler(ScalaInstance(args.head, launcher), componentManager, log) diff --git a/project/build/XSbt.scala b/project/build/XSbt.scala index 28e6323d8..a8c7850e7 100644 --- a/project/build/XSbt.scala +++ b/project/build/XSbt.scala @@ -12,26 +12,30 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths val interfaceSub = project("interface", "Interface", new InterfaceProject(_)) val apiSub = baseProject(compilePath / "api", "API", interfaceSub) + // util val controlSub = baseProject(utilPath / "control", "Control") val collectionSub = testedBase(utilPath / "collection", "Collections") val ioSub = testedBase(utilPath / "io", "IO", controlSub) - val classpathSub = baseProject(utilPath / "classpath", "Classpath") + val classpathSub = baseProject(utilPath / "classpath", "Classpath", launchInterfaceSub, ioSub) val classfileSub = testedBase(utilPath / "classfile", "Classfile", ioSub, interfaceSub) val completeSub = testedBase(utilPath / "complete", "Completion", ioSub) - - val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub, launchInterfaceSub) val logSub = project(utilPath / "log", "Logging", new LogProject(_), interfaceSub) - val datatypeSub = baseProject("util" /"datatype", "Datatype Generator", ioSub) + val datatypeSub = baseProject(utilPath /"datatype", "Datatype Generator", ioSub) + val processSub = project(utilPath /"process", "Process", new Base(_) with TestWithIO, ioSub, logSub) + // intermediate-level modules + val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub, launchInterfaceSub, logSub) + val testingSub = project("testing", "Testing", new TestingProject(_), ioSub, classpathSub, logSub) + val taskSub = testedBase(tasksPath, "Tasks", controlSub, collectionSub) + val cacheSub = project(cachePath, "Cache", new CacheProject(_), ioSub, collectionSub) + + // compilation/discovery related modules val compileInterfaceSub = project(compilePath / "interface", "Compiler Interface", new CompilerInterfaceProject(_), interfaceSub) val compileIncrementalSub = testedBase(compilePath / "inc", "Incremental Compiler", collectionSub, apiSub, ioSub) val discoverySub = testedBase(compilePath / "discover", "Discovery", compileIncrementalSub, apiSub) val compilePersistSub = project(compilePath / "persist", "Persist", new PersistProject(_), compileIncrementalSub, apiSub) val compilerSub = project(compilePath, "Compile", new CompileProject(_), - launchInterfaceSub, interfaceSub, ivySub, ioSub, classpathSub, compileInterfaceSub) - - val taskSub = testedBase(tasksPath, "Tasks", controlSub, collectionSub) - val cacheSub = project(cachePath, "Cache", new CacheProject(_), ioSub, collectionSub) + launchInterfaceSub, interfaceSub, ivySub, ioSub, classpathSub, compileInterfaceSub, logSub, processSub) val altCompilerSub = baseProject("main", "Alternate Compiler Test", compileIncrementalSub, compilerSub, ioSub, logSub, discoverySub, compilePersistSub) @@ -40,7 +44,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths val trackingSub = baseProject(cachePath / "tracking", "Tracking", cacheSub) - val sbtSub = project(sbtPath, "Simple Build Tool", new SbtProject(_) {}, compilerSub, launchInterfaceSub) + val sbtSub = project(sbtPath, "Simple Build Tool", new SbtProject(_) {}, compilerSub, launchInterfaceSub, testingSub, cacheSub, taskSub) val installerSub = project(sbtPath / "install", "Installer", new InstallerProject(_) {}, sbtSub) lazy val dist = task { None } dependsOn(launchSub.proguard, sbtSub.publishLocal, installerSub.publishLocal) @@ -123,6 +127,10 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths def seePaths(noticeString: String): List[Path] = seeRegex.findAllIn(noticeString).matchData.map(d => licensePath(d.group(1))).toList def extractLicenses = if(!notice.exists) Nil else FileUtilities.readString(notice asFile, log).fold(_ => { log.warn("Could not read NOTICE"); Nil} , seePaths _) } + class TestingProject(info: ProjectInfo) extends Base(info) + { + val testInterface = "org.scala-tools.testing" % "test-interface" % "0.5" + } class CompileProject(info: ProjectInfo) extends Base(info) with TestWithLog with TestWithLaunch { override def testCompileAction = super.testCompileAction dependsOn(compileInterfaceSub.`package`, interfaceSub.`package`) diff --git a/sbt/src/main/scala/sbt/Compile.scala b/sbt/src/main/scala/sbt/Compile.scala index 684fe7430..f89366f40 100644 --- a/sbt/src/main/scala/sbt/Compile.scala +++ b/sbt/src/main/scala/sbt/Compile.scala @@ -6,10 +6,6 @@ package sbt import java.io.File import xsbt.{AnalyzingCompiler, CompileFailed, CompilerArguments, ScalaInstance} -object CompileOrder extends Enumeration -{ - val Mixed, JavaThenScala, ScalaThenJava = Value -} sealed abstract class CompilerCore { @@ -75,50 +71,6 @@ final class Compile(maximumErrors: Int, compiler: AnalyzingCompiler, analysisCal val callbackInterface = new AnalysisInterface(analysisCallback, baseDirectory, outputDirectory) compiler(Set() ++ sources, Set() ++ classpath, outputDirectory, options, callbackInterface, maximumErrors, log) } - protected def processJava(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], log: Logger) - { - val augmentedClasspath = if(compiler.cp.autoBoot) classpath + compiler.scalaInstance.libraryJar else classpath - val cp = new xsbt.ClasspathOptions(false, compiler.cp.compiler, false) - val arguments = (new CompilerArguments(compiler.scalaInstance, cp))(sources, augmentedClasspath, outputDirectory, options) - log.debug("running javac with arguments:\n\t" + arguments.mkString("\n\t")) - val code: Int = - try { directJavac(arguments, log) } - catch { case e: ClassNotFoundException => forkJavac(arguments, log) } - log.debug("javac returned exit code: " + code) - if( code != 0 ) throw new CompileFailed(arguments.toArray, "javac returned nonzero exit code") - } - private def forkJavac(arguments: Seq[String], log: Logger): Int = - { - log.debug("com.sun.tools.javac.Main not found; forking javac instead") - def externalJavac(argFile: File) = Process("javac", ("@" + normalizeSlash(argFile.getAbsolutePath)) :: Nil) ! log - withArgumentFile(arguments)(externalJavac) - } - private def directJavac(arguments: Seq[String], log: Logger): Int = - { - val writer = new java.io.PrintWriter(new LoggerWriter(log, Level.Error)) - val argsArray = arguments.toArray - val javac = Class.forName("com.sun.tools.javac.Main") - log.debug("Calling javac directly.") - javac.getDeclaredMethod("compile", classOf[Array[String]], classOf[java.io.PrintWriter]) - .invoke(null, argsArray, writer) - .asInstanceOf[java.lang.Integer] - .intValue - } -} -trait WithArgumentFile extends NotNull -{ - def withArgumentFile[T](args: Seq[String])(f: File => T): T = - { - import xsbt.FileUtilities._ - withTemporaryDirectory { tmp => - val argFile = new File(tmp, "argfile") - write(argFile, args.map(escapeSpaces).mkString(FileUtilities.Newline)) - f(argFile) - } - } - // javac's argument file seems to allow naive space escaping with quotes. escaping a quote with a backslash does not work - def escapeSpaces(s: String): String = '\"' + normalizeSlash(s) + '\"' - def normalizeSlash(s: String) = s.replace(File.separatorChar, '/') } final class Scaladoc(maximumErrors: Int, compiler: AnalyzingCompiler) extends CompilerCore { @@ -142,40 +94,3 @@ final class Console(compiler: AnalyzingCompiler) extends NotNull JLine.withJLine( Run.executeTrapExit(console0, log) ) } } - -private final class AnalysisInterface(delegate: AnalysisCallback, basePath: Path, outputDirectory: File) extends xsbti.AnalysisCallback with NotNull -{ - val outputPath = Path.fromFile(outputDirectory) - def superclassNames = delegate.superclassNames.toSeq.toArray[String] - def annotationNames = delegate.annotationNames.toSeq.toArray[String] - def superclassNotFound(superclassName: String) = delegate.superclassNotFound(superclassName) - def beginSource(source: File) = delegate.beginSource(srcPath(source)) - - def foundSubclass(source: File, subclassName: String, superclassName: String, isModule: Boolean) = - delegate.foundSubclass(srcPath(source), subclassName, superclassName, isModule) - def foundAnnotated(source: File, className: String, annotationName: String, isModule: Boolean) = - delegate.foundAnnotated(srcPath(source), className, annotationName, isModule) - def foundApplication(source: File, className: String) = delegate.foundApplication(srcPath(source), className) - - def sourceDependency(dependsOn: File, source: File) = - delegate.sourceDependency(srcPath(dependsOn), srcPath(source)) - def jarDependency(jar: File, source: File) = delegate.jarDependency(jar, srcPath(source)) - def generatedClass(source: File, clazz: File) = delegate.generatedClass(srcPath(source), classPath(clazz)) - def endSource(source: File) = delegate.endSource(srcPath(source)) - - def classDependency(clazz: File, source: File) = - { - val sourcePath = srcPath(source) - Path.relativize(outputPath, clazz) match - { - case None => // dependency is a class file outside of the output directory - delegate.classDependency(clazz, sourcePath) - case Some(relativeToOutput) => // dependency is a product of a source not included in this compilation - delegate.productDependency(relativeToOutput, sourcePath) - } - } - def relativizeOrAbs(base: Path, file: File) = Path.relativize(base, file).getOrElse(Path.fromFile(file)) - def classPath(file: File) = relativizeOrAbs(outputPath, file) - def srcPath(file: File) = relativizeOrAbs(basePath, file) - def api(file: File, source: xsbti.api.Source) = delegate.api(srcPath(file), source) -} \ No newline at end of file diff --git a/sbt/src/main/scala/sbt/Logger.scala b/sbt/src/main/scala/sbt/Logger.scala index 29a938797..f0d4b4709 100644 --- a/sbt/src/main/scala/sbt/Logger.scala +++ b/sbt/src/main/scala/sbt/Logger.scala @@ -5,9 +5,6 @@ package sbt import scala.collection.mutable.{Buffer, HashMap, ListBuffer} - -trait Logger extends AbstractLogger with xsbt.CompileLogger with IvyLogger - /** A logger that can buffer the logging done on it by currently executing Thread and * then can flush the buffer to the delegate logger provided in the constructor. Use * 'startRecording' to start buffering and then 'play' from to flush the buffer for the @@ -19,7 +16,7 @@ trait Logger extends AbstractLogger with xsbt.CompileLogger with IvyLogger * * This logger is thread-safe. * */ -final class BufferedLogger(delegate: AbstractLogger) extends Logger +final class BufferedLogger(delegate: Logger) extends AbstractLogger { override lazy val ansiCodesSupported = delegate.ansiCodesSupported private[this] val buffers = wrap.Wrappers.weakMap[Thread, Buffer[LogEvent]] diff --git a/sbt/src/main/scala/sbt/Main.scala b/sbt/src/main/scala/sbt/Main.scala index 49a93e568..61a7e40d0 100755 --- a/sbt/src/main/scala/sbt/Main.scala +++ b/sbt/src/main/scala/sbt/Main.scala @@ -740,7 +740,8 @@ class xMain extends xsbti.AppMain if(actionValid) { var count = 0 - SourceModificationWatch.watchUntil(project, ContinuousCompilePollDelaySeconds)(shouldTerminate) + val sourcesFinder: PathFinder = (Path.emptyPathFinder /: project.topologicalSort)(_ +++ _.watchPaths) + SourceModificationWatch.watchUntil(sourcesFinder, ContinuousCompilePollDelaySeconds)(shouldTerminate) { count += 1 handleAction(project, action) diff --git a/sbt/src/main/scala/sbt/Pack.scala b/sbt/src/main/scala/sbt/Pack.scala deleted file mode 100644 index ecfed74a2..000000000 --- a/sbt/src/main/scala/sbt/Pack.scala +++ /dev/null @@ -1,87 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2009 Mark Harrah - */ -package sbt - -import java.io.{File, FileOutputStream} -import java.util.jar.{JarEntry, JarFile, JarOutputStream, Pack200} -import scala.collection.Map -import FileUtilities._ - -object Pack -{ - def pack(jarPath: Path, out: Path, log: Logger): Option[String] = pack(jarPath, out, defaultPackerOptions, log) - def pack(jarPath: Path, out: Path, options: Map[String, String], log: Logger): Option[String] = - { - val packer = Pack200.newPacker - val properties = new wrap.MutableMapWrapper(packer.properties) - properties ++= options - - OpenResource.jarFile(false).ioOption(jarPath.asFile, "applying pack200 compression to jar", log) { f => - writeStream(out.asFile, log) { stream => - packer.pack(f, stream) - None - } - } - } - def unpack(packedPath: Path, toJarPath: Path, log: Logger): Option[String] = - { - val unpacker = Pack200.newUnpacker - writeStream(toJarPath.asFile, log) { fileStream => - val jarOut = new JarOutputStream(fileStream) - Control.trapUnitAndFinally("Error unpacking '" + packedPath + "': ", log) - { unpacker.unpack(packedPath.asFile, jarOut); None } - { jarOut.close() } - } - } - def defaultPackerOptions: Map[String, String] = scala.collection.immutable.Map() -} - -import java.net.URL -/** This is somewhat of a mess and is not entirely correct. jarsigner doesn't work properly -* on scalaz and it is difficult to determine whether a jar is both signed and valid. */ -object SignJar -{ - final class SignOption private[SignJar](val toList: List[String], val signOnly: Boolean) extends NotNull - { - override def toString = toList.mkString(" ") - } - def keyStore(url: URL) = new SignOption("-keystore" :: url.toExternalForm :: Nil, true) - def signedJar(p: Path) = new SignOption("-signedjar" :: p.asFile.getAbsolutePath :: Nil, true) - def verbose = new SignOption("-verbose" :: Nil, false) - def sigFile(name: String) = new SignOption("-sigfile" :: name :: Nil, true) - def storeType(t: String) = new SignOption("-storetype" :: t :: Nil, false) - def provider(p: String) = new SignOption("-provider" :: p :: Nil, false) - def providerName(p: String) = new SignOption("-providerName" :: p :: Nil, false) - def storePassword(p: String) = new SignOption("-storepass" :: p :: Nil, true) - def keyPassword(p: String) = new SignOption("-keypass" :: p :: Nil, true) - - private def VerifyOption = "-verify" - - /** Uses jarsigner to sign the given jar. */ - def sign(jarPath: Path, alias: String, options: Seq[SignOption], log: Logger): Option[String] = - { - require(!alias.trim.isEmpty, "Alias cannot be empty") - val arguments = options.toList.flatMap(_.toList) ::: jarPath.asFile.getAbsolutePath :: alias :: Nil - execute("Signed " + jarPath, "signing", arguments, log) - } - /** Uses jarsigner to verify the given jar.*/ - def verify(jarPath: Path, options: Seq[SignOption], log: Logger): Option[String] = - { - val arguments = options.filter(!_.signOnly).toList.flatMap(_.toList) ::: VerifyOption :: jarPath.asFile.getAbsolutePath :: Nil - execute("Verified " + jarPath, "verifying", arguments, log) - } - private def execute(successMessage: String, action: String, arguments: List[String], log: Logger): Option[String] = - { - val exitCode = Process(CommandName, arguments) ! log - if(exitCode == 0) - { - log.debug(successMessage) - None - } - else - Some("Error " + action + " jar (exit code was " + exitCode + ".)") - } - - private val CommandName = "jarsigner" -} \ No newline at end of file diff --git a/sbt/src/main/scala/sbt/SourceModificationWatch.scala b/sbt/src/main/scala/sbt/SourceModificationWatch.scala deleted file mode 100644 index 4aeb21746..000000000 --- a/sbt/src/main/scala/sbt/SourceModificationWatch.scala +++ /dev/null @@ -1,31 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2009 Mikko Peltonen, Mark Harrah - */ -package sbt - -object SourceModificationWatch -{ - def watchUntil(project: Project, pollDelaySec: Int)(terminationCondition: => Boolean)(onSourcesModified: => Unit) - { - def sourceFiles: Iterable[java.io.File] = sourcesFinder.getFiles - def sourcesFinder: PathFinder = (Path.emptyPathFinder /: project.topologicalSort)(_ +++ _.watchPaths) - def loop(lastCallbackCallTime: Long, previousFileCount: Int) - { - val (lastModifiedTime, fileCount) = sourceFiles.foldLeft((0L, 0)){(acc, file) => (Math.max(acc._1, file.lastModified), acc._2 + 1)} - val newCallbackCallTime = - // check if sources are modified - if (lastModifiedTime > lastCallbackCallTime || previousFileCount != fileCount) - { - val now = System.currentTimeMillis - onSourcesModified - now - } - else - lastCallbackCallTime - Thread.sleep(pollDelaySec * 1000) - if(!terminationCondition) - loop(newCallbackCallTime, fileCount) - } - loop(0L, 0) - } -} \ No newline at end of file diff --git a/sbt/src/test/scala/sbt/FileUtilitiesSpecification.scala b/sbt/src/test/scala/sbt/FileUtilitiesSpecification.scala deleted file mode 100644 index f3054a105..000000000 --- a/sbt/src/test/scala/sbt/FileUtilitiesSpecification.scala +++ /dev/null @@ -1,84 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2008, 2009 Mark Harrah - */ -package sbt - -import org.scalacheck._ -import java.io.File - -object WriteContentSpecification extends Properties("Write content") -{ - val log = new ConsoleLogger - log.setLevel(Level.Warn) - - specify("Roundtrip string", writeAndCheckString _) - specify("Roundtrip bytes", writeAndCheckBytes _) - specify("Write string overwrites", overwriteAndCheckStrings _) - specify("Write bytes overwrites", overwriteAndCheckBytes _) - specify("Append string appends", appendAndCheckStrings _) - specify("Append bytes appends", appendAndCheckBytes _) - property("Unzip doesn't stack overflow") = largeUnzip() match { case Some(msg) => error(msg); case None => true } - - private def largeUnzip() = - testUnzip[ScalaObject] orElse - testUnzip[scala.tools.nsc.Global] - private def testUnzip[T](implicit mf: scala.reflect.Manifest[T]) = - unzipFile(FileUtilities.classLocationFile(mf.erasure)) - private def unzipFile(jar: File) = - FileUtilities.withTemporaryDirectory(log) { tmp => - FileUtilities.unzip(jar, Path.fromFile(tmp), log).left.toOption - } - - // make the test independent of underlying platform and allow any unicode character in Strings to be encoded - val charset = java.nio.charset.Charset.forName("UTF-8") - - import FileUtilities._ - private def writeAndCheckString(s: String) = - { - val result = withTemporaryFile( file => writeThen(file, s)( readString(file, charset, log) ) ) - handleResult[String](result, _ == s) - } - private def writeAndCheckBytes(b: Array[Byte]) = - { - val result = withTemporaryFile( file => writeThen(file, b)( readBytes(file, log) ) ) - handleResult[Array[Byte]](result, _ deepEquals b) - } - private def overwriteAndCheckStrings(a: String, b: String) = - { - val result = withTemporaryFile( file => writeThen(file, a)( writeThen(file, b)( readString(file, charset, log) ) ) ) - handleResult[String](result, _ == b) - } - private def overwriteAndCheckBytes(a: Array[Byte], b: Array[Byte]) = - { - val result = withTemporaryFile( file => writeThen(file, a)( writeThen(file, b)( readBytes(file, log) ) ) ) - handleResult[Array[Byte]](result, _ deepEquals b) - } - private def appendAndCheckStrings(a: String, b: String) = - { - val result = withTemporaryFile( file => appendThen(file, a)( appendThen(file, b)( readString(file, charset, log) ) ) ) - handleResult[String](result, _ == (a+b)) - } - private def appendAndCheckBytes(a: Array[Byte], b: Array[Byte]) = - { - val result = withTemporaryFile( file => appendThen(file, a)( appendThen(file, b)( readBytes(file, log) ) ) ) - handleResult[Array[Byte]](result, _ deepEquals (a++b)) - } - - private def withTemporaryFile[T](f: File => Either[String, T]): Either[String, T] = - doInTemporaryDirectory(log) { dir => f(new java.io.File(dir, "out")) } - - private def handleResult[T](result: Either[String, T], check: T => Boolean): Boolean = - result match - { - case Left(err) => log.trace(new RuntimeException(err)); log.error(err); false - case Right(x) => check(x) - } - private def writeThen[T](file: File, content: String)(action: => Either[String, T]) = - write(file, content, charset, log).toLeft(()).right.flatMap { x =>action } - private def writeThen[T](file: File, content: Array[Byte])(action: => Either[String, T]) = - write(file, content, log).toLeft(()).right.flatMap { x =>action } - private def appendThen[T](file: File, content: String)(action: => Either[String, T]) = - append(file, content, charset, log).toLeft(()).right.flatMap { x =>action } - private def appendThen[T](file: File, content: Array[Byte])(action: => Either[String, T]) = - append(file, content, log).toLeft(()).right.flatMap { x =>action } -} \ No newline at end of file diff --git a/sbt/src/test/scala/sbt/ReflectSpecification.scala b/sbt/src/test/scala/sbt/ReflectSpecification.scala deleted file mode 100644 index 515c220a5..000000000 --- a/sbt/src/test/scala/sbt/ReflectSpecification.scala +++ /dev/null @@ -1,182 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2008 Mark Harrah - */ -package sbt - -// Still TODO: -// inheritance- hierarchy, overriding - -import org.scalacheck._ -import scala.reflect.Manifest - -// specify members -// specify classes -// map members to classes -// compile, instantiate base -// get all vals -// validate vals - -object ReflectiveSpecification extends Properties("ReflectUtilities") -{ - import ReflectiveArbitrary._ - // pick other modifiers, any name, any type for the member, any type to find, - // pick a class hierarchy, select any class from that hierarchy, add member - // instantiate instance, perform reflection and verify it is empty - specify("Public found", publicFound _) - specify("Private hidden", privateHidden _) - - private def publicFound(isFinal: Boolean, decl: DeclarationType, name: Identifier, rType: ReturnType, - findType: ReturnType, container: ConcreteContainer) = - { - val modifiers: Set[Modifier] = if(isFinal) Set(Final) else Set() - val member = Member(Public, modifiers, decl, name.toString, rType, true) - val shouldFind = decl != Def && rType.manifest <:< findType.manifest - allVals(Map(container -> List(member)), container, findType.manifest).isEmpty == !shouldFind - } - private def privateHidden(isFinal: Boolean, decl: DeclarationType, name: Identifier, rType: ReturnType, - findType: ReturnType, container: ConcreteContainer) = - { - val scope = Private(None) - val modifiers: Set[Modifier] = if(isFinal) Set(Final) else Set() - val member = Member(scope, modifiers, decl, name.toString, rType, true) - allVals(Map(container -> List(member)), container, findType.manifest).isEmpty - } - private def allVals(classes: Map[Container, List[Member]], check: Container, manifest: Manifest[_]) = - { - val instance = ReflectiveCreate.compileInstantiate(classes, check) - ReflectUtilities.allVals(instance)(manifest) - } -} - -object ReflectiveArbitrary -{ - implicit val arbIdentifier: Arbitrary[Identifier] = Arbitrary { for(id <- Gen.identifier) yield Identifier(id) } - implicit val arbDeclarationType: Arbitrary[DeclarationType] = Arbitrary { Gen.elements(Val, Def, LazyVal) } - implicit val arbModifier: Arbitrary[Modifier] = Arbitrary { Gen.elements(Final, Abstract, Override) } - // TODO: parameterize - implicit val arbType: Arbitrary[ReturnType] = - Arbitrary { Gen.elements(classes: _*) } - implicit val arbConcrete: Arbitrary[ConcreteContainer] = Arbitrary(genConcrete) - //implicit val arbContainer: Arbitrary[Container] = Arbitrary { Gen.oneOf(arbConcreteContainer, arbTrait) } - //implicit val arbTrait: Arbitrary[Trait] = Arbitrary { } - - //TODO: inheritance - def genConcrete = for(name <- Gen.identifier) yield ConcreteClass(name, None, Nil) - - val classes = List[ReturnType]( - typ[String]("String", "null"), - typ[Object]("Object", "null"), - typ[Int]("Int", "0"), - typ[List[String]]("List[String]", "Nil"), - typ[Option[Int]]("Option[Int]", "None") ) - - def typ[T](name: String, defaultValue: String)(implicit mf: Manifest[T]) = - BasicReturnType(name, Nil, mf, defaultValue) -} - -object ReflectiveCreate -{ - import scala.collection.mutable - - def compileInstantiate(classes: Map[Container, List[Member]], instantiate: Container): AnyRef = - { - val log = new ConsoleLogger - log.setLevel(Level.Warn) - val code = new StringBuilder - def addMember(m: Member) - { - code.append(m.toString) - code.append("\n") - } - def addClass(c: Container, m: List[Member]) - { - code.append(c.signature) - code.append(" {\n") - m.foreach(addMember) - code.append(" }\n") - } - for((c, members) <- classes) addClass(c, members) - - val codeString = code.toString - def doCompileInstantiate(dir: java.io.File): Either[String, AnyRef] = - { - val basePath = new ProjectDirectory(dir) - val source = basePath / "a.scala" - val sourceFile = source.asFile - val outputDirectory = basePath / "target" - for(writeOK <- FileUtilities.write(sourceFile, codeString, log).toLeft("").right; - compileOK <- Left("").right)//(new RawCompile(100))("reflect", source :: Nil, "", outputDirectory, Nil, log).toLeft("").right) - yield - { - error("Reintegrate") - val loader = new java.net.URLClassLoader(Array(outputDirectory.asURL), getClass.getClassLoader) - val c = Class.forName(instantiate.name, true, loader) - c.newInstance.asInstanceOf[AnyRef] - } - } - FileUtilities.doInTemporaryDirectory(log)(doCompileInstantiate) match - { - case Left(err) => log.error(err); log.error(codeString); throw new RuntimeException(err) - case Right(x) => x - } - } -} - -final case class Identifier(override val toString: String) extends NotNull - -sealed abstract class Modifier(override val toString: String) extends NotNull -object Final extends Modifier("final") -object Abstract extends Modifier("abstract") -object Override extends Modifier("override") - -sealed trait Scope extends NotNull -sealed abstract class QualifiedScope(label: String, qualifier: Option[String]) extends Scope -{ - override def toString = label + qualifier.map("[" + _ + "]").getOrElse("") -} -final case class Private(qualifier: Option[String]) extends QualifiedScope("private", qualifier) -final case class Protected(qualifier: Option[String]) extends QualifiedScope("protected", qualifier) -final object Public extends Scope { override def toString = "" } - - -sealed abstract class DeclarationType(override val toString: String) extends NotNull -object LazyVal extends DeclarationType("lazy val") -object Val extends DeclarationType("val") -object Def extends DeclarationType("def") - -sealed abstract class Container(prefix: String) extends NotNull -{ - def signature: String = prefix + " " + name + parents.map(_.name).mkString(" extends ", " with ", "") - def name: String - def mixins: List[Trait] - def parents: List[Container] = mixins -} -sealed abstract class ClassContainer(prefix: String) extends Container(prefix) -{ - def base: Option[ClassContainer] - override def parents = base.toList ::: mixins -} -sealed abstract class ConcreteContainer(prefix: String) extends ClassContainer(prefix) -final case class AbstractClass(name: String, base: Option[ClassContainer], mixins: List[Trait]) extends ClassContainer("abstract class") -final case class ConcreteClass(name: String, base: Option[ClassContainer], mixins: List[Trait]) extends ConcreteContainer("class") -final case class Module(name: String, base: Option[ClassContainer], mixins: List[Trait]) extends ConcreteContainer("object") -final case class Trait(name: String, mixins: List[Trait]) extends Container("trait") - -trait ReturnType -{ - def name: String - def parameters: List[ReturnType] - def manifest: Manifest[_] - def defaultValue: String - override def toString = name -} -sealed case class BasicReturnType(name: String, parameters: List[ReturnType], - manifest: Manifest[_], defaultValue: String) extends ReturnType - -case class Member(scope: Scope, modifiers: Set[Modifier], declaration: DeclarationType, - name: String, mType: ReturnType, valueSpecified: Boolean) extends NotNull -{ - override def toString = scope.toString + modifiers.mkString(" ", " "," ") + - declaration.toString + " " + name + " : " + mType.toString + - (if(valueSpecified) " = " + mType.defaultValue else "") -} \ No newline at end of file diff --git a/testing/NOTICE b/testing/NOTICE new file mode 100644 index 000000000..2853703b9 --- /dev/null +++ b/testing/NOTICE @@ -0,0 +1,3 @@ +Simple Build Tool: Testing Component +Copyright 2008, 2009, 2010 Steven Blundy, Mark Harrah, Josh Cough +Licensed under BSD-style license (see LICENSE) \ No newline at end of file diff --git a/sbt/src/main/scala/sbt/TestFramework.scala b/testing/TestFramework.scala similarity index 92% rename from sbt/src/main/scala/sbt/TestFramework.scala rename to testing/TestFramework.scala index f8120c556..5e0932e1a 100644 --- a/sbt/src/main/scala/sbt/TestFramework.scala +++ b/testing/TestFramework.scala @@ -6,7 +6,7 @@ package sbt import java.net.URLClassLoader import org.scalatools.testing.{AnnotatedFingerprint, Fingerprint, SubclassFingerprint, TestFingerprint} import org.scalatools.testing.{Event, EventHandler, Framework, Runner, Runner2, Logger=>TLogger} - import xsbt.ScalaInstance + import classpath.{ClasspathUtilities, DualLoader, FilteredLoader} object Result extends Enumeration { @@ -44,7 +44,7 @@ final class TestDefinition(val name: String, val fingerprint: Fingerprint) exten } } -final class TestRunner(framework: Framework, loader: ClassLoader, listeners: Seq[TestReportListener], log: Logger) extends NotNull +final class TestRunner(framework: Framework, loader: ClassLoader, listeners: Seq[TestReportListener], log: Logger) { private[this] val delegate = framework.testRunner(loader, listeners.flatMap(_.contentLogger).toArray) private[this] def run(testDefinition: TestDefinition, handler: EventHandler, args: Array[String]): Unit = @@ -89,7 +89,7 @@ final class TestRunner(framework: Framework, loader: ClassLoader, listeners: Seq TestFramework.safeForeach(listeners, log)(call) } -final class NamedTestTask(val name: String, action: => Option[String]) extends NotNull { def run() = action } +final class NamedTestTask(val name: String, action: => Unit) { def run() = action } object TestFramework { @@ -107,7 +107,7 @@ object TestFramework private val TestFinishName = "test-finish" private[sbt] def safeForeach[T](it: Iterable[T], log: Logger)(f: T => Unit): Unit = - it.foreach(i => Control.trapAndLog(log){ f(i) } ) + it.foreach(i => try f(i) catch { case e: Exception => log.trace(e); log.error(e.toString) }) def matches(a: Fingerprint, b: Fingerprint) = (a, b) match @@ -126,8 +126,8 @@ object TestFramework log: Logger, listeners: Seq[TestReportListener], endErrorsEnabled: Boolean, - setup: Iterable[() => Option[String]], - cleanup: Iterable[() => Option[String]], + setup: Iterable[() => Unit], + cleanup: Iterable[() => Unit], testArgsByFramework: Map[TestFramework, Seq[String]]): (Iterable[NamedTestTask], Iterable[NamedTestTask], Iterable[NamedTestTask]) = { @@ -135,7 +135,7 @@ object TestFramework val arguments = immutable.Map() ++ ( for(framework <- frameworks; created <- framework.create(loader, log)) yield (created, testArgsByFramework.getOrElse(framework, Nil)) ) - val cleanTmp = () => FileUtilities.clean(tempDir, log) + val cleanTmp = () => IO.delete(tempDir) val mappedTests = testMap(arguments.keys.toList, tests, arguments) if(mappedTests.isEmpty) @@ -162,12 +162,12 @@ object TestFramework assignTests() (immutable.Map() ++ map) transform { (framework, tests) => (tests, args(framework)) } } - private def createTasks(work: Iterable[() => Option[String]], baseName: String) = + private def createTasks(work: Iterable[() => Unit], baseName: String) = work.toList.zipWithIndex.map{ case (work, index) => new NamedTestTask(baseName + " " + (index+1), work()) } private def createTestTasks(loader: ClassLoader, tests: Map[Framework, (Set[TestDefinition], Seq[String])], log: Logger, - listeners: Seq[TestReportListener], endErrorsEnabled: Boolean, setup: Iterable[() => Option[String]], - cleanup: Iterable[() => Option[String]]) = + listeners: Seq[TestReportListener], endErrorsEnabled: Boolean, setup: Iterable[() => Unit], + cleanup: Iterable[() => Unit]) = { val testsListeners = listeners.filter(_.isInstanceOf[TestsListener]).map(_.asInstanceOf[TestsListener]) def foreachListenerSafe(f: TestsListener => Unit): Unit = safeForeach(testsListeners, log)(f) @@ -228,7 +228,7 @@ object TestFramework val filterCompilerLoader = new FilteredLoader(scalaInstance.loader, ScalaCompilerJarPackages) val interfaceFilter = (name: String) => name.startsWith("org.scalatools.testing.") val notInterfaceFilter = (name: String) => !interfaceFilter(name) - val dual = new xsbt.DualLoader(filterCompilerLoader, notInterfaceFilter, x => true, getClass.getClassLoader, interfaceFilter, x => false) + val dual = new DualLoader(filterCompilerLoader, notInterfaceFilter, x => true, getClass.getClassLoader, interfaceFilter, x => false) ClasspathUtilities.makeLoader(classpath, dual, scalaInstance) } } diff --git a/sbt/src/main/scala/sbt/TestReportListener.scala b/testing/TestReportListener.scala similarity index 100% rename from sbt/src/main/scala/sbt/TestReportListener.scala rename to testing/TestReportListener.scala diff --git a/sbt/src/main/scala/sbt/impl/TestParser.scala b/testing/impl/TestParser.scala similarity index 100% rename from sbt/src/main/scala/sbt/impl/TestParser.scala rename to testing/impl/TestParser.scala diff --git a/sbt/src/main/scala/sbt/impl/TestStatusReporter.scala b/testing/impl/TestStatusReporter.scala similarity index 100% rename from sbt/src/main/scala/sbt/impl/TestStatusReporter.scala rename to testing/impl/TestStatusReporter.scala diff --git a/util/classpath/ClassLoaders.scala b/util/classpath/ClassLoaders.scala index 1ee1bfc72..af3c039b5 100644 --- a/util/classpath/ClassLoaders.scala +++ b/util/classpath/ClassLoaders.scala @@ -1,7 +1,8 @@ /* sbt -- Simple Build Tool * Copyright 2008, 2009 Mark Harrah */ -package xsbt +package sbt +package classpath import java.io.File import java.net.{URI, URL, URLClassLoader} diff --git a/sbt/src/main/scala/sbt/ClasspathUtilities.scala b/util/classpath/ClasspathUtilities.scala similarity index 77% rename from sbt/src/main/scala/sbt/ClasspathUtilities.scala rename to util/classpath/ClasspathUtilities.scala index 8703b66f3..b9f8d6c5e 100644 --- a/sbt/src/main/scala/sbt/ClasspathUtilities.scala +++ b/util/classpath/ClasspathUtilities.scala @@ -2,14 +2,14 @@ * Copyright 2008, 2009, 2010 Mark Harrah */ package sbt +package classpath import java.io.File import java.net.{URI, URL, URLClassLoader} import java.util.Collections import scala.collection.Set -import scala.collection.mutable.{HashSet, ListBuffer} -import xsbt.ScalaInstance -import xsbt.FileUtilities.{createTemporaryDirectory, withTemporaryDirectory, write} +import scala.collection.mutable.{HashSet, LinkedHashSet, ListBuffer} +import IO.{createTemporaryDirectory, write} object ClasspathUtilities { @@ -57,7 +57,7 @@ object ClasspathUtilities } private[sbt] def printSource(c: Class[_]) = - println(c.getName + " loader=" +c.getClassLoader + " location=" + FileUtilities.classLocationFile(c)) + println(c.getName + " loader=" +c.getClassLoader + " location=" + IO.classLocationFile(c)) def isArchive(path: Path): Boolean = isArchive(path.asFile) def isArchive(file: File): Boolean = isArchiveName(file.getName) @@ -66,16 +66,16 @@ object ClasspathUtilities private[sbt] def separate(paths: Iterable[File]): (Iterable[File], Iterable[File]) = paths.partition(isArchive) // Partitions the given classpath into (jars, directories) private[sbt] def separatePaths(paths: Iterable[Path]) = separate(paths.map(_.asFile.getCanonicalFile)) - private[sbt] def buildSearchPaths(classpath: Iterable[Path]): (wrap.Set[File], wrap.Set[File]) = + private[sbt] def buildSearchPaths(classpath: Iterable[Path]): (collection.Set[File], collection.Set[File]) = { val (jars, dirs) = separatePaths(classpath) (linkedSet(jars ++ extraJars.toList), linkedSet(dirs ++ extraDirs.toList)) } - private[sbt] def onClasspath(classpathJars: wrap.Set[File], classpathDirectories: wrap.Set[File], file: File): Boolean = + private[sbt] def onClasspath(classpathJars: collection.Set[File], classpathDirectories: collection.Set[File], file: File): Boolean = { val f = file.getCanonicalFile if(ClasspathUtilities.isArchive(f)) - classpathJars.contains(f) + classpathJars(f) else classpathDirectories.toList.find(Path.relativize(_, f).isDefined).isDefined } @@ -83,8 +83,9 @@ object ClasspathUtilities /** Returns all entries in 'classpath' that correspond to a compiler plugin.*/ private[sbt] def compilerPlugins(classpath: Iterable[Path]): Iterable[File] = { + import collection.JavaConversions._ val loader = new URLClassLoader(Path.getURLs(classpath)) - wrap.Wrappers.toList(loader.getResources("scalac-plugin.xml")).flatMap(asFile(true)) + loader.getResources("scalac-plugin.xml").toList.flatMap(asFile(true)) } /** Converts the given URL to a File. If the URL is for an entry in a jar, the File for the jar is returned. */ private[sbt] def asFile(url: URL): List[File] = asFile(false)(url) @@ -94,7 +95,7 @@ object ClasspathUtilities { url.getProtocol match { - case "file" if !jarOnly=> FileUtilities.toFile(url) :: Nil + case "file" if !jarOnly=> IO.toFile(url) :: Nil case "jar" => val path = url.getPath val end = path.indexOf('!') @@ -109,7 +110,7 @@ object ClasspathUtilities { import scala.tools.nsc.GenericRunnerCommand val settings = (new GenericRunnerCommand(Nil, message => error(message))).settings - val bootPaths = FileUtilities.pathSplit(settings.bootclasspath.value).map(p => new File(p)).toList + val bootPaths = IO.pathSplit(settings.bootclasspath.value).map(p => new File(p)).toList val (bootJars, bootDirs) = separate(bootPaths) val extJars = { @@ -120,41 +121,21 @@ object ClasspathUtilities for(dir <- dir.listFiles(DirectoryFilter)) findJars(dir) } - for(path <- FileUtilities.pathSplit(settings.extdirs.value); val dir = new File(path) if dir.isDirectory) + for(path <- IO.pathSplit(settings.extdirs.value); val dir = new File(path) if dir.isDirectory) findJars(dir) buffer.readOnly.map(_.getCanonicalFile) } (linkedSet(extJars ++ bootJars), linkedSet(bootDirs)) } - private def linkedSet[T](s: Iterable[T]): wrap.Set[T] = + private def linkedSet[T](s: Iterable[T]): Set[T] = { - val set = new wrap.MutableSetWrapper(new java.util.LinkedHashSet[T]) + val set = new LinkedHashSet[T] set ++= s - set.readOnly + set } } -private abstract class LoaderBase(urls: Array[URL], parent: ClassLoader) extends URLClassLoader(urls, parent) with NotNull -{ - require(parent != null) // included because a null parent is legitimate in Java - @throws(classOf[ClassNotFoundException]) - override final def loadClass(className: String, resolve: Boolean): Class[_] = - { - val loaded = findLoadedClass(className) - val found = - if(loaded == null) - doLoadClass(className) - else - loaded - - if(resolve) - resolveClass(found) - found - } - protected def doLoadClass(className: String): Class[_] - protected final def selfLoadClass(className: String): Class[_] = super.loadClass(className, false) -} -private class IntermediateLoader(urls: Array[URL], parent: ClassLoader) extends LoaderBase(urls, parent) with NotNull +private class IntermediateLoader(urls: Array[URL], parent: ClassLoader) extends LoaderBase(urls, parent) { def doLoadClass(className: String): Class[_] = { @@ -163,12 +144,12 @@ private class IntermediateLoader(urls: Array[URL], parent: ClassLoader) extends if(className.startsWith(Loaders.SbtPackage)) findClass(className) else - selfLoadClass(className) + defaultLoadClass(className) } } /** Delegates class loading to `parent` for all classes included by `filter`. An attempt to load classes excluded by `filter` * results in a `ClassNotFoundException`.*/ -private class FilteredLoader(parent: ClassLoader, filter: ClassFilter) extends ClassLoader(parent) with NotNull +class FilteredLoader(parent: ClassLoader, filter: ClassFilter) extends ClassLoader(parent) { require(parent != null) // included because a null parent is legitimate in Java def this(parent: ClassLoader, excludePackages: Iterable[String]) = this(parent, new ExcludePackagesFilter(excludePackages)) @@ -182,7 +163,7 @@ private class FilteredLoader(parent: ClassLoader, filter: ClassFilter) extends C throw new ClassNotFoundException(className) } } -private class SelectiveLoader(urls: Array[URL], parent: ClassLoader, filter: ClassFilter) extends URLClassLoader(urls, parent) with NotNull +private class SelectiveLoader(urls: Array[URL], parent: ClassLoader, filter: ClassFilter) extends URLClassLoader(urls, parent) { require(parent != null) // included because a null parent is legitimate in Java def this(urls: Array[URL], parent: ClassLoader, includePackages: Iterable[String]) = this(urls, parent, new IncludePackagesFilter(includePackages)) @@ -201,26 +182,26 @@ private class SelectiveLoader(urls: Array[URL], parent: ClassLoader, filter: Cla } } } -private trait ClassFilter +trait ClassFilter { def include(className: String): Boolean } -private abstract class PackageFilter(packages: Iterable[String]) extends ClassFilter +abstract class PackageFilter(packages: Iterable[String]) extends ClassFilter { require(packages.forall(_.endsWith("."))) protected final def matches(className: String): Boolean = packages.exists(className.startsWith) } -private class ExcludePackagesFilter(exclude: Iterable[String]) extends PackageFilter(exclude) +class ExcludePackagesFilter(exclude: Iterable[String]) extends PackageFilter(exclude) { def include(className: String): Boolean = !matches(className) } -private class IncludePackagesFilter(include: Iterable[String]) extends PackageFilter(include) +class IncludePackagesFilter(include: Iterable[String]) extends PackageFilter(include) { def include(className: String): Boolean = matches(className) } private class LazyFrameworkLoader(runnerClassName: String, urls: Array[URL], parent: ClassLoader, grandparent: ClassLoader) - extends LoaderBase(urls, parent) with NotNull + extends LoaderBase(urls, parent) { def doLoadClass(className: String): Class[_] = { diff --git a/util/classpath/DualLoader.scala b/util/classpath/DualLoader.scala index f42bc89a8..9d7ea972b 100644 --- a/util/classpath/DualLoader.scala +++ b/util/classpath/DualLoader.scala @@ -1,7 +1,8 @@ /* sbt -- Simple Build Tool * Copyright 2008, 2009 Mark Harrah */ -package xsbt +package sbt +package classpath import java.net.URL import java.util.Enumeration diff --git a/sbt/src/main/scala/sbt/ModuleUtilities.scala b/util/classpath/ModuleUtilities.scala similarity index 100% rename from sbt/src/main/scala/sbt/ModuleUtilities.scala rename to util/classpath/ModuleUtilities.scala diff --git a/util/classpath/NOTICE b/util/classpath/NOTICE index 23ba8821b..dd4fe824e 100644 --- a/util/classpath/NOTICE +++ b/util/classpath/NOTICE @@ -1,3 +1,3 @@ Simple Build Tool: Classpath Component -Copyright 2008, 2009 Mark Harrah +Copyright 2008, 2009, 2010 Mark Harrah, Stuart Roebuck Licensed under BSD-style license (see LICENSE) \ No newline at end of file diff --git a/sbt/src/main/scala/sbt/ReflectUtilities.scala b/util/classpath/ReflectUtilities.scala similarity index 96% rename from sbt/src/main/scala/sbt/ReflectUtilities.scala rename to util/classpath/ReflectUtilities.scala index 60d6fb2ab..f3790563e 100644 --- a/sbt/src/main/scala/sbt/ReflectUtilities.scala +++ b/util/classpath/ReflectUtilities.scala @@ -1,7 +1,7 @@ /* sbt -- Simple Build Tool * Copyright 2008 David MacIver, Mark Harrah */ -package sbt; +package sbt import scala.collection._ diff --git a/compile/ScalaInstance.scala b/util/classpath/ScalaInstance.scala similarity index 96% rename from compile/ScalaInstance.scala rename to util/classpath/ScalaInstance.scala index 9f13b1bbb..8ae6fefdf 100644 --- a/compile/ScalaInstance.scala +++ b/util/classpath/ScalaInstance.scala @@ -1,4 +1,7 @@ -package xsbt +/* sbt -- Simple Build Tool + * Copyright 2009, 2010 Mark Harrah + */ +package sbt import java.io.File diff --git a/util/complete/NOTICE b/util/complete/NOTICE new file mode 100644 index 000000000..a6f2c1de4 --- /dev/null +++ b/util/complete/NOTICE @@ -0,0 +1,3 @@ +Simple Build Tool: Completion Component +Copyright 2010 Mark Harrah +Licensed under BSD-style license (see LICENSE) \ No newline at end of file diff --git a/util/io/IO.scala b/util/io/IO.scala index 526f2ed67..06ca74b70 100644 --- a/util/io/IO.scala +++ b/util/io/IO.scala @@ -6,7 +6,7 @@ package sbt import Using._ import ErrorHandling.translate -import java.io.{ByteArrayOutputStream, BufferedWriter, File, FileInputStream, InputStream, OutputStream} +import java.io.{BufferedReader, ByteArrayOutputStream, BufferedWriter, File, FileInputStream, InputStream, OutputStream} import java.net.{URI, URISyntaxException, URL} import java.nio.charset.Charset import java.util.jar.{Attributes, JarEntry, JarFile, JarInputStream, JarOutputStream, Manifest} @@ -395,10 +395,11 @@ object IO } def copyLastModified(sourceFile: File, targetFile: File) = targetFile.setLastModified( sourceFile.lastModified ) def defaultCharset = utf8 - def write(file: File, content: String, charset: Charset = defaultCharset, append: Boolean = false): Unit = - writeCharset(file, content, charset, append) { _.write(content) } - def writeCharset[T](file: File, content: String, charset: Charset, append: Boolean)(f: BufferedWriter => T): T = + def write(file: File, content: String, charset: Charset = defaultCharset, append: Boolean = false): Unit = + writer(file, content, charset, append) { _.write(content) } + + def writer[T](file: File, content: String, charset: Charset, append: Boolean = false)(f: BufferedWriter => T): T = { if(charset.newEncoder.canEncode(content)) fileWriter(charset, append)(file) { f } @@ -406,6 +407,9 @@ object IO error("String cannot be encoded by charset " + charset.name) } + def reader[T](file: File, charset: Charset = defaultCharset)(f: BufferedReader => T): T = + fileReader(charset)(file) { f } + def read(file: File, charset: Charset = defaultCharset): String = { val out = new ByteArrayOutputStream(file.length.toInt) @@ -428,6 +432,17 @@ object IO out.toByteArray } + def append(file: File, content: String, charset: Charset = defaultCharset): Unit = + write(file, content, charset, true) + def append(file: File, bytes: Array[Byte]): Unit = + writeBytes(file, bytes, true) + + def write(file: File, bytes: Array[Byte]): Unit = + writeBytes(file, bytes, false) + private def writeBytes(file: File, bytes: Array[Byte], append: Boolean): Unit = + fileOutputStream(append)(file) { _.write(bytes) } + + // Not optimized for large files def readLines(file: File, charset: Charset = defaultCharset): List[String] = { @@ -441,7 +456,7 @@ object IO } } def writeLines(file: File, lines: Seq[String], charset: Charset = defaultCharset, append: Boolean = false): Unit = - writeCharset(file, lines.headOption.getOrElse(""), charset, append) { w => + writer(file, lines.headOption.getOrElse(""), charset, append) { w => lines.foreach { line => w.write(line); w.newLine() } } diff --git a/util/io/Pack.scala b/util/io/Pack.scala new file mode 100644 index 000000000..afa01718b --- /dev/null +++ b/util/io/Pack.scala @@ -0,0 +1,79 @@ +/* sbt -- Simple Build Tool + * Copyright 2009 Mark Harrah + */ +package sbt + +import java.io.{File, FileOutputStream} +import java.util.jar.{JarEntry, JarFile, JarOutputStream, Pack200} +import IO._ + +object Pack +{ + def pack(jarPath: File, out: File): Unit = pack(jarPath, out, defaultPackerOptions) + def pack(jarPath: File, out: File, options: Iterable[(String, String)]) + { + val packer = Pack200.newPacker + import collection.JavaConversions._ + packer.properties ++= options + + Using.jarFile(false)(jarPath) { f => + Using.fileOutputStream()(out) { stream => + packer.pack(f, stream) + } + } + } + def unpack(packedPath: File, toJarPath: File) + { + val unpacker = Pack200.newUnpacker + Using.fileOutputStream()(toJarPath) { fileStream => + Using.jarOutputStream(fileStream) { jarOut => + unpacker.unpack(packedPath, jarOut) + } + } + } + def defaultPackerOptions = scala.collection.immutable.Map() +} + +import java.net.URL +/** This is somewhat of a mess and is not entirely correct. jarsigner doesn't work properly +* on scalaz and it is difficult to determine whether a jar is both signed and valid. */ +object SignJar +{ + final class SignOption private[SignJar](val toList: List[String], val signOnly: Boolean) + { + override def toString = toList.mkString(" ") + } + def keyStore(url: URL) = new SignOption("-keystore" :: url.toExternalForm :: Nil, true) + def signedJar(p: Path) = new SignOption("-signedjar" :: p.asFile.getAbsolutePath :: Nil, true) + def verbose = new SignOption("-verbose" :: Nil, false) + def sigFile(name: String) = new SignOption("-sigfile" :: name :: Nil, true) + def storeType(t: String) = new SignOption("-storetype" :: t :: Nil, false) + def provider(p: String) = new SignOption("-provider" :: p :: Nil, false) + def providerName(p: String) = new SignOption("-providerName" :: p :: Nil, false) + def storePassword(p: String) = new SignOption("-storepass" :: p :: Nil, true) + def keyPassword(p: String) = new SignOption("-keypass" :: p :: Nil, true) + + private def VerifyOption = "-verify" + + /** Uses jarsigner to sign the given jar. */ + def sign(jarPath: File, alias: String, options: Seq[SignOption])(fork: (String, List[String]) => Int) + { + require(!alias.trim.isEmpty, "Alias cannot be empty") + val arguments = options.toList.flatMap(_.toList) ::: jarPath.getAbsolutePath :: alias :: Nil + execute("signing", arguments)(fork) + } + /** Uses jarsigner to verify the given jar.*/ + def verify(jarPath: File, options: Seq[SignOption])(fork: (String, List[String]) => Int) + { + val arguments = options.filter(!_.signOnly).toList.flatMap(_.toList) ::: VerifyOption :: jarPath.getAbsolutePath :: Nil + execute("verifying", arguments)(fork) + } + private def execute(action: String, arguments: List[String])(fork: (String, List[String]) => Int) + { + val exitCode = fork(CommandName, arguments) + if(exitCode != 0) + error("Error " + action + " jar (exit code was " + exitCode + ".)") + } + + private val CommandName = "jarsigner" +} \ No newline at end of file diff --git a/util/io/Path.scala b/util/io/Path.scala index 91917f6d8..ebfdc7a90 100644 --- a/util/io/Path.scala +++ b/util/io/Path.scala @@ -246,7 +246,8 @@ object Path extends Alternatives with Mapper } def fromFile(file: String): Path = fromFile(new File(file)) def fromFile(file: File): Path = new FilePath(file) - def fromFiles(files: Traversable[File]): Traversable[Path] = files.map(fromFile) + import collection.generic.{CanBuildFrom, FilterMonadic} + def fromFiles[Repr, That](files: FilterMonadic[File, Repr])(implicit bf: CanBuildFrom[Repr, Path, That]): That = files.map(fromFile) def getFiles(files: Traversable[Path]): immutable.Set[File] = files.map(_.asFile).toSet def getURLs(files: Traversable[Path]): Array[URL] = files.map(_.asURL).toArray diff --git a/util/io/SourceModificationWatch.scala b/util/io/SourceModificationWatch.scala new file mode 100644 index 000000000..20dd4d0a3 --- /dev/null +++ b/util/io/SourceModificationWatch.scala @@ -0,0 +1,37 @@ +/* sbt -- Simple Build Tool + * Copyright 2009, 2010 Mikko Peltonen, Stuart Roebuck, Mark Harrah + */ +package sbt + +object SourceModificationWatch +{ + def watchUntil(sourcesFinder: PathFinder, pollDelaySec: Int)(terminationCondition: => Boolean)(onSourcesModified: => Unit) + { + def sourceFiles: Iterable[java.io.File] = sourcesFinder.getFiles + def loop(lastCallbackCallTime: Long, previousFileCount: Int, awaitingQuietPeriod:Boolean) + { + val (lastModifiedTime, fileCount) = + ( (0L, 0) /: sourceFiles) {(acc, file) => (math.max(acc._1, file.lastModified), acc._2 + 1)} + + val sourcesModified = + lastModifiedTime > lastCallbackCallTime || + previousFileCount != fileCount + + val newCallbackCallTime = + if (sourcesModified && !awaitingQuietPeriod) + System.currentTimeMillis + else if (!sourcesModified && awaitingQuietPeriod) + { + onSourcesModified + lastCallbackCallTime + } + else + lastCallbackCallTime + + Thread.sleep(pollDelaySec * 1000) + if(!terminationCondition) + loop(newCallbackCallTime, fileCount, sourcesModified) + } + loop(0L, 0, false) + } +} \ No newline at end of file diff --git a/util/io/src/test/scala/FileUtilitiesSpecification.scala b/util/io/src/test/scala/FileUtilitiesSpecification.scala new file mode 100644 index 000000000..6bb7dd4ff --- /dev/null +++ b/util/io/src/test/scala/FileUtilitiesSpecification.scala @@ -0,0 +1,78 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package sbt + +import org.scalacheck._ +import Prop._ +import Arbitrary.{arbString => _, arbChar => _, _} +import java.io.{File, IOException} + +object WriteContentSpecification extends Properties("Write content") +{ + property("Roundtrip string") = forAll( writeAndCheckString _) + property("Roundtrip bytes") = forAll(writeAndCheckBytes _) + property("Write string overwrites") = forAll(overwriteAndCheckStrings _) + property("Write bytes overwrites") = forAll( overwriteAndCheckBytes _) + property("Append string appends") = forAll( appendAndCheckStrings _) + property("Append bytes appends") = forAll( appendAndCheckBytes _) + property("Unzip doesn't stack overflow") = largeUnzip() + + implicit lazy val validChar: Arbitrary[Char] = Arbitrary( for(i <- Gen.choose(0, 0xd7ff)) yield i.toChar ) + implicit lazy val validString: Arbitrary[String] = Arbitrary(arbitrary[List[Char]] map (_.mkString)) + + private def largeUnzip() = + { + testUnzip[ScalaObject] + testUnzip[scala.tools.nsc.Global] + true + } + private def testUnzip[T](implicit mf: scala.reflect.Manifest[T]) = + unzipFile(IO.classLocationFile(mf.erasure)) + private def unzipFile(jar: File) = + IO.withTemporaryDirectory { tmp => + IO.unzip(jar, tmp) + } + + // make the test independent of underlying platform and allow any unicode character in Strings to be encoded + val charset = java.nio.charset.Charset.forName("UTF-8") + + import IO._ + private def writeAndCheckString(s: String) = + withTemporaryFile { file => + write(file, s, charset) + read(file, charset) == s + } + private def writeAndCheckBytes(b: Array[Byte]) = + withTemporaryFile { file => + write(file, b) + readBytes(file) sameElements b + } + private def overwriteAndCheckStrings(a: String, b: String) = + withTemporaryFile { file => + write(file, a, charset) + write(file, b, charset) + read(file, charset) == b + } + private def overwriteAndCheckBytes(a: Array[Byte], b: Array[Byte]) = + withTemporaryFile { file => + write(file, a) + write(file, b) + readBytes(file) sameElements b + } + private def appendAndCheckStrings(a: String, b: String) = + withTemporaryFile { file => + append(file, a, charset) + append(file, b, charset) + read(file, charset) == (a+b) + } + private def appendAndCheckBytes(a: Array[Byte], b: Array[Byte]) = + withTemporaryFile { file => + append(file, a) + append(file, b) + readBytes(file) sameElements (a++b) + } + + private def withTemporaryFile[T](f: File => T): T = + withTemporaryDirectory { dir => f(new java.io.File(dir, "out")) } +} \ No newline at end of file diff --git a/util/log/BufferedLogger.scala b/util/log/BufferedLogger.scala index 689d21b14..054a4b55d 100644 --- a/util/log/BufferedLogger.scala +++ b/util/log/BufferedLogger.scala @@ -1,7 +1,7 @@ /* sbt -- Simple Build Tool * Copyright 2008, 2009, 2010 Mark Harrah */ - package xsbt +package sbt import sbt.{AbstractLogger, ControlEvent, Level, Log, LogEvent, SetLevel, SetTrace, Success, Trace} import scala.collection.mutable.ListBuffer @@ -50,14 +50,14 @@ class BufferedLogger(delegate: AbstractLogger) extends AbstractLogger def setLevel(newLevel: Level.Value) { - buffer += new SetLevel(newLevel) + buffer += new SetLevel(newLevel) delegate.setLevel(newLevel) } def getLevel = delegate.getLevel def getTrace = delegate.getTrace def setTrace(level: Int) { - buffer += new SetTrace(level) + buffer += new SetTrace(level) delegate.setTrace(level) } diff --git a/util/log/FullLogger.scala b/util/log/FullLogger.scala new file mode 100644 index 000000000..091664244 --- /dev/null +++ b/util/log/FullLogger.scala @@ -0,0 +1,24 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt + +/** Promotes the simple Logger interface to the full AbstractLogger interface. */ +class FullLogger(delegate: Logger, override val ansiCodesSupported: Boolean = false) extends BasicLogger +{ + def trace(t: => Throwable) + { + if(traceEnabled) + delegate.trace(t) + } + def log(level: Level.Value, message: => String) + { + if(atLevel(level)) + delegate.log(level, message) + } + def success(message: => String): Unit = + info(message) + def control(event: ControlEvent.Value, message: => String): Unit = + info(message) + def logAll(events: Seq[LogEvent]): Unit = events.foreach(log) +} diff --git a/util/log/Logger.scala b/util/log/Logger.scala index 1be353c4b..68e1f26d8 100644 --- a/util/log/Logger.scala +++ b/util/log/Logger.scala @@ -1,28 +1,20 @@ /* sbt -- Simple Build Tool - * Copyright 2008, 2009 Mark Harrah + * Copyright 2008, 2009, 2010 Mark Harrah */ package sbt import xsbti.{Logger => xLogger, F0} -abstract class AbstractLogger extends xLogger with NotNull +abstract class AbstractLogger extends Logger { def getLevel: Level.Value def setLevel(newLevel: Level.Value) def setTrace(flag: Int) def getTrace: Int final def traceEnabled = getTrace >= 0 - def ansiCodesSupported = false def atLevel(level: Level.Value) = level.id >= getLevel.id - def trace(t: => Throwable): Unit - final def verbose(message: => String): Unit = debug(message) - final def debug(message: => String): Unit = log(Level.Debug, message) - final def info(message: => String): Unit = log(Level.Info, message) - final def warn(message: => String): Unit = log(Level.Warn, message) - final def error(message: => String): Unit = log(Level.Error, message) def success(message: => String): Unit - def log(level: Level.Value, message: => String): Unit def control(event: ControlEvent.Value, message: => String): Unit def logAll(events: Seq[LogEvent]): Unit @@ -39,11 +31,27 @@ abstract class AbstractLogger extends xLogger with NotNull case c: ControlEvent => control(c.event, c.msg) } } +} +/** This is intended to be the simplest logging interface for use by code that wants to log. +* It does not include configuring the logger. */ +trait Logger extends xLogger +{ + final def verbose(message: => String): Unit = debug(message) + final def debug(message: => String): Unit = log(Level.Debug, message) + final def info(message: => String): Unit = log(Level.Info, message) + final def warn(message: => String): Unit = log(Level.Warn, message) + final def error(message: => String): Unit = log(Level.Error, message) + + def ansiCodesSupported = false + + def trace(t: => Throwable): Unit + def log(level: Level.Value, message: => String): Unit + def debug(msg: F0[String]): Unit = log(Level.Debug, msg) def warn(msg: F0[String]): Unit = log(Level.Warn, msg) def info(msg: F0[String]): Unit = log(Level.Info, msg) def error(msg: F0[String]): Unit = log(Level.Error, msg) def trace(msg: F0[Throwable]) = trace(msg.apply) def log(level: Level.Value, msg: F0[String]): Unit = log(level, msg.apply) -} +} \ No newline at end of file diff --git a/util/log/LoggerWriter.scala b/util/log/LoggerWriter.scala index 885646973..81c0d89d0 100644 --- a/util/log/LoggerWriter.scala +++ b/util/log/LoggerWriter.scala @@ -5,9 +5,9 @@ package sbt /** Provides a `java.io.Writer` interface to a `Logger`. Content is line-buffered and logged at `level`. * A line is delimited by `nl`, which is by default the platform line separator.*/ -class LoggerWriter(delegate: AbstractLogger, level: Level.Value, nl: String) extends java.io.Writer +class LoggerWriter(delegate: Logger, level: Level.Value, nl: String) extends java.io.Writer { - def this(delegate: AbstractLogger, level: Level.Value) = this(delegate, level, System.getProperty("line.separator")) + def this(delegate: Logger, level: Level.Value) = this(delegate, level, System.getProperty("line.separator")) private[this] val buffer = new StringBuilder diff --git a/sbt/src/main/scala/sbt/Process.scala b/util/process/Process.scala similarity index 97% rename from sbt/src/main/scala/sbt/Process.scala rename to util/process/Process.scala index 51290207b..183469516 100644 --- a/sbt/src/main/scala/sbt/Process.scala +++ b/util/process/Process.scala @@ -14,8 +14,6 @@ object Process implicit def apply(command: String): ProcessBuilder = apply(command, None) implicit def apply(command: Seq[String]): ProcessBuilder = apply (command.toArray, None) def apply(command: String, arguments: Seq[String]): ProcessBuilder = apply(command :: arguments.toList, None) - /** create ProcessBuilder with working dir set to path and extra environment variables */ - def apply(command: String, cwd: Path, extraEnv: (String,String)*): ProcessBuilder = apply(command, cwd.asFile, extraEnv : _*) /** create ProcessBuilder with working dir set to File and extra environment variables */ def apply(command: String, cwd: File, extraEnv: (String,String)*): ProcessBuilder = apply(command, Some(cwd), extraEnv : _*) diff --git a/sbt/src/main/scala/sbt/impl/ProcessImpl.scala b/util/process/ProcessImpl.scala similarity index 98% rename from sbt/src/main/scala/sbt/impl/ProcessImpl.scala rename to util/process/ProcessImpl.scala index f1e8271bf..5f89a4749 100644 --- a/sbt/src/main/scala/sbt/impl/ProcessImpl.scala +++ b/util/process/ProcessImpl.scala @@ -41,7 +41,7 @@ private object Future } } -private object BasicIO +object BasicIO { def apply(buffer: StringBuffer, log: Option[Logger], withIn: Boolean) = new ProcessIO(input(withIn), processFully(buffer), getErr(log)) def apply(log: Logger, withIn: Boolean) = new ProcessIO(input(withIn), processFully(log, Level.Info), processFully(log, Level.Error)) @@ -50,7 +50,7 @@ private object BasicIO def ignoreOut = (i: OutputStream) => () final val BufferSize = 8192 - final val Newline = FileUtilities.Newline + final val Newline = System.getProperty("line.separator") def close(c: java.io.Closeable) = try { c.close() } catch { case _: java.io.IOException => () } def processFully(log: Logger, level: Level.Value): InputStream => Unit = processFully(line => log.log(level, line)) @@ -161,8 +161,8 @@ private abstract class AbstractProcessBuilder extends ProcessBuilder with SinkPa def !<(log: Logger) = runBuffered(log, true) private[this] def runBuffered(log: Logger, connectInput: Boolean) = { - val log2 = new BufferedLogger(log) - log2.bufferAll { run(log2, connectInput).exitValue() } + val log2 = new BufferedLogger(new FullLogger(log)) + log2.buffer { run(log2, connectInput).exitValue() } } def !(io: ProcessIO) = run(io).exitValue() diff --git a/sbt/src/test/scala/sbt/ProcessSpecification.scala b/util/process/src/test/scala/ProcessSpecification.scala similarity index 66% rename from sbt/src/test/scala/sbt/ProcessSpecification.scala rename to util/process/src/test/scala/ProcessSpecification.scala index fe02609df..0d7141635 100644 --- a/sbt/src/test/scala/sbt/ProcessSpecification.scala +++ b/util/process/src/test/scala/ProcessSpecification.scala @@ -12,13 +12,13 @@ object ProcessSpecification extends Properties("Process I/O") implicit val exitCodeArb: Arbitrary[Array[Byte]] = Arbitrary(Gen.choose(0, 10) flatMap { size => Gen.resize(size, Arbitrary.arbArray[Byte].arbitrary) }) - specify("Correct exit code", (exitCode: Byte) => checkExit(exitCode)) - specify("#&& correct", (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ #&& _)(_ && _)) - specify("#|| correct", (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ #|| _)(_ || _)) - specify("### correct", (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ ### _)( (x,latest) => latest)) - specify("Pipe to output file", (data: Array[Byte]) => checkFileOut(data)) - specify("Pipe to input file", (data: Array[Byte]) => checkFileIn(data)) - specify("Pipe to process", (data: Array[Byte]) => checkPipe(data)) + /*property("Correct exit code") = forAll( (exitCode: Byte) => checkExit(exitCode)) + property("#&& correct") = forAll( (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ #&& _)(_ && _)) + property("#|| correct") = forAll( (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ #|| _)(_ || _)) + property("### correct") = forAll( (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ ### _)( (x,latest) => latest))*/ + property("Pipe to output file") = forAll( (data: Array[Byte]) => checkFileOut(data)) + property("Pipe to input file") = forAll( (data: Array[Byte]) => checkFileIn(data)) + property("Pipe to process") = forAll( (data: Array[Byte]) => checkPipe(data)) private def checkBinary(codes: Array[Byte])(reduceProcesses: (ProcessBuilder, ProcessBuilder) => ProcessBuilder)(reduceExit: (Boolean, Boolean) => Boolean) = { @@ -64,14 +64,13 @@ object ProcessSpecification extends Properties("Process I/O") val temporaryFile2 = temp() try { - FileUtilities.write(temporaryFile1, data, log) + IO.write(temporaryFile1, data) val process = f(temporaryFile1, temporaryFile2) ( process ! ) == 0 && { - val result = - for(b1 <- FileUtilities.readBytes(temporaryFile1, log).right; b2 <- FileUtilities.readBytes(temporaryFile2, log).right) yield - b1 deepEquals b2 - result.fold(error, x => x) + val b1 = IO.readBytes(temporaryFile1) + val b2 = IO.readBytes(temporaryFile2) + b1 sameElements b2 } } finally @@ -85,10 +84,10 @@ object ProcessSpecification extends Properties("Process I/O") { val ignore = echo // just for the compile dependency so that this test is rerun when TestedProcess.scala changes, not used otherwise - val thisClasspath = List(getSource[ScalaObject], getSource[sbt.Logger], getSource[sbt.SourceTag]).mkString(File.pathSeparator) + val thisClasspath = List(getSource[ScalaObject], getSource[IO.type], getSource[SourceTag]).mkString(File.pathSeparator) "java -cp " + thisClasspath + " " + command } - private def getSource[T](implicit mf: scala.reflect.Manifest[T]): String = - (FileUtilities.toFile(mf.erasure.getProtectionDomain.getCodeSource.getLocation)).getAbsolutePath + private def getSource[T : Manifest]: String = + IO.classLocationFile[T].getAbsolutePath } private trait SourceTag \ No newline at end of file diff --git a/sbt/src/test/scala/sbt/TestedProcess.scala b/util/process/src/test/scala/TestedProcess.scala similarity index 56% rename from sbt/src/test/scala/sbt/TestedProcess.scala rename to util/process/src/test/scala/TestedProcess.scala index 0e9a6a3c4..c013de531 100644 --- a/sbt/src/test/scala/sbt/TestedProcess.scala +++ b/util/process/src/test/scala/TestedProcess.scala @@ -1,6 +1,6 @@ package sbt -import java.io.File +import java.io.{File, FileNotFoundException, IOException} object exit { @@ -13,18 +13,19 @@ object cat { def main(args: Array[String]) { - val result = + try { if(args.length == 0) - FileUtilities.transfer(System.in, System.out, log) + IO.transfer(System.in, System.out) else catFiles(args.toList) - result match - { - case Some(err) => System.err.println("Error: " + err); System.exit(1) - case None => System.exit(0) + System.exit(0) + } catch { + case e => + e.printStackTrace() + System.err.println("Error: " + e.toString) + System.exit(1) } } - private val log = new ConsoleLogger private def catFiles(filenames: List[String]): Option[String] = { filenames match @@ -32,16 +33,16 @@ object cat case head :: tail => val file = new File(head) if(file.isDirectory) - Some("Is directory: " + file) + throw new IOException("Is directory: " + file) else if(file.exists) { - FileUtilities.readStream(file, log) { stream => - FileUtilities.transfer(stream, System.out, log) + Using.fileInputStream(file) { stream => + IO.transfer(stream, System.out) } catFiles(tail) } else - Some("No such file or directory: " + file) + throw new FileNotFoundException("No such file or directory: " + file) case Nil => None } }