more reorganization, mostly IO. Also, move class file analyzer and history code to separate projects

This commit is contained in:
Mark Harrah 2010-06-13 22:59:29 -04:00
parent d73762e203
commit 1585d805bd
43 changed files with 406 additions and 391 deletions

12
cache/Cache.scala vendored
View File

@ -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
}

7
cache/CacheIO.scala vendored
View File

@ -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] =

16
cache/FileInfo.scala vendored
View File

@ -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
{

View File

@ -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)

5
cache/NoCache.scala vendored
View File

@ -1,4 +1,7 @@
package xsbt
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt
import java.io.{InputStream,OutputStream}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -1,7 +1,7 @@
/* sbt -- Simple Build Tool
* Copyright 2009, 2010 Mark Harrah
*/
package xsbt
package sbt
object ChangeReport
{

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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))

View File

@ -31,6 +31,12 @@ public interface AnalysisCallback
/** Called to indicate that the source file <code>source</code> depends on the class file
* <code>clazz</code>.*/
public void classDependency(File clazz, File source);
/** Called to indicate that the source file <code>sourcePath</code> depends on the class file
* <code>classFile</code> that is a product of some source. This differs from classDependency
* because it is really a sourceDependency. The source corresponding to <code>classFile</code>
* 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 <code>source</code> produces a class file at
* <code>module</code>.*/
public void generatedClass(File source, File module);

View File

@ -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)) }

View File

@ -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

View File

@ -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*/ )

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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
* <code>(basePath ##) / x / y</code> is <code>x / y</code>.
* <code>(basePath ###) / x / y</code> is <code>x / y</code>.
* @param outputJar The file to write the jar to.
* @param manifest The manifest for the jar.
* @param recursive If true, any directories in <code>sources</code> 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
* <code>(basePath ##) / x / y</code> is <code>x / y</code>.
* <code>(basePath ###) / x / y</code> is <code>x / y</code>.
* @param outputZip The file to write the zip to.
* @param recursive If true, any directories in <code>sources</code> 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 <code>(basePath ##) / x / y</code> is copied to <code>destinationDirectory / x / y</code>.
* A source <code>(basePath ###) / x / y</code> is copied to <code>destinationDirectory / x / y</code>.
*
* @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 <code>sourceDirectory</code> directory to the
* <code>targetDirectory</code> 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

View File

@ -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"""))
}

View File

@ -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
}

View File

@ -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
{

View File

@ -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))

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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) }
}
}

View File

@ -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()

View File

@ -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) }

View File

@ -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 !!

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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',
*
* <code>((a / b) ##) / x / y</code>
* <code>((a / b) ###) / x / y</code>
*
* the copied path would be
*
* <code>d / x / y</code>
*
* The <code>relativePath</code> 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 <code>URL</code>.*/
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 <code>##</code> operator.*/
* default base directory if one is not specified explicitly using the <code>###</code> 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 <code>PathFinder</code> that selects the paths provided by the <code>paths</code> argument, which is
* reevaluated on each call to the <code>PathFinder</code>'s <code>get</code> 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 <code>get</code>
@ -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 <code>include</code> and do not have an intermediate
* path with a name that matches <code>intermediateExclude</code>. 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

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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*/