From 1585d805bdc8c4fbc807f7430dd914ee2e6ca542 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 13 Jun 2010 22:59:29 -0400 Subject: [PATCH] more reorganization, mostly IO. Also, move class file analyzer and history code to separate projects --- cache/Cache.scala | 12 ++- cache/CacheIO.scala | 7 +- cache/FileInfo.scala | 16 ++-- cache/HListCache.scala | 17 ++-- cache/NoCache.scala | 5 +- cache/SeparatedCache.scala | 11 ++- cache/src/test/scala/CacheTest.scala | 11 +-- cache/tracking/ChangeReport.scala | 2 +- cache/tracking/DependencyTracking.scala | 13 ++- cache/tracking/Tracked.scala | 14 ++-- cache/tracking/TrackingFormat.scala | 26 +++--- .../src/main/java/xsbti/AnalysisCallback.java | 6 ++ interface/src/test/scala/TestCallback.scala | 2 + project/build.properties | 2 +- project/build/XSbt.scala | 14 ++-- .../main/scala/SelfExtractingProject.scala | 6 +- sbt/src/main/scala/sbt/Analysis.scala | 2 +- sbt/src/main/scala/sbt/DefaultProject.scala | 2 +- sbt/src/main/scala/sbt/FileUtilities.scala | 12 +-- sbt/src/main/scala/sbt/NameFilter.scala | 72 ---------------- sbt/src/main/scala/sbt/Process.scala | 2 +- sbt/src/main/scala/sbt/ProjectPaths.scala | 16 ++-- sbt/src/main/scala/sbt/ScalaProject.scala | 4 +- sbt/src/main/scala/sbt/Webstart.scala | 6 +- sbt/src/main/scala/sbt/impl/ProcessImpl.scala | 4 +- .../project/build/src/FlatProject.scala | 8 +- .../test/scala/sbt/ProcessSpecification.scala | 2 +- .../sbt => util}/classfile/Analyze.scala | 25 +++--- .../sbt => util}/classfile/ClassFile.scala | 5 +- util/classfile/ClassfileLogger.scala | 25 ++++++ .../scala/sbt => util}/classfile/Parser.scala | 12 +-- .../scala/sbt => util}/complete/History.scala | 16 ++-- .../complete/HistoryCommands.scala | 30 ++++--- util/control/ErrorHandling.scala | 4 +- util/datatype/Generator.scala | 5 +- util/io/IO.scala | 81 ++++++++++++++---- .../src/main/scala/sbt => util/io}/Path.scala | 83 +++++++++++-------- util/io/PathMapper.scala | 79 +++++++++--------- util/io/Paths.scala | 51 ------------ util/io/Using.scala | 8 +- .../src/test/scala}/PathSpecification.scala | 56 ++++++------- util/io/src/test/scala/StashSpec.scala | 7 +- .../log/src/test/scala}/LogWriterTest.scala | 16 ++-- 43 files changed, 406 insertions(+), 391 deletions(-) delete mode 100644 sbt/src/main/scala/sbt/NameFilter.scala rename {sbt/src/main/scala/sbt => util}/classfile/Analyze.scala (89%) rename {sbt/src/main/scala/sbt => util}/classfile/ClassFile.scala (96%) create mode 100644 util/classfile/ClassfileLogger.scala rename {sbt/src/main/scala/sbt => util}/classfile/Parser.scala (91%) rename {sbt/src/main/scala/sbt => util}/complete/History.scala (69%) rename {sbt/src/main/scala/sbt => util}/complete/HistoryCommands.scala (71%) rename {sbt/src/main/scala/sbt => util/io}/Path.scala (85%) delete mode 100644 util/io/Paths.scala rename {sbt/src/test/scala/sbt => util/io/src/test/scala}/PathSpecification.scala (81%) rename {sbt/src/test/scala/sbt => util/log/src/test/scala}/LogWriterTest.scala (93%) diff --git a/cache/Cache.scala b/cache/Cache.scala index e7ba310dc..c638e94f0 100644 --- a/cache/Cache.scala +++ b/cache/Cache.scala @@ -1,7 +1,11 @@ -package xsbt +/* sbt -- Simple Build Tool + * Copyright 2009 Mark Harrah + */ +package sbt import sbinary.{CollectionTypes, Format, JavaFormats} import java.io.File +import Types.:+: trait Cache[I,O] { @@ -44,13 +48,13 @@ trait BasicCacheImplicits extends NotNull new SeparatedCache(input, output) implicit def defaultEquiv[T]: Equiv[T] = new Equiv[T] { def equiv(a: T, b: T) = a == b } } -trait HListCacheImplicits extends HLists +trait HListCacheImplicits { - implicit def hConsInputCache[H,T<:HList](implicit headCache: InputCache[H], tailCache: InputCache[T]): InputCache[HCons[H,T]] = + implicit def hConsInputCache[H,T<:HList](implicit headCache: InputCache[H], tailCache: InputCache[T]): InputCache[H :+: T] = new HConsInputCache(headCache, tailCache) implicit lazy val hNilInputCache: InputCache[HNil] = new HNilInputCache - implicit def hConsOutputCache[H,T<:HList](implicit headCache: OutputCache[H], tailCache: OutputCache[T]): OutputCache[HCons[H,T]] = + implicit def hConsOutputCache[H,T<:HList](implicit headCache: OutputCache[H], tailCache: OutputCache[T]): OutputCache[H :+: T] = new HConsOutputCache(headCache, tailCache) implicit lazy val hNilOutputCache: OutputCache[HNil] = new HNilOutputCache } diff --git a/cache/CacheIO.scala b/cache/CacheIO.scala index e5c643c6a..7ff1eb519 100644 --- a/cache/CacheIO.scala +++ b/cache/CacheIO.scala @@ -1,4 +1,7 @@ -package xsbt +/* sbt -- Simple Build Tool + * Copyright 2009 Mark Harrah + */ +package sbt import java.io.{File, FileNotFoundException} import sbinary.{DefaultProtocol, Format, Operations} @@ -24,7 +27,7 @@ object CacheIO toFile(value)(file)(format, mf) def toFile[T](value: T)(file: File)(implicit format: Format[T], mf: Manifest[Format[T]]): Unit = { - FileUtilities.createDirectory(file.getParentFile) + IO.createDirectory(file.getParentFile) Operations.toFile(value)(file)(stampedFormat(format)) } def stampedFormat[T](format: Format[T])(implicit mf: Manifest[Format[T]]): Format[T] = diff --git a/cache/FileInfo.scala b/cache/FileInfo.scala index d1b350fa8..425a8598d 100644 --- a/cache/FileInfo.scala +++ b/cache/FileInfo.scala @@ -1,9 +1,11 @@ -package xsbt +/* sbt -- Simple Build Tool + * Copyright 2009 Mark Harrah + */ +package sbt import java.io.{File, IOException} import sbinary.{DefaultProtocol, Format} import DefaultProtocol._ -import Function.tupled import scala.reflect.Manifest sealed trait FileInfo extends NotNull @@ -43,22 +45,22 @@ object FileInfo type F = HashModifiedFileInfo implicit def apply(file: File): HashModifiedFileInfo = make(file, Hash(file).toList, file.lastModified) def make(file: File, hash: List[Byte], lastModified: Long): HashModifiedFileInfo = FileHashModified(file.getAbsoluteFile, hash, lastModified) - implicit val format: Format[HashModifiedFileInfo] = wrap(f => (f.file, f.hash, f.lastModified), tupled(make _)) + implicit val format: Format[HashModifiedFileInfo] = wrap(f => (f.file, f.hash, f.lastModified), (make _).tupled) } object hash extends Style { type F = HashFileInfo - implicit def apply(file: File): HashFileInfo = make(file, computeHash(file).toList) + implicit def apply(file: File): HashFileInfo = make(file, computeHash(file)) def make(file: File, hash: List[Byte]): HashFileInfo = FileHash(file.getAbsoluteFile, hash) - implicit val format: Format[HashFileInfo] = wrap(f => (f.file, f.hash), tupled(make _)) - private def computeHash(file: File) = try { Hash(file) } catch { case e: Exception => Nil } + implicit val format: Format[HashFileInfo] = wrap(f => (f.file, f.hash), (make _).tupled) + private def computeHash(file: File): List[Byte] = try { Hash(file).toList } catch { case e: Exception => Nil } } object lastModified extends Style { type F = ModifiedFileInfo implicit def apply(file: File): ModifiedFileInfo = make(file, file.lastModified) def make(file: File, lastModified: Long): ModifiedFileInfo = FileModified(file.getAbsoluteFile, lastModified) - implicit val format: Format[ModifiedFileInfo] = wrap(f => (f.file, f.lastModified), tupled(make _)) + implicit val format: Format[ModifiedFileInfo] = wrap(f => (f.file, f.lastModified), (make _).tupled) } object exists extends Style { diff --git a/cache/HListCache.scala b/cache/HListCache.scala index 90f00f47d..2bb3def3b 100644 --- a/cache/HListCache.scala +++ b/cache/HListCache.scala @@ -1,12 +1,15 @@ -package xsbt +/* sbt -- Simple Build Tool + * Copyright 2009 Mark Harrah + */ +package sbt import java.io.{InputStream,OutputStream} -import HLists._ +import Types._ class HNilInputCache extends NoInputCache[HNil] -class HConsInputCache[H,T <: HList](val headCache: InputCache[H], val tailCache: InputCache[T]) extends InputCache[HCons[H,T]] +class HConsInputCache[H,T <: HList](val headCache: InputCache[H], val tailCache: InputCache[T]) extends InputCache[H :+: T] { - def uptodate(in: HCons[H,T])(cacheStream: InputStream) = + def uptodate(in: H :+: T)(cacheStream: InputStream) = { val headResult = headCache.uptodate(in.head)(cacheStream) val tailResult = tailCache.uptodate(in.tail)(cacheStream) @@ -20,7 +23,7 @@ class HConsInputCache[H,T <: HList](val headCache: InputCache[H], val tailCache: } } } - def force(in: HCons[H,T])(cacheStream: OutputStream) = + def force(in: H :+: T)(cacheStream: OutputStream) = { headCache.force(in.head)(cacheStream) tailCache.force(in.tail)(cacheStream) @@ -28,7 +31,7 @@ class HConsInputCache[H,T <: HList](val headCache: InputCache[H], val tailCache: } class HNilOutputCache extends NoOutputCache[HNil](HNil) -class HConsOutputCache[H,T <: HList](val headCache: OutputCache[H], val tailCache: OutputCache[T]) extends OutputCache[HCons[H,T]] +class HConsOutputCache[H,T <: HList](val headCache: OutputCache[H], val tailCache: OutputCache[T]) extends OutputCache[H :+: T] { def loadCached(cacheStream: InputStream) = { @@ -36,7 +39,7 @@ class HConsOutputCache[H,T <: HList](val headCache: OutputCache[H], val tailCach val tail = tailCache.loadCached(cacheStream) HCons(head, tail) } - def update(out: HCons[H,T])(cacheStream: OutputStream) + def update(out: H :+: T)(cacheStream: OutputStream) { headCache.update(out.head)(cacheStream) tailCache.update(out.tail)(cacheStream) diff --git a/cache/NoCache.scala b/cache/NoCache.scala index a9cce3e99..bdb9c4f1b 100644 --- a/cache/NoCache.scala +++ b/cache/NoCache.scala @@ -1,4 +1,7 @@ -package xsbt +/* sbt -- Simple Build Tool + * Copyright 2009 Mark Harrah + */ +package sbt import java.io.{InputStream,OutputStream} diff --git a/cache/SeparatedCache.scala b/cache/SeparatedCache.scala index 91ecda713..6509e05bf 100644 --- a/cache/SeparatedCache.scala +++ b/cache/SeparatedCache.scala @@ -1,4 +1,7 @@ -package xsbt +/* sbt -- Simple Build Tool + * Copyright 2009 Mark Harrah + */ +package sbt import sbinary.Format import sbinary.JavaIO._ @@ -31,11 +34,11 @@ class SeparatedCache[I,O](input: InputCache[I], output: OutputCache[O]) extends catch { case _: Exception => Right(update(file)(in)) } protected def applyImpl(file: File, in: I) = { - OpenResource.fileInputStream(file) { stream => + Using.fileInputStream(file) { stream => val cache = input.uptodate(in)(stream) lazy val doUpdate = (result: O) => { - OpenResource.fileOutputStream(false)(file) { stream => + Using.fileOutputStream(false)(file) { stream => cache.update(stream) output.update(result)(stream) } @@ -49,7 +52,7 @@ class SeparatedCache[I,O](input: InputCache[I], output: OutputCache[O]) extends } protected def update(file: File)(in: I)(out: O) { - OpenResource.fileOutputStream(false)(file) { stream => + Using.fileOutputStream(false)(file) { stream => input.force(in)(stream) output.update(out)(stream) } diff --git a/cache/src/test/scala/CacheTest.scala b/cache/src/test/scala/CacheTest.scala index 65703ecaa..ad6085fc1 100644 --- a/cache/src/test/scala/CacheTest.scala +++ b/cache/src/test/scala/CacheTest.scala @@ -1,6 +1,7 @@ -package xsbt +package sbt import java.io.File +import Types.:+: object CacheTest// extends Properties("Cache test") { @@ -19,11 +20,11 @@ object CacheTest// extends Properties("Cache test") lazy val fileLength = length(create) - val c = cached(cCache) { (in: (File :: Long :: HNil)) => - val file :: len :: HNil = in + val c = cached(cCache) { (in: (File :+: Long :+: HNil)) => + val file :+: len :+: HNil = in println("File: " + file + " (" + file.exists + "), length: " + len) - (len+1) :: file :: HNil + (len+1) :+: file :+: HNil } - c(create :: fileLength :: HNil) + c(create :+: fileLength :+: HNil) } } \ No newline at end of file diff --git a/cache/tracking/ChangeReport.scala b/cache/tracking/ChangeReport.scala index c8f3a52eb..d25b1bbaa 100644 --- a/cache/tracking/ChangeReport.scala +++ b/cache/tracking/ChangeReport.scala @@ -1,7 +1,7 @@ /* sbt -- Simple Build Tool * Copyright 2009, 2010 Mark Harrah */ -package xsbt +package sbt object ChangeReport { diff --git a/cache/tracking/DependencyTracking.scala b/cache/tracking/DependencyTracking.scala index e34930f5a..30e060af3 100644 --- a/cache/tracking/DependencyTracking.scala +++ b/cache/tracking/DependencyTracking.scala @@ -1,7 +1,7 @@ /* sbt -- Simple Build Tool * Copyright 2009, 2010 Mark Harrah */ -package xsbt +package sbt private object DependencyTracking { @@ -24,7 +24,6 @@ trait UpdateTracking[T] extends NotNull // removes sources as keys/values in source, product maps and as values in reverseDependencies map def pending(sources: Iterable[T]): Unit } -import scala.collection.Set trait ReadTracking[T] extends NotNull { def isProduct(file: T): Boolean @@ -75,13 +74,13 @@ private abstract class DependencyTracking[T](translateProducts: Boolean) extends def isUsed(file: T): Boolean = exists(reverseUses, file) - final def allProducts = Set() ++ sourceMap.keys - final def allSources = Set() ++ productMap.keys - final def allUsed = Set() ++ reverseUses.keys + final def allProducts = sourceMap.keysIterator.toSet + final def allSources = productMap.keysIterator.toSet + final def allUsed = reverseUses.keysIterator.toSet final def allTags = tagMap.toSeq private def exists(map: DMap[T], value: T): Boolean = map.contains(value) - private def get(map: DMap[T], value: T): Set[T] = map.getOrElse(value, Set.empty[T]) + private def get(map: DMap[T], value: T): Set[T] = map.getOrElse[collection.Set[T]](value, Set.empty[T]).toSet final def dependency(sourceFile: T, dependsOn: T) { @@ -89,7 +88,7 @@ private abstract class DependencyTracking[T](translateProducts: Boolean) extends if(!translateProducts) Seq(dependsOn) else - sourceMap.getOrElse(dependsOn, Seq(dependsOn)) + sourceMap.getOrElse[Iterable[T]](dependsOn, Seq(dependsOn)) actualDependencies.foreach { actualDependency => reverseDependencies.add(actualDependency, sourceFile) } } final def product(sourceFile: T, product: T) diff --git a/cache/tracking/Tracked.scala b/cache/tracking/Tracked.scala index 49d33c622..77c7447f5 100644 --- a/cache/tracking/Tracked.scala +++ b/cache/tracking/Tracked.scala @@ -1,13 +1,13 @@ /* sbt -- Simple Build Tool * Copyright 2009, 2010 Mark Harrah */ -package xsbt +package sbt import java.io.{File,IOException} import CacheIO.{fromFile, toFile} import sbinary.Format import scala.reflect.Manifest -import xsbt.FileUtilities.{delete, read, write} +import IO.{delete, read, write} /* A proper implementation of fileTask that tracks inputs and outputs properly @@ -34,7 +34,7 @@ object Tracked * If 'useStartTime' is true, the recorded time is the start of the evaluated function. * If 'useStartTime' is false, the recorded time is when the evaluated function completes. * In both cases, the timestamp is not updated if the function throws an exception.*/ - def tstamp(cacheFile: File, useStartTime: Boolean): Timestamp = new Timestamp(cacheFile) + def tstamp(cacheFile: File, useStartTime: Boolean = true): Timestamp = new Timestamp(cacheFile, useStartTime) /** Creates a tracker that only evaluates a function when the input has changed.*/ def changed[O](cacheFile: File)(getValue: => O)(implicit input: InputCache[O]): Changed[O] = new Changed[O](getValue, cacheFile) @@ -76,13 +76,13 @@ class Changed[O](getValue: => O, val cacheFile: File)(implicit input: InputCache { val value = getValue val cache = - try { OpenResource.fileInputStream(cacheFile)(input.uptodate(value)) } + try { Using.fileInputStream(cacheFile)(input.uptodate(value)) } catch { case _: IOException => new ForceResult(input)(value) } if(cache.uptodate) ifUnchanged(value) else { - OpenResource.fileOutputStream(false)(cacheFile)(cache.update) + Using.fileOutputStream(false)(cacheFile)(cache.update) ifChanged(value) } } @@ -108,7 +108,7 @@ class Difference(getFiles: => Set[File], val style: FilesInfo.Style, val cache: if(defineClean) delete(raw(cachedFilesInfo)) else () clearCache() } - private def clearCache = delete(cache) + private def clearCache() = delete(cache) private def cachedFilesInfo = fromFile(style.formats, style.empty)(cache)(style.manifest).files private def raw(fs: Set[style.F]): Set[File] = fs.map(_.file) @@ -161,7 +161,7 @@ object InvalidateFiles def apply(cacheDirectory: File, translateProducts: Boolean): InvalidateTransitive[File] = { import sbinary.DefaultProtocol.FileFormat - new InvalidateTransitive[File](cacheDirectory, translateProducts, FileUtilities.delete) + new InvalidateTransitive[File](cacheDirectory, translateProducts, IO.delete) } } diff --git a/cache/tracking/TrackingFormat.scala b/cache/tracking/TrackingFormat.scala index 1318c6096..d8a5e0f2c 100644 --- a/cache/tracking/TrackingFormat.scala +++ b/cache/tracking/TrackingFormat.scala @@ -1,7 +1,7 @@ /* sbt -- Simple Build Tool * Copyright 2009, 2010 Mark Harrah */ -package xsbt +package sbt import java.io.File import scala.collection.mutable.{HashMap, Map, MultiMap, Set} @@ -34,16 +34,22 @@ private class TrackingFormat[T](directory: File, translateProducts: Boolean)(imp } private object TrackingFormat { - implicit def mutableMapFormat[S, T](implicit binS : Format[S], binT : Format[T]) : Format[Map[S, T]] = - viaArray( (x : Array[(S, T)]) => Map(x :_*)); - implicit def depMapFormat[T](implicit bin: Format[T]) : Format[DMap[T]] = - { - viaArray { (x : Array[(T, Set[T])]) => - val map = newMap[T] - map ++= x - map + implicit def mutableMapFormat[S, T](implicit binS : Format[S], binT : Format[T]) : Format[HashMap[S, T]] = + new LengthEncoded[HashMap[S, T], (S, T)] { + def build(size : Int, ts : Iterator[(S, T)]) : HashMap[S, T] = { + val b = new HashMap[S, T] + b ++= ts + b + } + } + implicit def depMapFormat[T](implicit bin: Format[T]) : Format[DMap[T]] = + new LengthEncoded[DMap[T], (T, Set[T])] { + def build(size : Int, ts : Iterator[(T, Set[T])]) : DMap[T] = { + val b = newMap[T] + b ++= ts + b + } } - } def trackingFormat[T](translateProducts: Boolean)(implicit tFormat: Format[T]): Format[DependencyTracking[T]] = asProduct4((a: DMap[T],b: DMap[T],c: DMap[T], d:TagMap[T]) => new DefaultTracking(translateProducts)(a,b,c,d) : DependencyTracking[T] )(dt => (dt.reverseDependencies, dt.reverseUses, dt.sourceMap, dt.tagMap)) diff --git a/interface/src/main/java/xsbti/AnalysisCallback.java b/interface/src/main/java/xsbti/AnalysisCallback.java index 4db5f28c3..03c4798c9 100644 --- a/interface/src/main/java/xsbti/AnalysisCallback.java +++ b/interface/src/main/java/xsbti/AnalysisCallback.java @@ -31,6 +31,12 @@ public interface AnalysisCallback /** Called to indicate that the source file source depends on the class file * clazz.*/ public void classDependency(File clazz, File source); + /** Called to indicate that the source file sourcePath depends on the class file + * classFile that is a product of some source. This differs from classDependency + * because it is really a sourceDependency. The source corresponding to classFile + * was not incuded in the compilation so the plugin doesn't know what the source is though. It + * only knows that the class file came from the output directory.*/ + public void productDependency(File classFile, File sourcePath); /** Called to indicate that the source file source produces a class file at * module.*/ public void generatedClass(File source, File module); diff --git a/interface/src/test/scala/TestCallback.scala b/interface/src/test/scala/TestCallback.scala index 8e7014af7..75e8d77af 100644 --- a/interface/src/test/scala/TestCallback.scala +++ b/interface/src/test/scala/TestCallback.scala @@ -13,6 +13,7 @@ class TestCallback(val superclassNames: Array[String], val annotationNames: Arra val sourceDependencies = new ArrayBuffer[(File, File)] val jarDependencies = new ArrayBuffer[(File, File)] val classDependencies = new ArrayBuffer[(File, File)] + val productDependencies = new ArrayBuffer[(File, File)] val products = new ArrayBuffer[(File, File)] val applications = new ArrayBuffer[(File, String)] @@ -25,6 +26,7 @@ class TestCallback(val superclassNames: Array[String], val annotationNames: Arra 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 productDependency(clazz: File, source: File) { productDependencies += ((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)) } diff --git a/project/build.properties b/project/build.properties index a055fdc51..06ee8d50c 100644 --- a/project/build.properties +++ b/project/build.properties @@ -2,4 +2,4 @@ project.organization=org.scala-tools.sbt project.name=xsbt sbt.version=0.7.4 project.version=0.9.0-SNAPSHOT -build.scala.versions=2.8.0.RC3 +build.scala.versions=2.8.0.RC4 diff --git a/project/build/XSbt.scala b/project/build/XSbt.scala index 98221cdc9..bca223f91 100644 --- a/project/build/XSbt.scala +++ b/project/build/XSbt.scala @@ -16,6 +16,8 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths val collectionSub = baseProject(utilPath / "collection", "Collections") val ioSub = project(utilPath / "io", "IO", new IOProject(_), controlSub) val classpathSub = baseProject(utilPath / "classpath", "Classpath") + val classfileSub = project(utilPath / "classfile", "Classfile", new ClassfileProject(_), ioSub, interfaceSub) + val completeSub = project(utilPath / "complete", "Completion", new CompletionProject(_), ioSub) val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub, launchInterfaceSub) val logSub = project(utilPath / "log", "Logging", new LogProject(_), interfaceSub) @@ -84,26 +86,28 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths } trait TestDependencies extends Project { - val sc = "org.scala-tools.testing" %% "scalacheck" % "1.7" % "test" - val sp = "org.scala-tools.testing" %% "specs" % "1.6.5-SNAPSHOT" % "test" + val sc = "org.scala-tools.testing" % "scalacheck_2.8.0.RC3" % "1.7" % "test" + val sp = "org.scala-tools.testing" % "specs_2.8.0.RC3" % "1.6.5-SNAPSHOT" % "test" val snaps = ScalaToolsSnapshots } class StandardTaskProject(info: ProjectInfo) extends Base(info) { override def testClasspath = super.testClasspath +++ compilerSub.testClasspath --- compilerInterfaceClasspath } - class LogProject(info: ProjectInfo) extends Base(info) + class LogProject(info: ProjectInfo) extends Base(info) with TestDependencies { val jline = jlineDep } class IOProject(info: ProjectInfo) extends Base(info) with TestDependencies class TaskProject(info: ProjectInfo) extends Base(info) with TestDependencies + class ClassfileProject(info: ProjectInfo) extends Base(info) with TestDependencies + class CompletionProject(info: ProjectInfo) extends Base(info) with TestDependencies class CacheProject(info: ProjectInfo) extends Base(info) { // these compilation options are useful for debugging caches and task composition //override def compileOptions = super.compileOptions ++ List(Unchecked,ExplainTypes, CompileOption("-Xlog-implicits")) - val sbinary = "org.scala-tools.sbinary" %% "sbinary" % "0.3" + val sbinary = "org.scala-tools.sbinary" %% "sbinary" % "0.3.1-SNAPSHOT" } class Base(info: ProjectInfo) extends DefaultProject(info) with ManagedBase with Component with Licensed { @@ -201,7 +205,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths // sub projects for each version of Scala to precompile against other than the one sbt is built against // each sub project here will add ~100k to the download - lazy val precompiled28 = precompiledSub("2.8.0.RC2") + lazy val precompiled28 = precompiledSub("2.8.0.RC4") def precompiledSub(v: String) = project(info.projectPath, "Precompiled " + v, new Precompiled(v)(_), cip.info.dependencies.toSeq : _* /*doesn't include subprojects of cip*/ ) diff --git a/sbt/install/plugin/src/main/scala/SelfExtractingProject.scala b/sbt/install/plugin/src/main/scala/SelfExtractingProject.scala index 8dc079e76..3c31694a3 100644 --- a/sbt/install/plugin/src/main/scala/SelfExtractingProject.scala +++ b/sbt/install/plugin/src/main/scala/SelfExtractingProject.scala @@ -28,7 +28,7 @@ trait SelfExtractingProject extends Project write(new File(tmp, "install"), installContents, log) orElse unzip(this.getClass.getResource(extractorJarLocation), tmpPath, log).left.toOption orElse Control.thread(compressLoader(loaderJar)) { compressedLoader => - zip( (tmpPath ##) :: flat(projectZip) :: compressedLoader :: Nil, outputJar, true, log) + zip( (tmpPath ###) :: flat(projectZip) :: compressedLoader :: Nil, outputJar, true, log) } } } @@ -49,7 +49,7 @@ trait SelfExtractingProject extends Project else jarName val packedName = baseName + ".pack" val packed = outputPath / packedName - val packedAndGzip = (outputPath ##) / (packedName + ".gz") + val packedAndGzip = (outputPath ###) / (packedName + ".gz") val result = Pack.pack(loaderJar, packed, log) orElse FileUtilities.gzip(packed, packedAndGzip, log) @@ -73,7 +73,7 @@ object SelfExtractingProject private def flat(p: Path) = p match { - case rp: RelativePath => (rp.parentPath ##) / rp.component + case rp: RelativePath => (rp.parentPath ###) / rp.component case _ => p } } \ No newline at end of file diff --git a/sbt/src/main/scala/sbt/Analysis.scala b/sbt/src/main/scala/sbt/Analysis.scala index da117c33f..139bd85b1 100644 --- a/sbt/src/main/scala/sbt/Analysis.scala +++ b/sbt/src/main/scala/sbt/Analysis.scala @@ -179,7 +179,7 @@ class BasicCompileAnalysis protected (analysisPath: Path, projectPath: Path, log def getClasses(sources: PathFinder, outputDirectory: Path): PathFinder = Path.lazyPathFinder { - val basePath = (outputDirectory ##) + val basePath = (outputDirectory ###) for(c <- products(sources.get)) yield Path.relativize(basePath, c).getOrElse(c) } diff --git a/sbt/src/main/scala/sbt/DefaultProject.scala b/sbt/src/main/scala/sbt/DefaultProject.scala index 6ca5c65a7..62350fa8d 100644 --- a/sbt/src/main/scala/sbt/DefaultProject.scala +++ b/sbt/src/main/scala/sbt/DefaultProject.scala @@ -295,7 +295,7 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec protected def packageAction = packageTask(packagePaths, jarPath, packageOptions).dependsOn(compile) describedAs PackageDescription protected def packageTestAction = packageTask(packageTestPaths, packageTestJar).dependsOn(testCompile) describedAs TestPackageDescription - protected def packageDocsAction = packageTask(mainDocPath ##, packageDocsJar, Recursive).dependsOn(doc) describedAs DocPackageDescription + protected def packageDocsAction = packageTask(mainDocPath ###, packageDocsJar, Recursive).dependsOn(doc) describedAs DocPackageDescription protected def packageSrcAction = packageTask(packageSourcePaths, packageSrcJar) describedAs SourcePackageDescription protected def packageTestSrcAction = packageTask(packageTestSourcePaths, packageTestSrcJar) describedAs TestSourcePackageDescription protected def packageProjectAction = zipTask(packageProjectPaths, packageProjectZip) describedAs ProjectPackageDescription diff --git a/sbt/src/main/scala/sbt/FileUtilities.scala b/sbt/src/main/scala/sbt/FileUtilities.scala index 504d6ce02..0d0477906 100644 --- a/sbt/src/main/scala/sbt/FileUtilities.scala +++ b/sbt/src/main/scala/sbt/FileUtilities.scala @@ -88,7 +88,7 @@ object FileUtilities /** Creates a jar file. * @param sources The files to include in the jar file. The path used for the jar is * relative to the base directory for the source. That is, the path in the jar for source - * (basePath ##) / x / y is x / y. + * (basePath ###) / x / y is x / y. * @param outputJar The file to write the jar to. * @param manifest The manifest for the jar. * @param recursive If true, any directories in sources are recursively processed. Otherwise, @@ -99,7 +99,7 @@ object FileUtilities /** Creates a zip file. * @param sources The files to include in the jar file. The path used for the jar is * relative to the base directory for the source. That is, the path in the jar for source - * (basePath ##) / x / y is x / y. + * (basePath ###) / x / y is x / y. * @param outputZip The file to write the zip to. * @param recursive If true, any directories in sources are recursively processed. Otherwise, * they are not @@ -465,7 +465,7 @@ object FileUtilities * directory. Directories are not recursively entered. The destination hierarchy matches the * source paths relative to any base directories. For example: * - * A source (basePath ##) / x / y is copied to destinationDirectory / x / y. + * A source (basePath ###) / x / y is copied to destinationDirectory / x / y. * * @param overwrite if true, existing destination files are always overwritten * @param preserveLastModified if true, the last modified time of copied files will be set equal to @@ -575,7 +575,7 @@ object FileUtilities /** Synchronizes the contents of the sourceDirectory directory to the * targetDirectory directory.*/ def sync(sourceDirectory: Path, targetDirectory: Path, log: Logger): Option[String] = - syncPaths((sourceDirectory ##) ** AllPassFilter, targetDirectory, log) + syncPaths((sourceDirectory ###) ** AllPassFilter, targetDirectory, log) def syncPaths(sources: PathFinder, targetDirectory: Path, log: Logger): Option[String] = { copy(sources.get, targetDirectory, log).right.flatMap @@ -583,7 +583,7 @@ object FileUtilities } def prune(directory: Path, keepOnly: Iterable[Path], log: Logger): Option[String] = { - val existing = ((directory ##) ** AllPassFilter).get + val existing = ((directory ###) ** AllPassFilter).get val toRemove = scala.collection.mutable.HashSet(existing.toSeq: _*) toRemove --= keepOnly if(log.atLevel(Level.Debug)) @@ -808,7 +808,7 @@ object FileUtilities def readChannelValue[R](file: File, log: Logger)(f: FileChannel => Either[String, R]): Either[String, R] = fileInputChannel.io(file, Reading, log)(f) - private[sbt] def wrapNull(a: Array[File]): Array[File] = + private[sbt] (a: Array[File]): Array[File] = if(a == null) new Array[File](0) else diff --git a/sbt/src/main/scala/sbt/NameFilter.scala b/sbt/src/main/scala/sbt/NameFilter.scala deleted file mode 100644 index 3387806b6..000000000 --- a/sbt/src/main/scala/sbt/NameFilter.scala +++ /dev/null @@ -1,72 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2008, 2009 Mark Harrah - */ -package sbt - -import java.io.File -import java.util.regex.Pattern - -trait FileFilter extends java.io.FileFilter with NotNull -{ - def || (filter: FileFilter): FileFilter = new SimpleFileFilter( file => accept(file) || filter.accept(file) ) - def && (filter: FileFilter): FileFilter = new SimpleFileFilter( file => accept(file) && filter.accept(file) ) - def -- (filter: FileFilter): FileFilter = new SimpleFileFilter( file => accept(file) && !filter.accept(file) ) - def unary_- : FileFilter = new SimpleFileFilter( file => !accept(file) ) -} -trait NameFilter extends FileFilter with NotNull -{ - def accept(name: String): Boolean - final def accept(file: File): Boolean = accept(file.getName) - def | (filter: NameFilter): NameFilter = new SimpleFilter( name => accept(name) || filter.accept(name) ) - def & (filter: NameFilter): NameFilter = new SimpleFilter( name => accept(name) && filter.accept(name) ) - def - (filter: NameFilter): NameFilter = new SimpleFilter( name => accept(name) && !filter.accept(name) ) - override def unary_- : NameFilter = new SimpleFilter( name => !accept(name) ) -} -object HiddenFileFilter extends FileFilter { - def accept(file: File) = file.isHidden && file.getName != "." -} -object ExistsFileFilter extends FileFilter { - def accept(file: File) = file.exists -} -object DirectoryFilter extends FileFilter { - def accept(file: File) = file.isDirectory -} -class SimpleFileFilter(val acceptFunction: File => Boolean) extends FileFilter -{ - def accept(file: File) = acceptFunction(file) -} -class ExactFilter(val matchName: String) extends NameFilter -{ - def accept(name: String) = matchName == name -} -class SimpleFilter(val acceptFunction: String => Boolean) extends NameFilter -{ - def accept(name: String) = acceptFunction(name) -} -class PatternFilter(val pattern: Pattern) extends NameFilter -{ - def accept(name: String) = pattern.matcher(name).matches -} -object AllPassFilter extends NameFilter -{ - def accept(name: String) = true -} -object NothingFilter extends NameFilter -{ - def accept(name: String) = false -} - -object GlobFilter -{ - def apply(expression: String): NameFilter = - { - require(!expression.exists(java.lang.Character.isISOControl), "Control characters not allowed in filter expression.") - if(expression == "*") - AllPassFilter - else if(expression.indexOf('*') < 0) // includes case where expression is empty - new ExactFilter(expression) - else - new PatternFilter(Pattern.compile(expression.split("\\*", -1).map(quote).mkString(".*"))) - } - private def quote(s: String) = if(s.isEmpty) "" else Pattern.quote(s.replaceAll("\n", """\n""")) -} \ No newline at end of file diff --git a/sbt/src/main/scala/sbt/Process.scala b/sbt/src/main/scala/sbt/Process.scala index a26635230..51290207b 100644 --- a/sbt/src/main/scala/sbt/Process.scala +++ b/sbt/src/main/scala/sbt/Process.scala @@ -156,7 +156,7 @@ trait ProcessBuilder extends SourcePartialBuilder with SinkPartialBuilder /** Constructs a command that will run this command and pipes the output to `other`. `other` must be a simple command.*/ def #| (other: ProcessBuilder): ProcessBuilder /** Constructs a command that will run this command and then `other`. The exit code will be the exit code of `other`.*/ - def ## (other: ProcessBuilder): ProcessBuilder + def ### (other: ProcessBuilder): ProcessBuilder def canPipeTo: Boolean } diff --git a/sbt/src/main/scala/sbt/ProjectPaths.scala b/sbt/src/main/scala/sbt/ProjectPaths.scala index f08129761..cfd285adf 100644 --- a/sbt/src/main/scala/sbt/ProjectPaths.scala +++ b/sbt/src/main/scala/sbt/ProjectPaths.scala @@ -83,17 +83,17 @@ trait BasicScalaPaths extends Project with ScalaPaths } def testSources = sources(testSourceRoots) - def mainResources = descendents(mainResourcesPath ##, "*") - def testResources = descendents(testResourcesPath ##, "*") + def mainResources = descendents(mainResourcesPath ###, "*") + def testResources = descendents(testResourcesPath ###, "*") - def mainClasses = (mainCompilePath ##) ** "*.class" - def testClasses = (testCompilePath ##) ** "*.class" + def mainClasses = (mainCompilePath ###) ** "*.class" + def testClasses = (testCompilePath ###) ** "*.class" def packagePaths = mainClasses +++ mainResources def packageTestPaths = testClasses +++ testResources def packageSourcePaths = mainSources +++ mainResources def packageTestSourcePaths = testSources +++ testResources - def packageProjectPaths = descendents( (info.projectPath ##), "*") --- (packageProjectExcludes ** "*") + def packageProjectPaths = descendents( (info.projectPath ###), "*") --- (packageProjectExcludes ** "*") protected def packageProjectExcludes: PathFinder = outputRootPath +++ managedDependencyRootPath +++ info.bootPath +++ info.builderProjectOutputPath +++ @@ -159,8 +159,8 @@ trait MavenStyleScalaPaths extends BasicScalaPaths with BasicPackagePaths testResourcesPath :: Nil - def mainSourceRoots = (mainJavaSourcePath##) +++ (mainScalaSourcePath##) - def testSourceRoots = (testJavaSourcePath##) +++ (testScalaSourcePath##) + def mainSourceRoots = (mainJavaSourcePath###) +++ (mainScalaSourcePath##) + def testSourceRoots = (testJavaSourcePath###) +++ (testScalaSourcePath##) } trait BasicPackagePaths extends ScalaPaths with PackagePaths @@ -222,7 +222,7 @@ trait MavenStyleWebScalaPaths extends WebScalaPaths with MavenStyleScalaPaths def warPath = outputPath / defaultWarName /** Additional files to include in the web application. */ protected def extraWebappFiles: PathFinder = Path.emptyPathFinder - def webappResources = descendents(webappPath ##, "*") +++ extraWebappFiles + def webappResources = descendents(webappPath ###, "*") +++ extraWebappFiles } object WebProjectPaths { diff --git a/sbt/src/main/scala/sbt/ScalaProject.scala b/sbt/src/main/scala/sbt/ScalaProject.scala index 9bdf9fb6c..e2eacad59 100644 --- a/sbt/src/main/scala/sbt/ScalaProject.scala +++ b/sbt/src/main/scala/sbt/ScalaProject.scala @@ -330,7 +330,7 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje trait WebScalaProject extends ScalaProject { protected def packageWarAction(stagedWarPath: Path, ignore: PathFinder, outputWarPath: => Path, options: => Seq[PackageOption]): Task = - packageTask(descendents(stagedWarPath ##, "*") --- ignore, outputWarPath, options) + packageTask(descendents(stagedWarPath ###, "*") --- ignore, outputWarPath, options) @deprecated protected def prepareWebappTask(webappContents: PathFinder, warPath: => Path, classpath: PathFinder, extraJars: => Iterable[File]): Task = prepareWebappTask(webappContents, warPath, classpath, Path.finder(extraJars)) @@ -344,7 +344,7 @@ trait WebScalaProject extends ScalaProject val classesTargetDirectory = webInfPath / "classes" val (libs, directories) = classpath.get.toList.partition(ClasspathUtilities.isArchive) - val classesAndResources = descendents(Path.lazyPathFinder(directories) ##, "*") + val classesAndResources = descendents(Path.lazyPathFinder(directories) ###, "*") if(log.atLevel(Level.Debug)) directories.foreach(d => log.debug(" Copying the contents of directory " + d + " to " + classesTargetDirectory)) diff --git a/sbt/src/main/scala/sbt/Webstart.scala b/sbt/src/main/scala/sbt/Webstart.scala index bd3d382a1..24c54ff59 100644 --- a/sbt/src/main/scala/sbt/Webstart.scala +++ b/sbt/src/main/scala/sbt/Webstart.scala @@ -61,7 +61,7 @@ trait WebstartScalaProject extends ScalaProject FileUtilities.createDirectories(webstartOutputDirectory :: webstartLibDirectory :: Nil, log) // ignore errors verifyOptions(options) - def relativize(jar: Path) = Path.relativize(webstartOutputDirectory ##, jar) getOrElse + def relativize(jar: Path) = Path.relativize(webstartOutputDirectory ###, jar) getOrElse error("Jar (" + jar + ") was not in webstart output directory (" + webstartOutputDirectory + ").") def signAndPack(jars: List[Path], targetDirectory: Path): Either[String, List[Path]] = { @@ -104,7 +104,7 @@ trait WebstartScalaProject extends ScalaProject val keep = jnlpFile +++ Path.lazyPathFinder(mainJars ++ libJars ++ copiedResources) +++ webstartOutputDirectory +++ webstartLibDirectory prune(webstartOutputDirectory, keep.get, log) orElse - webstartZip.flatMap( zipPath => zip(List(webstartOutputDirectory ##), zipPath, true, log) ) + webstartZip.flatMap( zipPath => zip(List(webstartOutputDirectory ###), zipPath, true, log) ) } } } @@ -276,7 +276,7 @@ abstract class BasicWebstartProject extends BasicScalaProject with WebstartScala def webstartExtraLibraries = mainDependencies.scalaJars def webstartLibraries = publicClasspath +++ jarsOfProjectDependencies - def webstartResources = descendents(jnlpResourcesPath ##, AllPassFilter) + def webstartResources = descendents(jnlpResourcesPath ###, AllPassFilter) def webstartPack200 = true def webstartGzip = true diff --git a/sbt/src/main/scala/sbt/impl/ProcessImpl.scala b/sbt/src/main/scala/sbt/impl/ProcessImpl.scala index 67e6b187a..f1e8271bf 100644 --- a/sbt/src/main/scala/sbt/impl/ProcessImpl.scala +++ b/sbt/src/main/scala/sbt/impl/ProcessImpl.scala @@ -121,7 +121,7 @@ private abstract class AbstractProcessBuilder extends ProcessBuilder with SinkPa require(other.canPipeTo, "Piping to multiple processes is not supported.") new PipedProcessBuilder(this, other, false) } - def ##(other: ProcessBuilder): ProcessBuilder = new SequenceProcessBuilder(this, other) + def ###(other: ProcessBuilder): ProcessBuilder = new SequenceProcessBuilder(this, other) protected def toSource = this protected def toSink = this @@ -246,7 +246,7 @@ private class OrProcessBuilder(first: ProcessBuilder, second: ProcessBuilder) ex { override def createProcess(io: ProcessIO) = new OrProcess(first, second, io) } -private class SequenceProcessBuilder(first: ProcessBuilder, second: ProcessBuilder) extends SequentialProcessBuilder(first, second, "##") +private class SequenceProcessBuilder(first: ProcessBuilder, second: ProcessBuilder) extends SequentialProcessBuilder(first, second, "###") { override def createProcess(io: ProcessIO) = new ProcessSequence(first, second, io) } diff --git a/sbt/src/sbt-test/project/flatten/project/build/src/FlatProject.scala b/sbt/src/sbt-test/project/flatten/project/build/src/FlatProject.scala index f9cb648fc..4ff35e60f 100644 --- a/sbt/src/sbt-test/project/flatten/project/build/src/FlatProject.scala +++ b/sbt/src/sbt-test/project/flatten/project/build/src/FlatProject.scala @@ -6,12 +6,12 @@ class FlatProject(info: ProjectInfo) extends DefaultProject(info) val sc = "org.scalacheck" % "scalacheck" % "1.5" % "test" def sourceFilter = "*.java" | "*.scala" - override def mainSources = descendents(sourcePath ##, sourceFilter) - override def mainResources = descendents(sourcePath ##, -sourceFilter) + override def mainSources = descendents(sourcePath ###, sourceFilter) + override def mainResources = descendents(sourcePath ###, -sourceFilter) override def testSourcePath = "test-src" - override def testSources = descendents(testSourcePath ##, sourceFilter) - override def testResources = descendents(testSourcePath ##, -sourceFilter) + override def testSources = descendents(testSourcePath ###, sourceFilter) + override def testResources = descendents(testSourcePath ###, -sourceFilter) lazy val unpackageProject = task diff --git a/sbt/src/test/scala/sbt/ProcessSpecification.scala b/sbt/src/test/scala/sbt/ProcessSpecification.scala index ed722128b..fe02609df 100644 --- a/sbt/src/test/scala/sbt/ProcessSpecification.scala +++ b/sbt/src/test/scala/sbt/ProcessSpecification.scala @@ -15,7 +15,7 @@ object ProcessSpecification extends Properties("Process I/O") 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("### 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)) diff --git a/sbt/src/main/scala/sbt/classfile/Analyze.scala b/util/classfile/Analyze.scala similarity index 89% rename from sbt/src/main/scala/sbt/classfile/Analyze.scala rename to util/classfile/Analyze.scala index cf1643152..7f6a1b297 100644 --- a/sbt/src/main/scala/sbt/classfile/Analyze.scala +++ b/util/classfile/Analyze.scala @@ -1,9 +1,10 @@ /* sbt -- Simple Build Tool - * Copyright 2009 Mark Harrah + * Copyright 2009, 2010 Mark Harrah */ -package sbt.classfile -import sbt._ +package sbt +package classfile +import ClassfileLogger._ import scala.collection.mutable import mutable.{ArrayBuffer, Buffer} import java.io.File @@ -13,8 +14,8 @@ import java.lang.reflect.Modifier.{STATIC, PUBLIC, ABSTRACT} private[sbt] object Analyze { - def apply[T](basePath: Path, outputDirectory: Path, sources: Iterable[Path], roots: Iterable[Path], log: Logger) - (allProducts: => scala.collection.Set[Path], analysis: AnalysisCallback, loader: ClassLoader) + def apply[T](basePath: Path, outputDirectory: Path, sources: Iterable[Path], roots: Iterable[Path], log: ClassfileLogger) + (allProducts: => scala.collection.Set[Path], analysis: xsbti.AnalysisCallback, loader: ClassLoader) (compile: => Option[String]): Option[String] = { val sourceSet = Set(sources.toSeq : _*) @@ -35,15 +36,15 @@ private[sbt] object Analyze val sourceToClassFiles = new mutable.HashMap[Path, Buffer[ClassFile]] val superclasses = analysis.superclassNames flatMap { tpe => load(tpe, "Could not load superclass '" + tpe + "'") } - val annotations = analysis.annotationNames + val annotations = analysis.annotationNames.toSeq - def annotated(fromClass: Seq[Annotation]) = if(fromClass.isEmpty) Nil else annotations.filter(Set() ++ fromClass.map(_.annotationType.getName)) + def annotated(fromClass: Seq[Annotation]) = if(fromClass.isEmpty) Nil else annotations.filter(fromClass.map(_.annotationType.getName).toSet) // parse class files and assign classes to sources. This must be done before dependencies, since the information comes // as class->class dependencies that must be mapped back to source->class dependencies using the source+class assignment for(newClass <- newClasses; path <- Path.relativize(outputDirectory, newClass); - classFile = Parser(newClass.asFile, log); + classFile = Parser(newClass.asFile); sourceFile <- classFile.sourceFile orElse guessSourceName(newClass.asFile.getName); source <- guessSourcePath(sourceSet, roots, classFile, log)) { @@ -75,10 +76,10 @@ private[sbt] object Analyze } def processDependency(tpe: String) { - Control.trapAndLog(log) + trapAndLog(log) { val loaded = load(tpe, "Problem processing dependencies of source " + source) - for(clazz <- loaded; file <- Control.convertException(FileUtilities.classLocationFile(clazz)).right) + for(clazz <- loaded; file <- ErrorHandling.convert(IO.classLocationFile(clazz)).right) { if(file.isDirectory) { @@ -107,7 +108,7 @@ private[sbt] object Analyze } } - compile orElse Control.convertErrorMessage(log)(analyze()).left.toOption + compile orElse ClassfileLogger.convertErrorMessage(log)(analyze()).left.toOption } private def guessSourceName(name: String) = Some( takeToDollar(trimClassExt(name)) ) private def takeToDollar(name: String) = @@ -118,7 +119,7 @@ private[sbt] object Analyze private final val ClassExt = ".class" private def trimClassExt(name: String) = if(name.endsWith(ClassExt)) name.substring(0, name.length - ClassExt.length) else name private def resolveClassFile(file: File, className: String): File = (file /: (className.replace('.','/') + ClassExt).split("/"))(new File(_, _)) - private def guessSourcePath(sources: scala.collection.Set[Path], roots: Iterable[Path], classFile: ClassFile, log: Logger) = + private def guessSourcePath(sources: scala.collection.Set[Path], roots: Iterable[Path], classFile: ClassFile, log: ClassfileLogger) = { val classNameParts = classFile.className.split("""\.""") val lastIndex = classNameParts.length - 1 diff --git a/sbt/src/main/scala/sbt/classfile/ClassFile.scala b/util/classfile/ClassFile.scala similarity index 96% rename from sbt/src/main/scala/sbt/classfile/ClassFile.scala rename to util/classfile/ClassFile.scala index df004c855..df73acf9b 100644 --- a/sbt/src/main/scala/sbt/classfile/ClassFile.scala +++ b/util/classfile/ClassFile.scala @@ -1,7 +1,8 @@ /* sbt -- Simple Build Tool * Copyright 2009 Mark Harrah */ -package sbt.classfile +package sbt +package classfile import Constants._ import java.io.File @@ -31,7 +32,7 @@ private[sbt] final case class Constant(tag: Byte, nameIndex: Int, typeIndex: Int def this(tag: Byte, value: AnyRef) = this(tag, -1, -1, Some(value)) def wide = tag == ConstantLong || tag == ConstantDouble } -private[sbt] final case class FieldOrMethodInfo(accessFlags: Int, name: Option[String], descriptor: Option[String], attributes: RandomAccessSeq[AttributeInfo]) extends NotNull +private[sbt] final case class FieldOrMethodInfo(accessFlags: Int, name: Option[String], descriptor: Option[String], attributes: IndexedSeq[AttributeInfo]) extends NotNull { def isStatic = (accessFlags&ACC_STATIC)== ACC_STATIC def isPublic = (accessFlags&ACC_PUBLIC)==ACC_PUBLIC diff --git a/util/classfile/ClassfileLogger.scala b/util/classfile/ClassfileLogger.scala new file mode 100644 index 000000000..6c2ede1f9 --- /dev/null +++ b/util/classfile/ClassfileLogger.scala @@ -0,0 +1,25 @@ +/* sbt -- Simple Build Tool + * Copyright 2009, 2010 Mark Harrah + */ +package sbt +package classfile + +trait ClassfileLogger +{ + def warn(msg: => String): Unit + def error(msg: => String): Unit + def trace(exception: => Throwable): Unit +} +object ClassfileLogger +{ + def convertErrorMessage[T](log: ClassfileLogger)(t: => T): Either[String, T] = + { + try { Right(t) } + catch { case e: Exception => log.trace(e); Left(e.toString) } + } + def trapAndLog(log: ClassfileLogger)(execute: => Unit) + { + try { execute } + catch { case e => log.trace(e); log.error(e.toString) } + } +} \ No newline at end of file diff --git a/sbt/src/main/scala/sbt/classfile/Parser.scala b/util/classfile/Parser.scala similarity index 91% rename from sbt/src/main/scala/sbt/classfile/Parser.scala rename to util/classfile/Parser.scala index b4c85fada..cf33fca2e 100644 --- a/sbt/src/main/scala/sbt/classfile/Parser.scala +++ b/util/classfile/Parser.scala @@ -1,8 +1,8 @@ /* sbt -- Simple Build Tool * Copyright 2009 Mark Harrah */ -package sbt.classfile -import sbt._ +package sbt +package classfile import java.io.{DataInputStream, File, InputStream} @@ -15,9 +15,9 @@ import Constants._ private[sbt] object Parser { - def apply(file: File, log: Logger): ClassFile = FileUtilities.readStreamValue(file, log)(parse(file.getCanonicalPath, log)).right.get - private def parse(fileName: String, log: Logger)(is: InputStream): Either[String, ClassFile] = Right(parseImpl(fileName, is, log)) - private def parseImpl(filename: String, is: InputStream, log: Logger): ClassFile = + def apply(file: File): ClassFile = Using.fileInputStream(file)(parse(file.getCanonicalPath)).right.get + private def parse(fileName: String)(is: InputStream): Either[String, ClassFile] = Right(parseImpl(fileName, is)) + private def parseImpl(filename: String, is: InputStream): ClassFile = { val in = new DataInputStream(is) new ClassFile @@ -114,7 +114,7 @@ private[sbt] object Parser } } } - private def array[T](size: Int)(f: => T)(implicit mf: scala.reflect.Manifest[T]) = Array.fromFunction(i => f)(size) + private def array[T : scala.reflect.Manifest](size: Int)(f: => T) = Array.tabulate(size)(_ => f) private def parseConstantPool(in: DataInputStream) = { val constantPoolSize = in.readUnsignedShort() diff --git a/sbt/src/main/scala/sbt/complete/History.scala b/util/complete/History.scala similarity index 69% rename from sbt/src/main/scala/sbt/complete/History.scala rename to util/complete/History.scala index cb4fae033..bf009f626 100644 --- a/sbt/src/main/scala/sbt/complete/History.scala +++ b/util/complete/History.scala @@ -1,15 +1,19 @@ -package sbt.complete +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt +package complete import History.number -final class History private(lines: Array[String], log: Logger) extends NotNull +final class History private(lines: IndexedSeq[String], error: (=> String) => Unit) extends NotNull { private def reversed = lines.reverse - def all: Seq[String] = lines.toArray + def all: Seq[String] = lines def size = lines.length def !! : Option[String] = !- (1) - def apply(i: Int): Option[String] = if(0 <= i && i < size) Some( lines(i) ) else { log.error("Invalid history index: " + i); None } + def apply(i: Int): Option[String] = if(0 <= i && i < size) Some( lines(i) ) else { error("Invalid history index: " + i); None } def !(i: Int): Option[String] = apply(i) def !(s: String): Option[String] = @@ -25,7 +29,7 @@ final class History private(lines: Array[String], log: Logger) extends NotNull private def nonEmpty[T](s: String)(act: => Option[T]): Option[T] = if(s.isEmpty) { - log.error("No action specified to history command") + error("No action specified to history command") None } else @@ -37,7 +41,7 @@ final class History private(lines: Array[String], log: Logger) extends NotNull object History { - def apply(lines: Seq[String], log: Logger): History = new History(lines.toArray, log) + def apply(lines: Seq[String], error: (=> String) => Unit): History = new History(lines.toIndexedSeq, error) def number(s: String): Option[Int] = try { Some(s.toInt) } diff --git a/sbt/src/main/scala/sbt/complete/HistoryCommands.scala b/util/complete/HistoryCommands.scala similarity index 71% rename from sbt/src/main/scala/sbt/complete/HistoryCommands.scala rename to util/complete/HistoryCommands.scala index a65893d78..a03c9cfca 100644 --- a/sbt/src/main/scala/sbt/complete/HistoryCommands.scala +++ b/util/complete/HistoryCommands.scala @@ -1,4 +1,8 @@ -package sbt.complete +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt +package complete object HistoryCommands { @@ -30,26 +34,27 @@ object HistoryCommands StartsWithString -> "Execute the most recent command starting with 'string'", ContainsString -> "Execute the most recent command containing 'string'" ) - def printHelp(log: Logger): Unit = - println("History commands:\n " + (descriptions.map{ case (c,d) => c + " " + d}).mkString("\n ")) + def helpString = "History commands:\n " + (descriptions.map{ case (c,d) => c + " " + d}).mkString("\n ") + def printHelp(): Unit = + println(helpString) - def apply(s: String, historyPath: Option[Path], maxLines: Int, log: Logger): Option[List[String]] = + def apply(s: String, historyPath: Option[Path], maxLines: Int, error: (=> String) => Unit): Option[List[String]] = if(s.isEmpty) { - printHelp(log) + printHelp() Some(Nil) } else { - val lines = historyPath.toList.flatMap(h => xsbt.FileUtilities.readLines(h.asFile) ).toArray + val lines = historyPath.toList.flatMap(h => IO.readLines(h.asFile) ).toArray if(lines.isEmpty) { - log.warn("No history") + error("No history") None } else { - val history = complete.History(lines, log) + val history = complete.History(lines, error) if(s.startsWith(ListCommands)) { val rest = s.substring(ListCommands.length) @@ -57,16 +62,17 @@ object HistoryCommands printHistory(history, maxLines, show) Some(Nil) } - else { - val command = historyCommand(history, s, log) + else + { + val command = historyCommand(history, s) command.foreach(lines(lines.length - 1) = _) - historyPath foreach { h => xsbt.FileUtilities.writeLines(h.asFile, lines) } + historyPath foreach { h => IO.writeLines(h.asFile, lines) } Some(command.toList) } } } def printHistory(history: complete.History, historySize: Int, show: Int): Unit = history.list(historySize, show).foreach(println) - def historyCommand(history: complete.History, s: String, log: Logger): Option[String] = + def historyCommand(history: complete.History, s: String): Option[String] = { if(s == Last) history !! diff --git a/util/control/ErrorHandling.scala b/util/control/ErrorHandling.scala index f4d42993b..4072edff3 100644 --- a/util/control/ErrorHandling.scala +++ b/util/control/ErrorHandling.scala @@ -1,7 +1,7 @@ /* sbt -- Simple Build Tool * Copyright 2009 Mark Harrah */ -package xsbt +package sbt object ErrorHandling { @@ -22,7 +22,7 @@ object ErrorHandling try { Right(f) } catch { case e: Exception => Left(e) } } -final class TranslatedException private[xsbt](msg: String, cause: Throwable) extends RuntimeException(msg, cause) +final class TranslatedException private[sbt](msg: String, cause: Throwable) extends RuntimeException(msg, cause) { override def toString = msg } \ No newline at end of file diff --git a/util/datatype/Generator.scala b/util/datatype/Generator.scala index a6eeec5e7..521473360 100644 --- a/util/datatype/Generator.scala +++ b/util/datatype/Generator.scala @@ -5,6 +5,7 @@ package xsbt package api import java.io.File +import sbt.Path import sbt.IO.write import Generator._ @@ -25,8 +26,8 @@ abstract class GeneratorBase(val basePkgName: String, val baseDirectory: File) e def writeSource(name: String, pkgName: String, content: String) { - import sbt.Paths._ - val file = baseDirectory / packagePath(pkgName) / (name+ ".java") + import Path._ + val file = Path.fromString(baseDirectory, packagePath(pkgName)) / (name+ ".java") file.getParentFile.mkdirs() write(file, "package " + pkgName + ";\n\n" + content) } diff --git a/util/io/IO.scala b/util/io/IO.scala index b506ab08d..7113e7aea 100644 --- a/util/io/IO.scala +++ b/util/io/IO.scala @@ -4,7 +4,7 @@ package sbt import Using._ -import xsbt.ErrorHandling.translate +import ErrorHandling.translate import java.io.{ByteArrayOutputStream, BufferedWriter, File, FileInputStream, InputStream, OutputStream} import java.net.{URI, URISyntaxException, URL} @@ -24,7 +24,7 @@ object IO val temporaryDirectory = new File(System.getProperty("java.io.tmpdir")) /** The size of the byte or char buffer used in various methods.*/ private val BufferSize = 8192 - private val Newline = System.getProperty("line.separator") + val Newline = System.getProperty("line.separator") val utf8 = Charset.forName("UTF-8") @@ -91,8 +91,38 @@ object IO else error(failBase) } - def unzip(from: File, toDirectory: File, filter: NameFilter = AllPassFilter): Set[File] = fileInputStream(from)(in => unzip(in, toDirectory, filter)) - def unzip(from: InputStream, toDirectory: File, filter: NameFilter): Set[File] = + + /** Gzips the file 'in' and writes it to 'out'. 'in' cannot be the same file as 'out'. */ + def gzip(in: File, out: File) + { + require(in != out, "Input file cannot be the same as the output file.") + Using.fileInputStream(in) { inputStream => + Using.fileOutputStream()(out) { outputStream => + gzip(inputStream, outputStream) + } + } + } + /** Gzips the InputStream 'in' and writes it to 'output'. Neither stream is closed.*/ + def gzip(input: InputStream, output: OutputStream): Unit = + gzipOutputStream(output) { gzStream => transfer(input, gzStream) } + + /** Gunzips the file 'in' and writes it to 'out'. 'in' cannot be the same file as 'out'. */ + def gunzip(in: File, out: File) + { + require(in != out, "Input file cannot be the same as the output file.") + Using.fileInputStream(in) { inputStream => + Using.fileOutputStream()(out) { outputStream => + gunzip(inputStream, outputStream) + } + } + } + /** Gunzips the InputStream 'input' and writes it to 'output'. Neither stream is closed.*/ + def gunzip(input: InputStream, output: OutputStream): Unit = + gzipInputStream(input) { gzStream => transfer(gzStream, output) } + + def unzip(from: File, toDirectory: File, filter: NameFilter = AllPassFilter): Set[File] = fileInputStream(from)(in => unzipStream(in, toDirectory, filter)) + def unzipURL(from: URL, toDirectory: File, filter: NameFilter = AllPassFilter): Set[File] = urlInputStream(from)(in => unzipStream(in, toDirectory, filter)) + def unzipStream(from: InputStream, toDirectory: File, filter: NameFilter = AllPassFilter): Set[File] = { createDirectory(toDirectory) zipInputStream(from) { zipInput => extract(zipInput, toDirectory, filter) } @@ -135,6 +165,14 @@ object IO Set() ++ set } + /** Retrieves the content of the given URL and writes it to the given File. */ + def download(url: URL, to: File) = + Using.urlInputStream(url) { inputStream => + Using.fileOutputStream()(to) { outputStream => + transfer(inputStream, outputStream) + } + } + /** Copies all bytes from the given input stream to the given output stream. * Neither stream is closed.*/ def transfer(in: InputStream, out: OutputStream): Unit = transferImpl(in, out, false) @@ -185,6 +223,12 @@ object IO } create(0) } + def withTemporaryFile[T](prefix: String, postfix: String)(action: File => T): T = + { + val file = File.createTempFile(prefix, postfix) + try { action(file) } + finally { file.delete() } + } private[sbt] def jars(dir: File): Iterable[File] = listFiles(dir, GlobFilter("*.jar")) @@ -205,7 +249,7 @@ object IO def listFiles(filter: java.io.FileFilter)(dir: File): Array[File] = wrapNull(dir.listFiles(filter)) def listFiles(dir: File, filter: java.io.FileFilter): Array[File] = wrapNull(dir.listFiles(filter)) def listFiles(dir: File): Array[File] = wrapNull(dir.listFiles()) - private def wrapNull(a: Array[File]) = + private[sbt] def wrapNull(a: Array[File]) = { if(a == null) new Array[File](0) @@ -219,14 +263,14 @@ object IO * @param outputJar The file to write the jar to. * @param manifest The manifest for the jar.*/ def jar(sources: Iterable[(File,String)], outputJar: File, manifest: Manifest): Unit = - archive(sources, outputJar, Some(manifest)) + archive(sources.toSeq, outputJar, Some(manifest)) /** Creates a zip file. * @param sources The files to include in the zip file paired with the entry name in the zip. * @param outputZip The file to write the zip to.*/ def zip(sources: Iterable[(File,String)], outputZip: File): Unit = - archive(sources, outputZip, None) + archive(sources.toSeq, outputZip, None) - private def archive(sources: Iterable[(File,String)], outputFile: File, manifest: Option[Manifest]) + private def archive(sources: Seq[(File,String)], outputFile: File, manifest: Option[Manifest]) { if(outputFile.isDirectory) error("Specified output file " + outputFile + " is a directory.") @@ -241,7 +285,7 @@ object IO } } } - private def writeZip(sources: Iterable[(File,String)], output: ZipOutputStream)(createEntry: String => ZipEntry) + private def writeZip(sources: Seq[(File,String)], output: ZipOutputStream)(createEntry: String => ZipEntry) { def add(sourceFile: File, name: String) { @@ -316,22 +360,26 @@ object IO else None } - def copy(sources: Iterable[(File,File)]): Set[File] = Set( sources.map(tupled(copyImpl)).toSeq.toArray : _*) - private def copyImpl(from: File, to: File): File = + def copy(sources: Iterable[(File,File)], overwrite: Boolean = false, preserveLastModified: Boolean = false): Set[File] = + sources.map( tupled(copyImpl(overwrite, preserveLastModified)) ).toSet + private def copyImpl(overwrite: Boolean, preserveLastModified: Boolean)(from: File, to: File): File = { - if(!to.exists || from.lastModified > to.lastModified) + if(overwrite || !to.exists || from.lastModified > to.lastModified) { if(from.isDirectory) createDirectory(to) else { createDirectory(to.getParentFile) - copyFile(from, to) + copyFile(from, to, preserveLastModified) } } to } - def copyFile(sourceFile: File, targetFile: File) + def copyDirectory(source: File, target: File, overwrite: Boolean = false, preserveLastModified: Boolean = false): Unit = + copy( (Path.fromFile(source) ***) x Path.rebase(source, target), overwrite, preserveLastModified) + + def copyFile(sourceFile: File, targetFile: File, preserveLastModified: Boolean = false) { require(sourceFile.exists, "Source file '" + sourceFile.getAbsolutePath + "' does not exist.") require(!sourceFile.isDirectory, "Source file '" + sourceFile.getAbsolutePath + "' is a directory.") @@ -342,7 +390,10 @@ object IO error("Could not copy '" + sourceFile + "' to '" + targetFile + "' (" + copied + "/" + in.size + " bytes copied)") } } + if(preserveLastModified) + copyLastModified(sourceFile, targetFile) } + 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) } @@ -427,7 +478,7 @@ object IO delete(b) if(!a.renameTo(b)) { - copyFile(a, b) + copyFile(a, b, true) delete(a) } } diff --git a/sbt/src/main/scala/sbt/Path.scala b/util/io/Path.scala similarity index 85% rename from sbt/src/main/scala/sbt/Path.scala rename to util/io/Path.scala index 69c6a6ce3..c2b5e4e64 100644 --- a/sbt/src/main/scala/sbt/Path.scala +++ b/util/io/Path.scala @@ -1,32 +1,31 @@ /* sbt -- Simple Build Tool - * Copyright 2008, 2009 Mark Harrah + * Copyright 2008, 2009, 2010 Mark Harrah */ package sbt import Path._ -import FileUtilities.wrapNull +import IO.{pathSplit, wrapNull} import java.io.File import java.net.URL -import scala.collection.{immutable, mutable} -import mutable.{Set, HashSet} +import scala.collection.{generic, immutable, mutable, TraversableLike} /** A Path represents a file in a project. * @see sbt.PathFinder*/ -sealed abstract class Path extends PathFinder with NotNull +sealed abstract class Path extends PathFinder { /** Creates a base directory for this path. This is used by copy and zip functions * to determine the relative path that should be used in the destination. For example, * if the following path is specified to be copied to directory 'd', * - * ((a / b) ##) / x / y + * ((a / b) ###) / x / y * * the copied path would be * * d / x / y * * The relativePath method is used to return the relative path to the base directory. */ - override def ## : Path = new BaseDirectory(this) - private[sbt] def addTo(pathSet: Set[Path]) + override def ### : Path = new BaseDirectory(this) + private[sbt] def addTo(pathSet: mutable.Set[Path]) { if(asFile.exists) pathSet += this @@ -49,7 +48,7 @@ sealed abstract class Path extends PathFinder with NotNull /** The file represented by this path converted to a URL.*/ def asURL = asFile.toURI.toURL /** The string representation of this path relative to the base directory. The project directory is the - * default base directory if one is not specified explicitly using the ## operator.*/ + * default base directory if one is not specified explicitly using the ### operator.*/ lazy val relativePath: String = relativePathString(sep.toString) def relativePathString(separator: String): String final def projectRelativePath: String = projectRelativePathString(sep.toString) @@ -82,7 +81,7 @@ sealed abstract class Path extends PathFinder with NotNull } private final class BaseDirectory(private[sbt] val path: Path) extends Path { - override def ## : Path = this + override def ### : Path = this override def toString = path.toString def asFile = path.asFile def relativePathString(separator: String) = "" @@ -124,10 +123,14 @@ private[sbt] final class RelativePath(val parentPath: Path, val component: Strin parentRelative + separator + component } } -object Path +object Path extends Alternatives with Mapper { import java.io.File import File.pathSeparator + implicit def fileToPath(file: File): Path = Path.fromFile(file) + implicit def pathToFile(path: Path): File = path.asFile + implicit def pathsToFiles[CC[X] <: TraversableLike[X,CC[X]]](cc: CC[Path])(implicit cb: generic.CanBuildFrom[CC[Path], File, CC[File]]): CC[File] = + cc.map(_.asFile) def fileProperty(name: String) = Path.fromFile(System.getProperty(name)) def userHome = fileProperty("user.home") @@ -146,7 +149,7 @@ object Path def splitString(projectPath: Path, value: String): Iterable[Path] = { - for(pathString <- FileUtilities.pathSplit(value) if pathString.length > 0) yield + for(pathString <- pathSplit(value) if pathString.length > 0) yield Path.fromString(projectPath, pathString) } @@ -154,14 +157,14 @@ object Path def emptyPathFinder = new PathFinder { - private[sbt] def addTo(pathSet: Set[Path]) {} + private[sbt] def addTo(pathSet: mutable.Set[Path]) {} } /** A PathFinder that selects the paths provided by the paths argument, which is * reevaluated on each call to the PathFinder's get method. */ def lazyPathFinder(paths: => Iterable[Path]): PathFinder = new PathFinder { - private[sbt] def addTo(pathSet: Set[Path]) = pathSet ++= paths + private[sbt] def addTo(pathSet: mutable.Set[Path]) = pathSet ++= paths } def finder(files: => Iterable[File]): PathFinder = lazyPathFinder { fromFiles(files) } @@ -243,11 +246,8 @@ object Path def fromFile(file: File): Path = new FilePath(file) def fromFiles(files: Iterable[File]): Iterable[Path] = files.map(fromFile) - // done this way because collection.Set.map returns Iterable that is Set underneath, so no need to create a new set - def mapSet[T](files: Iterable[Path])(f: Path => T): immutable.Set[T] = - files.map(f) match { case s: immutable.Set[T] => s; case x => immutable.Set() ++ x } - def getFiles(files: Iterable[Path]): immutable.Set[File] = mapSet(files)(_.asFile) - def getURLs(files: Iterable[Path]): Array[URL] = files.map(_.asURL).toSeq.toArray + def getFiles(files: Traversable[Path]): immutable.Set[File] = files.map(_.asFile).toSet + def getURLs(files: Traversable[Path]): Array[URL] = files.map(_.asURL).toArray } /** A path finder constructs a set of paths. The set is evaluated by a call to the get @@ -274,9 +274,20 @@ sealed abstract class PathFinder extends NotNull final def \ (literal: String): PathFinder = this / literal /** Makes the paths selected by this finder into base directories. - * @see Path.## + * @see Path.### */ - def ## : PathFinder = new BasePathFinder(this) + def ### : PathFinder = new BasePathFinder(this) + + /** Applies `mapper` to each path selected by this PathFinder and returns the path paired with the non-empty result. + * If the result is empty (None) and `errorIfNone` is true, an exception is thrown. + * If `errorIfNone` is false, the path is dropped from the returned Traversable.*/ + def x[T](mapper: File => Option[T], errorIfNone: Boolean = true): Traversable[(File,T)] = + { + val apply = if(errorIfNone) mapper | fail else mapper + for(file <- getFiles; mapped <- apply(file)) yield (file, mapped) + } + /** Pairs each path selected by this PathFinder with its relativePath.*/ + def xx: Traversable[(File, String)] = get.map(path => (path.asFile, path.relativePath)) /** Selects all descendent paths with a name that matches include and do not have an intermediate * path with a name that matches intermediateExclude. Typical usage is: @@ -287,11 +298,11 @@ sealed abstract class PathFinder extends NotNull /** Evaluates this finder. The set returned by this method will reflect the underlying filesystem at the * time of calling. If the filesystem changes, two calls to this method might be different.*/ - final def get: scala.collection.Set[Path] = + final def get: immutable.Set[Path] = { - val pathSet = new HashSet[Path] + val pathSet = new mutable.HashSet[Path] addTo(pathSet) - wrap.Wrappers.readOnly(pathSet) + pathSet.toSet } /** Only keeps paths for which `f` returns true. It is non-strict, so it is not evaluated until the returned finder is evaluated.*/ final def filter(f: Path => Boolean): PathFinder = Path.lazyPathFinder(get.filter(f)) @@ -305,8 +316,8 @@ sealed abstract class PathFinder extends NotNull final def getPaths: immutable.Set[String] = strictMap(_.absolutePath) /** Evaluates this finder and converts the results to a `Set` of relative path strings.*/ final def getRelativePaths: immutable.Set[String] = strictMap(_.relativePath) - final def strictMap[T](f: Path => T): immutable.Set[T] = Path.mapSet(get)(f) - private[sbt] def addTo(pathSet: Set[Path]) + final def strictMap[T](f: Path => T): immutable.Set[T] = get.map(f).toSet + private[sbt] def addTo(pathSet: mutable.Set[Path]) /** Create a PathFinder from this one where each path has a unique name. * A single path is arbitrarily selected from the set of paths with the same name.*/ @@ -321,10 +332,10 @@ sealed abstract class PathFinder extends NotNull } private class BasePathFinder(base: PathFinder) extends PathFinder { - private[sbt] def addTo(pathSet: Set[Path]) + private[sbt] def addTo(pathSet: mutable.Set[Path]) { for(path <- base.get) - pathSet += (path ##) + pathSet += (path ###) } } private abstract class FilterPath extends PathFinder with FileFilter @@ -333,7 +344,7 @@ private abstract class FilterPath extends PathFinder with FileFilter def filter: FileFilter final def accept(file: File) = filter.accept(file) - protected def handlePath(path: Path, pathSet: Set[Path]) + protected def handlePath(path: Path, pathSet: mutable.Set[Path]) { for(matchedFile <- wrapNull(path.asFile.listFiles(this))) pathSet += path / matchedFile.getName @@ -341,7 +352,7 @@ private abstract class FilterPath extends PathFinder with FileFilter } private class DescendentOrSelfPathFinder(val parent: PathFinder, val filter: FileFilter) extends FilterPath { - private[sbt] def addTo(pathSet: Set[Path]) + private[sbt] def addTo(pathSet: mutable.Set[Path]) { for(path <- parent.get) { @@ -350,7 +361,7 @@ private class DescendentOrSelfPathFinder(val parent: PathFinder, val filter: Fil handlePathDescendent(path, pathSet) } } - private def handlePathDescendent(path: Path, pathSet: Set[Path]) + private def handlePathDescendent(path: Path, pathSet: mutable.Set[Path]) { handlePath(path, pathSet) for(childDirectory <- wrapNull(path.asFile.listFiles(DirectoryFilter))) @@ -359,7 +370,7 @@ private class DescendentOrSelfPathFinder(val parent: PathFinder, val filter: Fil } private class ChildPathFinder(val parent: PathFinder, val filter: FileFilter) extends FilterPath { - private[sbt] def addTo(pathSet: Set[Path]) + private[sbt] def addTo(pathSet: mutable.Set[Path]) { for(path <- parent.get) handlePath(path, pathSet) @@ -367,7 +378,7 @@ private class ChildPathFinder(val parent: PathFinder, val filter: FileFilter) ex } private class Paths(a: PathFinder, b: PathFinder) extends PathFinder { - private[sbt] def addTo(pathSet: Set[Path]) + private[sbt] def addTo(pathSet: mutable.Set[Path]) { a.addTo(pathSet) b.addTo(pathSet) @@ -375,12 +386,12 @@ private class Paths(a: PathFinder, b: PathFinder) extends PathFinder } private class ExcludePaths(include: PathFinder, exclude: PathFinder) extends PathFinder { - private[sbt] def addTo(pathSet: Set[Path]) + private[sbt] def addTo(pathSet: mutable.Set[Path]) { - val includeSet = new HashSet[Path] + val includeSet = new mutable.HashSet[Path] include.addTo(includeSet) - val excludeSet = new HashSet[Path] + val excludeSet = new mutable.HashSet[Path] exclude.addTo(excludeSet) includeSet --= excludeSet diff --git a/util/io/PathMapper.scala b/util/io/PathMapper.scala index 4d3ab2ee4..fd0327225 100644 --- a/util/io/PathMapper.scala +++ b/util/io/PathMapper.scala @@ -5,45 +5,50 @@ package sbt import java.io.File -trait PathMapper extends NotNull +trait Mapper { - def apply(file: File): String - def apply(files: Set[File]): Iterable[(File,String)] = files.view.map(f => (f,apply(f))) -} -final case class RelativePathMapper(base: File) extends PMapper(file => IO.relativize(base, file).getOrElse(file.getPath)) -final case object BasicPathMapper extends PMapper(_.getPath) -final case object FlatPathMapper extends PMapper(_.getName) -class PMapper(val f: File => String) extends PathMapper -{ - def apply(file: File): String = f(file) -} -object PathMapper -{ - val basic: PathMapper = BasicPathMapper - def relativeTo(base: File): PathMapper = RelativePathMapper(base) - def rebase(oldBase: File, newBase: File): PathMapper = - new PMapper(file => if(file == oldBase) "." else IO.relativize(oldBase, file).getOrElse(error(file + " not a descendent of " + oldBase))) - val flat = FlatPathMapper - def apply(f: File => String): PathMapper = new PMapper(f) + type PathMap = File => Option[String] + type FileMap = File => Option[File] + + val basic: PathMap = f => Some(f.getPath) + def relativeTo(base: File): PathMap = IO.relativize(base, _) + def rebase(oldBase: File, newBase0: String): PathMap = + { + val newBase = normalizeBase(newBase0) + (file: File) => + if(file == oldBase) + Some( if(newBase.isEmpty) "." else newBase ) + else + IO.relativize(oldBase, file).map(newBase + _) + } + def fail: Any => Nothing = f => error("No mapping for " + f) + val flat: PathMap = f => Some(f.getName) + def flatRebase(newBase0: String): PathMap = + { + val newBase = normalizeBase(newBase0) + f => Some(newBase + f.getName) + } + def some[A,B](f: A => B): A => Some[B] = x => Some(f(x)) + + def normalizeBase(base: String) = if(!base.isEmpty && !base.endsWith("/")) base + "/" else base + + def abs: FileMap = f => Some(f.getAbsoluteFile) + def resolve(newDirectory: File): FileMap = file => Some(new File(newDirectory, file.getPath)) + def rebase(oldBase: File, newBase: File): FileMap = + file => + if(file == oldBase) + Some(newBase) + else + IO.relativize(oldBase, file) map { r => new File(newBase, r) } + + def flat(newDirectory: File): FileMap = file => Some(new File(newDirectory, file.getName)) } -trait FileMapper extends NotNull +trait Alternative[A,B] { def | (g: A => Option[B]): A => Option[B] } +trait Alternatives { - def apply(file: File): File - def apply(files: Set[File]): Iterable[(File,File)] = files.view.map(f => (f,apply(f))) -} -class FMapper(f: File => File) extends FileMapper -{ - def apply(file: File) = f(file) -} -object FileMapper -{ - def basic(newDirectory: File) = new FMapper(file => new File(newDirectory, file.getPath)) - def rebase(oldBase: File, newBase: File): FileMapper = - { - val paths = PathMapper.rebase(oldBase, newBase) - new FMapper(file => new File(newBase, paths(file))) - } - def flat(newDirectory: File) = new FMapper(file => new File(newDirectory, file.getName)) - def apply(f: File => File) = new FMapper(f) + implicit def alternative[A,B](f:A => Option[B]): Alternative[A,B] = + new Alternative[A,B] { def | (g: A => Option[B]) = + (a: A) => f(a) orElse g(a) + } } \ No newline at end of file diff --git a/util/io/Paths.scala b/util/io/Paths.scala deleted file mode 100644 index 79ea148b4..000000000 --- a/util/io/Paths.scala +++ /dev/null @@ -1,51 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2008, 2009, 2010 Mark Harrah - */ -package sbt - -import java.io.File -import java.net.URL - -object Paths -{ - implicit def stringToPath(s: String): Path = new Path(new File(s)) - implicit def fileToPath(f: File): Path = new Path(f) - implicit def pathToFile(p: Path): File = p.asFile - implicit def filesToPaths(fs: Set[File]): Paths = new Paths(fs) - implicit def filesToPaths(fs: Iterable[File]): Paths = - fs match - { - case s: Set[File] => filesToPaths(s) - case _ => new Paths(Set(fs.toSeq : _*)) - } - def normalize(path: String): String = path.replace('/', File.separatorChar).replace('\\', File.separatorChar) -} - -import Paths._ -trait PathBase extends NotNull -{ - def files: Set[File] - def urls: Array[URL] = files.toArray[File].map(_.toURI.toURL) - def x(mapper: PathMapper): Iterable[(File,String)] = mapper(files) - def x(mapper: FileMapper): Iterable[(File,File)] = mapper(files) - def *(filter: java.io.FileFilter): Set[File] = files.flatMap(IO.listFiles(filter)) - def **(filter: java.io.FileFilter): Set[File] = files.filter(filter.accept) ++ files.flatMap(_ * AllPassFilter ** filter) - def *** = **(AllPassFilter) - def abs = files.map(_.getAbsoluteFile) - def descendentsExcept(include: java.io.FileFilter, intermediateExclude: java.io.FileFilter): Set[File] = - (this ** include) -- (this ** intermediateExclude ** include) -} - -final class Paths(val files: Set[File]) extends PathBase -{ - def \(subPath: String) = /(subPath) - def /(subPath: String): Set[File] = files.flatMap { file => val f = file / subPath; if(f.exists) Seq(f) else Seq() } -} -final class Path(val asFile: File) extends PathBase -{ - def files = Set(asFile) - def \(subPath: String) = /(subPath) - def /(subPath: String) = new File(asFile, normalize(subPath)) - def ++(files: Set[File]) = files + asFile - def ++(file: File) = Set(file, asFile) -} \ No newline at end of file diff --git a/util/io/Using.scala b/util/io/Using.scala index be93e25f0..731c8ca8d 100644 --- a/util/io/Using.scala +++ b/util/io/Using.scala @@ -4,7 +4,7 @@ package sbt import java.io.{Closeable, File, FileInputStream, FileOutputStream, InputStream, OutputStream} -import java.io.{ByteArrayOutputStream, InputStreamReader, OutputStreamWriter} +import java.io.{BufferedInputStream, BufferedOutputStream, ByteArrayOutputStream, InputStreamReader, OutputStreamWriter} import java.io.{BufferedReader, BufferedWriter, FileReader, FileWriter, Reader, Writer} import java.util.zip.{GZIPInputStream, GZIPOutputStream} import java.net.{URL, URISyntaxException} @@ -13,7 +13,7 @@ import java.nio.channels.FileChannel import java.util.jar.{Attributes, JarEntry, JarFile, JarInputStream, JarOutputStream, Manifest} import java.util.zip.{GZIPOutputStream, ZipEntry, ZipFile, ZipInputStream, ZipOutputStream} -import xsbt.ErrorHandling.translate +import ErrorHandling.translate import Using._ abstract class Using[Source, T] extends NotNull @@ -74,8 +74,8 @@ object Using } private def closeCloseable[T <: Closeable]: T => Unit = _.close() - def fileOutputStream(append: Boolean = false) = file(f => new FileOutputStream(f, append)) - def fileInputStream = file(f => new FileInputStream(f)) + def fileOutputStream(append: Boolean = false) = file(f => new BufferedOutputStream(new FileOutputStream(f, append))) + def fileInputStream = file(f => new BufferedInputStream(new FileInputStream(f))) def urlInputStream = resource( (u: URL) => translate("Error opening " + u + ": ")(u.openStream)) def fileOutputChannel = file(f => new FileOutputStream(f).getChannel) def fileInputChannel = file(f => new FileInputStream(f).getChannel) diff --git a/sbt/src/test/scala/sbt/PathSpecification.scala b/util/io/src/test/scala/PathSpecification.scala similarity index 81% rename from sbt/src/test/scala/sbt/PathSpecification.scala rename to util/io/src/test/scala/PathSpecification.scala index be3123f0c..888e175dd 100644 --- a/sbt/src/test/scala/sbt/PathSpecification.scala +++ b/util/io/src/test/scala/PathSpecification.scala @@ -10,9 +10,6 @@ import java.io.File object PathSpecification extends Properties("Path") { - val log = new ConsoleLogger - log.setLevel(Level.Warn) - // certain operations require a real underlying file. We'd like to create them in a managed temporary directory so that junk isn't left over from the test. // The arguments to several properties are functions that construct a Path or PathFinder given a base directory. type ToPath = ProjectDirectory => Path @@ -33,7 +30,7 @@ object PathSpecification extends Properties("Path") } property("Relative path") = forAll { (a: List[String], b: List[String]) => inTemp { dir => - pathForComponents(pathForComponents(dir, a) ##, b).relativePath == pathString(b) } + pathForComponents(pathForComponents(dir, a) ###, b).relativePath == pathString(b) } } property("Proper URL conversion") = forAll { (tp: ToPath) => withPath(tp) { path => path.asURL == path.asFile.toURI.toURL } @@ -43,7 +40,7 @@ object PathSpecification extends Properties("Path") } property("Base path equality") = forAll { (a: List[String], b: List[String]) => inTemp { dir => - pathForComponents(pathForComponents(dir, a) ##, b) == pathForComponents(pathForComponents(dir, a) ##, b) + pathForComponents(pathForComponents(dir, a) ###, b) == pathForComponents(pathForComponents(dir, a) ###, b) } } @@ -86,15 +83,15 @@ object PathSpecification extends Properties("Path") inTemp { dir => val bases = repeat(baseDirs(dir).get) val reallyDistinct: Set[String] = Set() ++ distinctNames -- dupNames.map(_._1) - val dupList = dupNames.flatMap { case (name, repeat) => if(reallyDistinct(name)) Nil else List.make(repeat, name) } + val dupList = dupNames.flatMap { case (name, repeat) => if(reallyDistinct(name)) Nil else List.fill(repeat)(name) } def create(names: List[String]): PathFinder = { val paths = (bases zip names ).map { case (a, b) => a / b }.filter(!_.exists) - paths.foreach { f => xsbt.FileUtilities.touch(f asFile) } + paths.foreach { f => IO.touch(f asFile) } Path.lazyPathFinder(paths) } - def names(s: scala.collection.Set[Path]) = s.map(_.name) + def names(s: Set[Path]) = s.map(_.name) val distinctPaths = create(reallyDistinct.toList) val dupPaths = create(dupList) @@ -102,15 +99,22 @@ object PathSpecification extends Properties("Path") val all = distinctPaths +++ dupPaths val distinct = all.distinct.get - val allNames = Set() ++ names(all.get) + val allNames = names(all.get) - (Set() ++ names(distinct)) == allNames && // verify nothing lost + names(distinct) == allNames && // verify nothing lost distinct.size == allNames.size // verify duplicates removed } } catch { case e => e.printStackTrace; throw e} } - private def repeat[T](s: Iterable[T]): List[T] = - List.make(100, ()).flatMap(_ => s) // should be an infinite Stream, but Stream isn't very lazy + def repeat[T](s: Iterable[T]): Stream[T] = + { + def toStr(l: List[T]): Stream[T] = l match { + case Nil => st + case x :: xs => Stream.cons(x, toStr(xs)) + } + lazy val st = if(s.isEmpty) Stream.empty else toStr(s.toList) + st + } private def withPath[T](tp: ToPath)(f: Path => T): T = @@ -119,24 +123,12 @@ object PathSpecification extends Properties("Path") inTemp { dir => f(ta(dir), tb(dir)) } private def createFileAndDo(a: List[String], b: List[String])(f: Path => Boolean) = - { - val result = - FileUtilities.doInTemporaryDirectory(log)( dir => - { - FileUtilities.touch(fileForComponents(dir, a ::: b), log) match - { - case None => Right(Some( f(new ProjectDirectory(dir)) )) - case Some(err) => Left(err) - } - }) - result match - { - case Left(err) => throw new RuntimeException(err) - case Right(opt) => opt.isDefined ==> opt.get + IO.withTemporaryDirectory { dir => + IO.touch(fileForComponents(dir, a ::: b)) + f(new ProjectDirectory(dir)) } - } private def inTemp[T](f: ProjectDirectory => T): T = - xsbt.FileUtilities.withTemporaryDirectory { dir => f(new ProjectDirectory(dir)) } + IO.withTemporaryDirectory { dir => f(new ProjectDirectory(dir)) } private def pathString(components: List[String]): String = components.mkString(File.separator) private def pathForComponents(base: Path, components: List[String]): Path = @@ -148,7 +140,7 @@ object PathSpecification extends Properties("Path") for(dir <- d; name <- s) yield { (projectPath: ProjectDirectory) => val f = dir(projectPath) / name - xsbt.FileUtilities.touch(f asFile) + IO.touch(f asFile) f } @@ -161,7 +153,7 @@ object PathSpecification extends Properties("Path") for(p <- genPath) yield { (projectPath: ProjectDirectory) => { val f = p(projectPath) - xsbt.FileUtilities.createDirectory(f asFile) + IO.createDirectory(f asFile) f } } @@ -176,7 +168,7 @@ object PathSpecification extends Properties("Path") b match { case None => base - case Some(relative) => pathForComponents(base ##, relative) + case Some(relative) => pathForComponents(base ###, relative) } } private implicit lazy val componentList: Gen[List[String]] = genList[String](MaxComponentCount)(pathComponent.arbitrary) @@ -185,7 +177,7 @@ object PathSpecification extends Properties("Path") for(size <- Gen.choose(0, maxSize); a <- Gen.listOfN(size, genA)) yield a private def trim(components: List[String]): List[String] = components.take(MaxComponentCount) - private def trim(component: String): String = component.substring(0, Math.min(component.length, MaxFilenameLength)) + private def trim(component: String): String = component.substring(0, math.min(component.length, MaxFilenameLength)) final val MaxFilenameLength = 20 final val MaxComponentCount = 6 diff --git a/util/io/src/test/scala/StashSpec.scala b/util/io/src/test/scala/StashSpec.scala index df76165d2..6f3fe2841 100644 --- a/util/io/src/test/scala/StashSpec.scala +++ b/util/io/src/test/scala/StashSpec.scala @@ -1,11 +1,11 @@ /* sbt -- Simple Build Tool * Copyright 2010 Mark Harrah */ -package xsbt +package sbt import org.specs._ -import FileUtilities._ +import IO._ import java.io.File import Function.tupled @@ -14,6 +14,7 @@ object CheckStash extends Specification "stash" should { "handle empty files" in { stash(Set()) { } + true must beTrue } "move files during execution" in { @@ -57,7 +58,7 @@ object CheckStash extends Specification case _: TestError | _: TestException | _: TestRuntimeException => false } - def allCorrect(s: Seq[File]) = (s.toList zip TestFiles.toList).forall(tupled(correct)) + def allCorrect(s: Seq[File]) = (s.toList zip TestFiles.toList).foreach((correct _).tupled) def correct(check: File, ref: (File, String)) = { check.exists must beTrue diff --git a/sbt/src/test/scala/sbt/LogWriterTest.scala b/util/log/src/test/scala/LogWriterTest.scala similarity index 93% rename from sbt/src/test/scala/sbt/LogWriterTest.scala rename to util/log/src/test/scala/LogWriterTest.scala index 0d4dac14d..6d01341a0 100644 --- a/sbt/src/test/scala/sbt/LogWriterTest.scala +++ b/util/log/src/test/scala/LogWriterTest.scala @@ -5,13 +5,16 @@ package sbt import org.scalacheck._ import Arbitrary.{arbitrary => arb, _} -import Gen.{oneOf, value} +import Gen.{listOfN, oneOf} import Prop._ import java.io.Writer object LogWriterTest extends Properties("Log Writer") { + final val MaxLines = 100 + final val MaxSegments = 10 + /* Tests that content written through a LoggerWriter is properly passed to the underlying Logger. * Each line, determined by the specified newline separator, must be logged at the correct logging level. */ property("properly logged") = forAll { (output: Output, newLine: NewLine) => @@ -72,7 +75,7 @@ object LogWriterTest extends Properties("Log Writer") implicit lazy val arbLevel : Arbitrary[Level.Value] = Arbitrary(genLevel) implicit def genLine(implicit logG: Gen[ToLog]): Gen[List[ToLog]] = - for(l <- arbList[ToLog].arbitrary; last <- logG) yield + for(l <- listOf[ToLog](MaxSegments); last <- logG) yield (addNewline(last) :: l.filter(!_.content.isEmpty)).reverse implicit def genLog(implicit content: Arbitrary[String], byChar: Arbitrary[Boolean]): Gen[ToLog] = @@ -87,17 +90,18 @@ object LogWriterTest extends Properties("Log Writer") new NewLine(str) implicit lazy val genLevel: Gen[Level.Value] = - oneOf(levelsGen : _*) + oneOf(Level.values.toSeq) implicit lazy val genOutput: Gen[Output] = - for(ls <- arbList[List[ToLog]].arbitrary; lv <- genLevel) yield + for(ls <- listOf[List[ToLog]](MaxLines); lv <- genLevel) yield new Output(ls, lv) - def levelsGen: Seq[Gen[Level.Value]] = Level.values.toList.map(x => value(x)) - def removeNewlines(s: String) = s.replaceAll("""[\n\r]+""", "") def addNewline(l: ToLog): ToLog = new ToLog(l.content + "\n", l.byCharacter) // \n will be replaced by a random line terminator for all lines + + def listOf[T](max: Int)(implicit content: Arbitrary[T]): Gen[List[T]] = + Gen.choose(0, max) flatMap { sz => listOfN(sz, content.arbitrary) } } /* Helper classes*/