Merge branch '0.9' of github.com:harrah/xsbt into 0.9

This commit is contained in:
Mark Harrah 2010-06-10 21:34:13 -04:00
commit 1ee470282d
46 changed files with 846 additions and 655 deletions

10
cache/Cache.scala vendored
View File

@ -2,7 +2,6 @@ package xsbt
import sbinary.{CollectionTypes, Format, JavaFormats}
import java.io.File
import scala.reflect.Manifest
trait Cache[I,O]
{
@ -23,13 +22,6 @@ object Cache extends BasicCacheImplicits with SBinaryFormats with HListCacheImpl
def wrapOutputCache[O,DO](implicit convert: O => DO, reverse: DO => O, base: OutputCache[DO]): OutputCache[O] =
new WrappedOutputCache[O,DO](convert, reverse, base)
def apply[I,O](file: File)(f: I => Task[O])(implicit cache: Cache[I,O]): I => Task[O] =
in =>
cache(file)(in) match
{
case Left(value) => Task(value)
case Right(store) => f(in) map { out => store(out); out }
}
def cached[I,O](file: File)(f: I => O)(implicit cache: Cache[I,O]): I => O =
in =>
cache(file)(in) match
@ -61,4 +53,4 @@ trait HListCacheImplicits extends HLists
implicit def hConsOutputCache[H,T<:HList](implicit headCache: OutputCache[H], tailCache: OutputCache[T]): OutputCache[HCons[H,T]] =
new HConsOutputCache(headCache, tailCache)
implicit lazy val hNilOutputCache: OutputCache[HNil] = new HNilOutputCache
}
}

15
cache/FileInfo.scala vendored
View File

@ -18,8 +18,10 @@ sealed trait ModifiedFileInfo extends FileInfo
{
val lastModified: Long
}
sealed trait PlainFileInfo extends FileInfo
sealed trait HashModifiedFileInfo extends HashFileInfo with ModifiedFileInfo
private final case class PlainFile(file: File) extends PlainFileInfo
private final case class FileHash(file: File, hash: List[Byte]) extends HashFileInfo
private final case class FileModified(file: File, lastModified: Long) extends ModifiedFileInfo
private final case class FileHashModified(file: File, hash: List[Byte], lastModified: Long) extends HashModifiedFileInfo
@ -32,8 +34,6 @@ object FileInfo
implicit def apply(file: File): F
implicit def unapply(info: F): File = info.file
implicit val format: Format[F]
/*val manifest: Manifest[F]
def formatManifest: Manifest[Format[F]] = CacheIO.manifest[Format[F]]*/
import Cache._
implicit def infoInputCache: InputCache[File] = wrapInputCache[File,F]
implicit def infoOutputCache: OutputCache[File] = wrapOutputCache[File,F]
@ -41,7 +41,6 @@ object FileInfo
object full extends Style
{
type F = HashModifiedFileInfo
//val manifest: Manifest[F] = CacheIO.manifest[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 _))
@ -49,7 +48,6 @@ object FileInfo
object hash extends Style
{
type F = HashFileInfo
//val manifest: Manifest[F] = CacheIO.manifest[HashFileInfo]
implicit def apply(file: File): HashFileInfo = make(file, computeHash(file).toList)
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 _))
@ -58,11 +56,17 @@ object FileInfo
object lastModified extends Style
{
type F = ModifiedFileInfo
//val manifest: Manifest[F] = CacheIO.manifest[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 _))
}
object exists extends Style
{
type F = PlainFileInfo
implicit def apply(file: File): PlainFileInfo = make(file)
def make(file: File): PlainFileInfo = PlainFile(file.getAbsoluteFile)
implicit val format: Format[PlainFileInfo] = wrap(_.file, make)
}
}
final case class FilesInfo[F <: FileInfo] private(files: Set[F]) extends NotNull
@ -92,4 +96,5 @@ object FilesInfo
lazy val full: Style = new BasicStyle(FileInfo.full)
lazy val hash: Style = new BasicStyle(FileInfo.hash)
lazy val lastModified: Style = new BasicStyle(FileInfo.lastModified)
lazy val exists: Style = new BasicStyle(FileInfo.exists)
}

View File

@ -7,22 +7,23 @@ object CacheTest// extends Properties("Cache test")
val lengthCache = new File("/tmp/length-cache")
val cCache = new File("/tmp/c-cache")
import Task._
import Cache._
import FileInfo.hash._
def test
{
val createTask = Task { new File("test") }
lazy val create = new File("test")
val length = (f: File) => { println("File length: " + f.length); f.length }
val cachedLength = cached(lengthCache) ( length )
val length = cached(lengthCache) {
(f: File) => { println("File length: " + f.length); f.length }
}
val lengthTask = createTask map cachedLength
lazy val fileLength = length(create)
val c = (file: File, len: Long) => { println("File: " + file + ", length: " + len); len :: file :: HNil }
val cTask = (createTask :: lengthTask :: TNil) map cached(cCache) { case (file :: len :: HNil) => c(file, len) }
try { TaskRunner(cTask) }
catch { case TasksFailed(failures) => failures.foreach(_.exception.printStackTrace) }
val c = cached(cCache) { (in: (File :: Long :: HNil)) =>
val file :: len :: HNil = in
println("File: " + file + " (" + file.exists + "), length: " + len)
(len+1) :: file :: HNil
}
c(create :: fileLength :: HNil)
}
}

View File

@ -7,112 +7,152 @@ import java.io.{File,IOException}
import CacheIO.{fromFile, toFile}
import sbinary.Format
import scala.reflect.Manifest
import Task.{iterableToBuilder, iterableToForkBuilder}
import xsbt.FileUtilities.{delete, read, write}
/* A proper implementation of fileTask that tracks inputs and outputs properly
def fileTask(cacheBaseDirectory: Path)(inputs: PathFinder, outputs: PathFinder)(action: => Unit): Task =
fileTask(cacheBaseDirectory, FilesInfo.hash, FilesInfo.lastModified)
def fileTask(cacheBaseDirectory: Path, inStyle: FilesInfo.Style, outStyle: FilesInfo.Style)(inputs: PathFinder, outputs: PathFinder)(action: => Unit): Task =
{
lazy val inCache = diffInputs(base / "in-cache", inStyle)(inputs)
lazy val outCache = diffOutputs(base / "out-cache", outStyle)(outputs)
task
{
inCache { inReport =>
outCache { outReport =>
if(inReport.modified.isEmpty && outReport.modified.isEmpty) () else action
}
}
}
}
*/
object Tracked
{
/** Creates a tracker that provides the last time it was evaluated.
* 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)
/** 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)
/** Creates a tracker that provides the difference between the set of input files provided for successive invocations.*/
def diffInputs(cache: File, style: FilesInfo.Style)(files: => Set[File]): Difference =
Difference.inputs(files, style, cache)
/** Creates a tracker that provides the difference between the set of output files provided for successive invocations.*/
def diffOutputs(cache: File, style: FilesInfo.Style)(files: => Set[File]): Difference =
Difference.outputs(files, style, cache)
}
trait Tracked extends NotNull
{
/** Cleans outputs. This operation might require information from the cache, so it should be called first if clear is also called.*/
def clean: Task[Unit]
/** Clears the cache. If also cleaning, 'clean' should be called first as it might require information from the cache.*/
def clear: Task[Unit]
/** Cleans outputs and clears the cache.*/
def clean: Unit
}
class Timestamp(val cacheFile: File) extends Tracked
class Timestamp(val cacheFile: File, useStartTime: Boolean) extends Tracked
{
val clean = Clean(cacheFile)
def clear = Task.empty
def apply[T](f: Long => Task[T]): Task[T] =
def clean = delete(cacheFile)
/** Reads the previous timestamp, evaluates the provided function, and then updates the timestamp.*/
def apply[T](f: Long => T): T =
{
val getTimestamp = Task { readTimestamp }
getTimestamp bind f map { result =>
FileUtilities.write(cacheFile, System.currentTimeMillis.toString)
result
}
val start = now()
val result = f(readTimestamp)
write(cacheFile, (if(useStartTime) start else now()).toString)
result
}
private def now() = System.currentTimeMillis
def readTimestamp: Long =
try { FileUtilities.read(cacheFile).toLong }
try { read(cacheFile).toLong }
catch { case _: NumberFormatException | _: java.io.FileNotFoundException => 0 }
}
object Clean
{
def apply(src: Task[Set[File]]): Task[Unit] = src map FileUtilities.delete
def apply(srcs: File*): Task[Unit] = Task(FileUtilities.delete(srcs))
def apply(srcs: Set[File]): Task[Unit] = Task(FileUtilities.delete(srcs))
}
class Changed[O](val task: Task[O], val cacheFile: File)(implicit input: InputCache[O]) extends Tracked
class Changed[O](getValue: => O, val cacheFile: File)(implicit input: InputCache[O]) extends Tracked
{
val clean = Clean(cacheFile)
def clear = Task.empty
def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): Task[O2] =
task map { value =>
val cache =
try { OpenResource.fileInputStream(cacheFile)(input.uptodate(value)) }
catch { case _: IOException => new ForceResult(input)(value) }
if(cache.uptodate)
ifUnchanged(value)
else
{
OpenResource.fileOutputStream(false)(cacheFile)(cache.update)
ifChanged(value)
}
def clean = delete(cacheFile)
def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O2 =
{
val value = getValue
val cache =
try { OpenResource.fileInputStream(cacheFile)(input.uptodate(value)) }
catch { case _: IOException => new ForceResult(input)(value) }
if(cache.uptodate)
ifUnchanged(value)
else
{
OpenResource.fileOutputStream(false)(cacheFile)(cache.update)
ifChanged(value)
}
}
}
object Difference
{
sealed class Constructor private[Difference](defineClean: Boolean, filesAreOutputs: Boolean) extends NotNull
{
def apply(filesTask: Task[Set[File]], style: FilesInfo.Style, cache: File): Difference = new Difference(filesTask, style, cache, defineClean, filesAreOutputs)
def apply(files: Set[File], style: FilesInfo.Style, cache: File): Difference = apply(Task(files), style, cache)
def apply(files: => Set[File], style: FilesInfo.Style, cache: File): Difference = new Difference(files, style, cache, defineClean, filesAreOutputs)
}
/** Provides a constructor for a Difference that removes the files from the previous run on a call to 'clean' and saves the
* hash/last modified time of the files as they are after running the function. This means that this information must be evaluated twice:
* before and after running the function.*/
object outputs extends Constructor(true, true)
/** Provides a constructor for a Difference that does nothing on a call to 'clean' and saves the
* hash/last modified time of the files as they were prior to running the function.*/
object inputs extends Constructor(false, false)
}
class Difference(val filesTask: Task[Set[File]], val style: FilesInfo.Style, val cache: File, val defineClean: Boolean, val filesAreOutputs: Boolean) extends Tracked
class Difference(getFiles: => Set[File], val style: FilesInfo.Style, val cache: File, val defineClean: Boolean, val filesAreOutputs: Boolean) extends Tracked
{
val clean = if(defineClean) Clean(Task(raw(cachedFilesInfo))) else Task.empty
val clear = Clean(cache)
def clean =
{
if(defineClean) delete(raw(cachedFilesInfo)) else ()
clearCache()
}
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)
def apply[T](f: ChangeReport[File] => Task[T]): Task[T] =
filesTask bind { files =>
val lastFilesInfo = cachedFilesInfo
val lastFiles = raw(lastFilesInfo)
val currentFiles = files.map(_.getAbsoluteFile)
val currentFilesInfo = style(currentFiles)
def apply[T](f: ChangeReport[File] => T): T =
{
val files = getFiles
val lastFilesInfo = cachedFilesInfo
val lastFiles = raw(lastFilesInfo)
val currentFiles = files.map(_.getAbsoluteFile)
val currentFilesInfo = style(currentFiles)
val report = new ChangeReport[File]
{
lazy val checked = currentFiles
lazy val removed = lastFiles -- checked // all files that were included previously but not this time. This is independent of whether the files exist.
lazy val added = checked -- lastFiles // all files included now but not previously. This is independent of whether the files exist.
lazy val modified = raw(lastFilesInfo -- currentFilesInfo.files) ++ added
lazy val unmodified = checked -- modified
}
f(report) map { result =>
val info = if(filesAreOutputs) style(currentFiles) else currentFilesInfo
toFile(style.formats)(info)(cache)(style.manifest)
result
}
val report = new ChangeReport[File]
{
lazy val checked = currentFiles
lazy val removed = lastFiles -- checked // all files that were included previously but not this time. This is independent of whether the files exist.
lazy val added = checked -- lastFiles // all files included now but not previously. This is independent of whether the files exist.
lazy val modified = raw(lastFilesInfo -- currentFilesInfo.files) ++ added
lazy val unmodified = checked -- modified
}
val result = f(report)
val info = if(filesAreOutputs) style(currentFiles) else currentFilesInfo
toFile(style.formats)(info)(cache)(style.manifest)
result
}
}
class DependencyTracked[T](val cacheDirectory: File, val translateProducts: Boolean, cleanT: T => Unit)(implicit format: Format[T], mf: Manifest[T]) extends Tracked
{
private val trackFormat = new TrackingFormat[T](cacheDirectory, translateProducts)
private def cleanAll(fs: Set[T]) = fs.foreach(cleanT)
val clean = Task(cleanAll(trackFormat.read.allProducts))
val clear = Clean(cacheDirectory)
def clean =
{
cleanAll(trackFormat.read.allProducts)
delete(cacheDirectory)
}
def apply[R](f: UpdateTracking[T] => Task[R]): Task[R] =
def apply[R](f: UpdateTracking[T] => R): R =
{
val tracker = trackFormat.read
f(tracker) map { result =>
trackFormat.write(tracker)
result
}
val result = f(tracker)
trackFormat.write(tracker)
result
}
}
object InvalidateFiles
@ -179,30 +219,29 @@ class InvalidateTransitive[T](cacheDirectory: File, translateProducts: Boolean,
this(cacheDirectory, translateProducts, (_: T) => ())
private val tracked = new DependencyTracked(cacheDirectory, translateProducts, cleanT)
def clean = tracked.clean
def clear = tracked.clear
def apply[R](changes: ChangeReport[T])(f: (InvalidationReport[T], UpdateTracking[T]) => Task[R]): Task[R] =
apply(Task(changes))(f)
def apply[R](changesTask: Task[ChangeReport[T]])(f: (InvalidationReport[T], UpdateTracking[T]) => Task[R]): Task[R] =
def clean
{
changesTask bind { changes =>
tracked { tracker =>
val report = InvalidateTransitive.andClean[T](tracker, _.foreach(cleanT), changes.modified)
f(report, tracker)
}
tracked.clean
tracked.clear
}
def apply[R](getChanges: => ChangeReport[T])(f: (InvalidationReport[T], UpdateTracking[T]) => R): R =
{
val changes = getChanges
tracked { tracker =>
val report = InvalidateTransitive.andClean[T](tracker, _.foreach(cleanT), changes.modified)
f(report, tracker)
}
}
}
class BasicTracked(filesTask: Task[Set[File]], style: FilesInfo.Style, cacheDirectory: File) extends Tracked
class BasicTracked(files: => Set[File], style: FilesInfo.Style, cacheDirectory: File) extends Tracked
{
private val changed = Difference.inputs(filesTask, style, new File(cacheDirectory, "files"))
private val changed = Difference.inputs(files, style, new File(cacheDirectory, "files"))
private val invalidation = InvalidateFiles(new File(cacheDirectory, "invalidation"))
private def onTracked(f: Tracked => Task[Unit]) = Seq(invalidation, changed).forkTasks(f).joinIgnore
val clear = onTracked(_.clear)
val clean = onTracked(_.clean)
private def onTracked(f: Tracked => Unit) = { f(invalidation); f(changed) }
def clean = onTracked(_.clean)
def apply[R](f: (ChangeReport[File], InvalidationReport[File], UpdateTracking[File]) => Task[R]): Task[R] =
def apply[R](f: (ChangeReport[File], InvalidationReport[File], UpdateTracking[File]) => R): R =
changed { sourceChanges =>
invalidation(sourceChanges) { (report, tracking) =>
f(sourceChanges, report, tracking)

View File

@ -78,7 +78,7 @@ private class SameAPI(a: Source, b: Source, includePrivate: Boolean)
import SameAPI._
/** de Bruijn levels for type parameters in source `a`*/
private lazy val tagsA = TagTypeVariables(a)
/** de Bruijn levels for type parameters in source `a`*/
/** de Bruijn levels for type parameters in source `b`*/
private lazy val tagsB = TagTypeVariables(b)
def debug(flag: Boolean, msg: => String): Boolean =

View File

@ -35,7 +35,7 @@ class ConfigurationParser extends NotNull
val (properties, m6) = processSection(m5, "app-properties", getAppProperties)
val (cacheDir, m7) = processSection(m6, "ivy", getIvy)
check(m7, "section")
val classifiers = Classifiers("" :: scalaClassifiers, "" :: appClassifiers)
val classifiers = Classifiers("" :: scalaClassifiers, "" :: appClassifiers) // the added "" ensures that the main jars are retrieved
new LaunchConfiguration(scalaVersion, IvyOptions(cacheDir, classifiers, repositories), app, boot, logging, properties)
}
def getScala(m: LabelMap) =
@ -43,7 +43,7 @@ class ConfigurationParser extends NotNull
val (scalaVersion, m1) = getVersion(m, "Scala version", "scala.version")
val (scalaClassifiers, m2) = ids(m1, "classifiers", Nil)
check(m2, "label")
(scalaVersion, scalaClassifiers) // the added "" ensures that the main jars are retrieved
(scalaVersion, scalaClassifiers)
}
def getVersion(m: LabelMap, label: String, defaultName: String): (Version, LabelMap) = process(m, "version", processVersion(label, defaultName))
def processVersion(label: String, defaultName: String)(value: Option[String]): Version =

View File

@ -18,7 +18,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths
val classpathSub = baseProject(utilPath / "classpath", "Classpath")
val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub, launchInterfaceSub)
val logSub = baseProject(utilPath / "log", "Logging", interfaceSub)
val logSub = project(utilPath / "log", "Logging", new LogProject(_), interfaceSub)
val datatypeSub = baseProject("util" /"datatype", "Datatype Generator", ioSub)
val testSub = project("scripted", "Test", new TestProject(_), ioSub)
@ -26,17 +26,19 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths
val compileInterfaceSub = project(compilePath / "interface", "Compiler Interface", new CompilerInterfaceProject(_), interfaceSub)
val taskSub = project(tasksPath, "Tasks", new TaskProject(_), controlSub, collectionSub)
val cacheSub = project(cachePath, "Cache", new CacheProject(_), taskSub, ioSub)
val cacheSub = project(cachePath, "Cache", new CacheProject(_), ioSub, collectionSub)
val trackingSub = baseProject(cachePath / "tracking", "Tracking", cacheSub)
val compilerSub = project(compilePath, "Compile", new CompileProject(_),
launchInterfaceSub, interfaceSub, ivySub, ioSub, classpathSub, compileInterfaceSub)
val stdTaskSub = project(tasksPath / "standard", "Standard Tasks", new StandardTaskProject(_), trackingSub, compilerSub, apiSub)
val stdTaskSub = project(tasksPath / "standard", "Standard Tasks", new StandardTaskProject(_), trackingSub, taskSub, compilerSub, apiSub)
val altCompilerSub = baseProject("main", "Alternate Compiler Test", stdTaskSub, logSub)
val sbtSub = project(sbtPath, "Simple Build Tool", new SbtProject(_) {}, compilerSub, launchInterfaceSub)
val installerSub = project(sbtPath / "install", "Installer", new InstallerProject(_) {}, sbtSub)
lazy val dist = task { None } dependsOn(launchSub.proguard, sbtSub.publishLocal, installerSub.publishLocal)
def baseProject(path: Path, name: String, deps: Project*) = project(path, name, new Base(_), deps : _*)
/* Multi-subproject paths */
@ -90,6 +92,10 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths
{
override def testClasspath = super.testClasspath +++ compilerSub.testClasspath --- compilerInterfaceClasspath
}
class LogProject(info: ProjectInfo) extends Base(info)
{
val jline = jlineDep
}
class IOProject(info: ProjectInfo) extends Base(info) with TestDependencies
class TaskProject(info: ProjectInfo) extends Base(info) with TestDependencies

View File

@ -322,8 +322,6 @@ trait BasicManagedProject extends ManagedProject with ReflectiveManagedProject w
def deliverIvyModule = newIvyModule(deliverModuleSettings)
def publishModuleSettings = deliverModuleSettings
def publishIvyModule = newIvyModule(publishModuleSettings)
/** True if the 'provided' configuration should be included on the 'compile' classpath. The default value is true.*/
def includeProvidedWithCompile = true
/** True if the default implicit extensions should be used when determining classpaths. The default value is true. */
def defaultConfigurationExtensions = true
/** If true, verify that explicit dependencies on Scala libraries use the same version as scala.version. */
@ -343,22 +341,15 @@ trait BasicManagedProject extends ManagedProject with ReflectiveManagedProject w
case _ => None
}
}
/** Includes the Provided configuration on the Compile classpath, the Compile configuration on the Runtime classpath,
* and Compile and Runtime on the Test classpath. Including Provided can be disabled by setting
* includeProvidedWithCompile to false. Including Compile and Runtime can be disabled by setting
* defaultConfigurationExtensions to false.*/
/** Includes the Compile configuration on the Runtime classpath, and Compile and Runtime on the Test classpath.
* Including Compile and Runtime can be disabled by setting defaultConfigurationExtensions to false.*/
override def managedClasspath(config: Configuration) =
{
import Configurations.{Compile, CompilerPlugin, Default, Provided, Runtime, Test}
import Configurations.{Compile, Default, Runtime, Test}
val baseClasspath = configurationClasspath(config)
config match
{
case Compile =>
val baseCompileClasspath = baseClasspath +++ managedClasspath(Default)
if(includeProvidedWithCompile)
baseCompileClasspath +++ managedClasspath(Provided)
else
baseCompileClasspath
case Compile => baseClasspath +++ managedClasspath(Default)
case Runtime if defaultConfigurationExtensions => baseClasspath +++ managedClasspath(Compile)
case Test if defaultConfigurationExtensions => baseClasspath +++ managedClasspath(Runtime)
case _ => baseClasspath

View File

@ -5,140 +5,8 @@ package sbt
import scala.collection.mutable.{Buffer, HashMap, ListBuffer}
sealed trait LogEvent extends NotNull
final class Success(val msg: String) extends LogEvent
final class Log(val level: Level.Value, val msg: String) extends LogEvent
final class Trace(val exception: Throwable) extends LogEvent
final class SetLevel(val newLevel: Level.Value) extends LogEvent
final class SetTrace(val level: Int) extends LogEvent
final class ControlEvent(val event: ControlEvent.Value, val msg: String) extends LogEvent
object ControlEvent extends Enumeration
{
val Start, Header, Finish = Value
}
abstract class Logger extends xsbt.CompileLogger with IvyLogger
{
def getLevel: Level.Value
def setLevel(newLevel: Level.Value)
def setTrace(flag: Int)
def getTrace: Int
final def traceEnabled = getTrace >= 0
def ansiCodesSupported = false
def atLevel(level: Level.Value) = level.id >= getLevel.id
def trace(t: => Throwable): Unit
final def verbose(message: => String): Unit = debug(message)
final def debug(message: => String): Unit = log(Level.Debug, message)
final def info(message: => String): Unit = log(Level.Info, message)
final def warn(message: => String): Unit = log(Level.Warn, message)
final def error(message: => String): Unit = log(Level.Error, message)
def success(message: => String): Unit
def log(level: Level.Value, message: => String): Unit
def control(event: ControlEvent.Value, message: => String): Unit
def logAll(events: Seq[LogEvent]): Unit
/** Defined in terms of other methods in Logger and should not be called from them. */
final def log(event: LogEvent)
{
event match
{
case s: Success => success(s.msg)
case l: Log => log(l.level, l.msg)
case t: Trace => trace(t.exception)
case setL: SetLevel => setLevel(setL.newLevel)
case setT: SetTrace => setTrace(setT.level)
case c: ControlEvent => control(c.event, c.msg)
}
}
import xsbti.F0
def debug(msg: F0[String]): Unit = log(Level.Debug, msg)
def warn(msg: F0[String]): Unit = log(Level.Warn, msg)
def info(msg: F0[String]): Unit = log(Level.Info, msg)
def error(msg: F0[String]): Unit = log(Level.Error, msg)
def trace(msg: F0[Throwable]) = trace(msg.apply)
def log(level: Level.Value, msg: F0[String]): Unit = log(level, msg.apply)
}
/** Implements the level-setting methods of Logger.*/
abstract class BasicLogger extends Logger
{
private var traceEnabledVar = java.lang.Integer.MAX_VALUE
private var level: Level.Value = Level.Info
def getLevel = level
def setLevel(newLevel: Level.Value) { level = newLevel }
def setTrace(level: Int) { traceEnabledVar = level }
def getTrace = traceEnabledVar
}
final class SynchronizedLogger(delegate: Logger) extends Logger
{
override lazy val ansiCodesSupported = delegate.ansiCodesSupported
def getLevel = { synchronized { delegate.getLevel } }
def setLevel(newLevel: Level.Value) { synchronized { delegate.setLevel(newLevel) } }
def setTrace(level: Int) { synchronized { delegate.setTrace(level) } }
def getTrace: Int = { synchronized { delegate.getTrace } }
def trace(t: => Throwable) { synchronized { delegate.trace(t) } }
def log(level: Level.Value, message: => String) { synchronized { delegate.log(level, message) } }
def success(message: => String) { synchronized { delegate.success(message) } }
def control(event: ControlEvent.Value, message: => String) { synchronized { delegate.control(event, message) } }
def logAll(events: Seq[LogEvent]) { synchronized { delegate.logAll(events) } }
}
final class MultiLogger(delegates: List[Logger]) extends BasicLogger
{
override lazy val ansiCodesSupported = delegates.forall(_.ansiCodesSupported)
override def setLevel(newLevel: Level.Value)
{
super.setLevel(newLevel)
dispatch(new SetLevel(newLevel))
}
override def setTrace(level: Int)
{
super.setTrace(level)
dispatch(new SetTrace(level))
}
def trace(t: => Throwable) { dispatch(new Trace(t)) }
def log(level: Level.Value, message: => String) { dispatch(new Log(level, message)) }
def success(message: => String) { dispatch(new Success(message)) }
def logAll(events: Seq[LogEvent]) { delegates.foreach(_.logAll(events)) }
def control(event: ControlEvent.Value, message: => String) { delegates.foreach(_.control(event, message)) }
private def dispatch(event: LogEvent) { delegates.foreach(_.log(event)) }
}
/** A filter logger is used to delegate messages but not the logging level to another logger. This means
* that messages are logged at the higher of the two levels set by this logger and its delegate.
* */
final class FilterLogger(delegate: Logger) extends BasicLogger
{
override lazy val ansiCodesSupported = delegate.ansiCodesSupported
def trace(t: => Throwable)
{
if(traceEnabled)
delegate.trace(t)
}
override def setTrace(level: Int) { delegate.setTrace(level) }
override def getTrace = delegate.getTrace
def log(level: Level.Value, message: => String)
{
if(atLevel(level))
delegate.log(level, message)
}
def success(message: => String)
{
if(atLevel(Level.Info))
delegate.success(message)
}
def control(event: ControlEvent.Value, message: => String)
{
if(atLevel(Level.Info))
delegate.control(event, message)
}
def logAll(events: Seq[LogEvent]): Unit = delegate.logAll(events)
}
trait Logger extends AbstractLogger with xsbt.CompileLogger with IvyLogger
/** A logger that can buffer the logging done on it by currently executing Thread and
* then can flush the buffer to the delegate logger provided in the constructor. Use
@ -151,7 +19,7 @@ final class FilterLogger(delegate: Logger) extends BasicLogger
*
* This logger is thread-safe.
* */
final class BufferedLogger(delegate: Logger) extends Logger
final class BufferedLogger(delegate: AbstractLogger) extends Logger
{
override lazy val ansiCodesSupported = delegate.ansiCodesSupported
private[this] val buffers = wrap.Wrappers.weakMap[Thread, Buffer[LogEvent]]
@ -244,9 +112,9 @@ final class BufferedLogger(delegate: Logger) extends Logger
}
def control(event: ControlEvent.Value, message: => String): Unit =
doBufferable(Level.Info, new ControlEvent(event, message), _.control(event, message))
private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: Logger => Unit): Unit =
private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit =
doBufferableIf(atLevel(level), appendIfBuffered, doUnbuffered)
private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: Logger => Unit): Unit =
private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit =
synchronized
{
if(condition)
@ -259,134 +127,3 @@ final class BufferedLogger(delegate: Logger) extends Logger
}
}
}
object ConsoleLogger
{
private val formatEnabled = ansiSupported && !formatExplicitlyDisabled
private[this] def formatExplicitlyDisabled = java.lang.Boolean.getBoolean("sbt.log.noformat")
private[this] def ansiSupported =
try { jline.Terminal.getTerminal.isANSISupported }
catch { case e: Exception => !isWindows }
private[this] def os = System.getProperty("os.name")
private[this] def isWindows = os.toLowerCase.indexOf("windows") >= 0
}
/** A logger that logs to the console. On supported systems, the level labels are
* colored.
*
* This logger is not thread-safe.*/
class ConsoleLogger extends BasicLogger
{
override def ansiCodesSupported = ConsoleLogger.formatEnabled
def messageColor(level: Level.Value) = Console.RESET
def labelColor(level: Level.Value) =
level match
{
case Level.Error => Console.RED
case Level.Warn => Console.YELLOW
case _ => Console.RESET
}
def successLabelColor = Console.GREEN
def successMessageColor = Console.RESET
override def success(message: => String)
{
if(atLevel(Level.Info))
log(successLabelColor, Level.SuccessLabel, successMessageColor, message)
}
def trace(t: => Throwable): Unit =
System.out.synchronized
{
val traceLevel = getTrace
if(traceLevel >= 0)
System.out.synchronized { System.out.print(StackTrace.trimmed(t, traceLevel)) }
}
def log(level: Level.Value, message: => String)
{
if(atLevel(level))
log(labelColor(level), level.toString, messageColor(level), message)
}
private def setColor(color: String)
{
if(ansiCodesSupported)
System.out.synchronized { System.out.print(color) }
}
private def log(labelColor: String, label: String, messageColor: String, message: String): Unit =
System.out.synchronized
{
for(line <- message.split("""\n"""))
{
setColor(Console.RESET)
System.out.print('[')
setColor(labelColor)
System.out.print(label)
setColor(Console.RESET)
System.out.print("] ")
setColor(messageColor)
System.out.print(line)
setColor(Console.RESET)
System.out.println()
}
}
def logAll(events: Seq[LogEvent]) = System.out.synchronized { events.foreach(log) }
def control(event: ControlEvent.Value, message: => String)
{ log(labelColor(Level.Info), Level.Info.toString, Console.BLUE, message) }
}
/** An enumeration defining the levels available for logging. A level includes all of the levels
* with id larger than its own id. For example, Warn (id=3) includes Error (id=4).*/
object Level extends Enumeration with NotNull
{
val Debug = Value(1, "debug")
val Info = Value(2, "info")
val Warn = Value(3, "warn")
val Error = Value(4, "error")
/** Defines the label to use for success messages. A success message is logged at the info level but
* uses this label. Because the label for levels is defined in this module, the success
* label is also defined here. */
val SuccessLabel = "success"
// added because elements was renamed to iterator in 2.8.0 nightly
def levels = Debug :: Info :: Warn :: Error :: Nil
/** Returns the level with the given name wrapped in Some, or None if no level exists for that name. */
def apply(s: String) = levels.find(s == _.toString)
/** Same as apply, defined for use in pattern matching. */
private[sbt] def unapply(s: String) = apply(s)
}
/** Provides a `java.io.Writer` interface to a `Logger`. Content is line-buffered and logged at `level`.
* A line is delimited by `nl`, which is by default the platform line separator.*/
final class LoggerWriter(delegate: Logger, level: Level.Value, nl: String) extends java.io.Writer
{
def this(delegate: Logger, level: Level.Value) = this(delegate, level, FileUtilities.Newline)
private[this] val buffer = new StringBuilder
override def close() = flush()
override def flush(): Unit =
synchronized {
if(buffer.length > 0)
{
log(buffer.toString)
buffer.clear()
}
}
override def write(content: Array[Char], offset: Int, length: Int): Unit =
synchronized {
buffer.append(content, offset, length)
process()
}
private[this] def process()
{
val i = buffer.indexOf(nl)
if(i >= 0)
{
log(buffer.substring(0, i))
buffer.delete(0, i + nl.length)
process()
}
}
private[this] def log(s: String): Unit = delegate.log(level, s)
}

View File

@ -416,7 +416,7 @@ class xMain extends xsbti.AppMain
val ContinuousCompilePollDelaySeconds = 1
/** The list of logging levels.*/
private def logLevels: Iterable[String] = TreeSet.empty[String] ++ Level.levels.map(_.toString)
private def logLevels: Iterable[String] = TreeSet.empty[String] ++ Level.values.map(_.toString)
/** The list of all interactive commands other than logging level.*/
private def basicCommands: Iterable[String] = TreeSet(ShowProjectsAction, ShowActions, ShowCurrent, HelpAction,
RebootCommand, TraceCommand, ContinuousCompileCommand, ProjectConsoleAction, BuilderCommand) ++
@ -464,7 +464,7 @@ class xMain extends xsbti.AppMain
printCmd(RebootCommand, "Reloads sbt, picking up modifications to sbt.version or scala.version and recompiling modified project definitions.")
printCmd(HelpAction, "Displays this help message.")
printCmd(ShowCurrent, "Shows the current project, Scala version, and logging level.")
printCmd(Level.levels.mkString(", "), "Set logging for the current project to the specified level.")
printCmd(Level.values.mkString(", "), "Set logging for the current project to the specified level.")
printCmd(TraceCommand + " " + validTraceArguments, "Configures stack trace logging. " + traceExplanation)
printCmd(ProjectAction + " <project name>", "Sets the currently active project.")
printCmd(ShowProjectsAction, "Shows all available projects.")

View File

@ -0,0 +1,6 @@
import sbinary._
trait A
{
def format: Format[A]
}

View File

@ -0,0 +1,6 @@
import sbinary._
trait B
{
def format(a: A): Format[A]
}

View File

@ -0,0 +1,19 @@
import sbt._
class P(info: ProjectInfo) extends ParentProject(info)
{
val a = project("a", "A", new A(_))
val b = project("b", "B", new B(_), a)
def aLibrary = "org.scala-tools.sbinary" %% "sbinary" % "0.3" % "provided"
class A(info: ProjectInfo) extends DefaultProject(info)
{
val a = aLibrary
}
class B(info: ProjectInfo) extends DefaultWebProject(info)
{
override def libraryDependencies =
if("declare.lib".asFile.exists) Set(aLibrary) else Set()
}
}

View File

@ -0,0 +1,2 @@
project.name=Multi Project Provided
project.version=1.0

View File

@ -0,0 +1,21 @@
> set build.scala.versions 2.7.7
$ copy-file changes/P.scala project/build/P.scala
$ copy-file changes/A.scala a/src/main/scala/A.scala
$ copy-file changes/B.scala b/src/main/scala/B.scala
> reload
> project A
-> compile
> update
> compile
> project B
-> compile
> update
-> compile
$ touch b/declare.lib
> reload
-> compile
> update
> compile

View File

@ -93,7 +93,7 @@ object LogWriterTest extends Properties("Log Writer")
for(ls <- arbList[List[ToLog]].arbitrary; lv <- genLevel) yield
new Output(ls, lv)
def levelsGen: Seq[Gen[Level.Value]] = Level.elements.toList.map(x => value(x))
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 =

View File

@ -3,9 +3,7 @@
*/
package sbt
// TODO: Incomplete needs to be parameterized with A[_] and have val node
import Node._
import ErrorHandling.wideConvert
import Types._
import Execute._
@ -87,13 +85,21 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: A ~> No
readyInv( node )
}
state(node) = Calling
addChecked(target)
addCaller(node, target)
results.get(target) match {
case Some(result) => retire(node, result)
case None =>
state(node) = Calling
addChecked(target)
addCaller(node, target)
}
post {
assert( calling(node) )
assert( callers(target) contains node )
if(done(target))
assert(done(node))
else {
assert(calling(node) )
assert( callers(target) contains node )
}
readyInv( node )
}
}
@ -200,20 +206,18 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: A ~> No
def submit[T]( node: A[T] )(implicit strategy: Strategy)
{
val v = view(node)
val rs: v.Inputs#Map[Result] = v.inputs.map(results)
val ud = v.unitDependencies.flatMap(incomplete)
val rs: v.Mixed#Map[Result] = v.mixedIn.map(results)
val ud = v.uniformIn.map(results.apply[v.Uniform])
strategy.submit( node, () => work(node, v.work(rs, ud)) )
}
/** Evaluates the computation 'f' for 'node'.
* This returns a Completed instance, which contains the post-processing to perform after the result is retrieved from the Strategy.*/
def work[T](node: A[T], f: => Either[A[T], T])(implicit strategy: Strategy): Completed =
{
val result =
try { Right(f) }
catch {
case i: Incomplete => Left(i)
case e => Left( Incomplete(Incomplete.Error, directCause = Some(e)) )
}
val result = wideConvert(f).left.map {
case i: Incomplete => i
case e => Incomplete(Incomplete.Error, directCause = Some(e))
}
completed {
result match {
case Left(i) => retire(node, Inc(i))
@ -229,7 +233,7 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: A ~> No
def addCaller[T](caller: A[T], target: A[T]): Unit = callers.getOrUpdate(target, IDSet.create[A[T]]) += caller
def dependencies(node: A[_]): Iterable[A[_]] = dependencies(view(node))
def dependencies(v: Node[A, _]): Iterable[A[_]] = v.unitDependencies ++ v.inputs.toList
def dependencies(v: Node[A, _]): Iterable[A[_]] = v.uniformIn ++ v.mixedIn.toList
// Contracts

View File

@ -9,4 +9,24 @@ final case class Incomplete(tpe: IValue = Error, message: Option[String] = None,
object Incomplete extends Enumeration {
val Skipped, Error = Value
def show(i: Incomplete, traces: Boolean): String =
{
val exceptions = allExceptions(i)
val traces = exceptions.map(_.getStackTrace).mkString("\n")
val causeStr = if(i.causes.isEmpty) "" else (i.causes.length + " cause(s)")
"Incomplete (" + show(i.tpe) + ") " + i.message.getOrElse("") + causeStr + "\n" + traces
}
def allExceptions(i: Incomplete): Iterable[Throwable] =
{
val exceptions = IDSet.create[Throwable]
val visited = IDSet.create[Incomplete]
def visit(inc: Incomplete): Unit =
visited.process(inc)( () ) {
exceptions ++= inc.directCause.toList
inc.causes.foreach(visit)
}
visit(i)
exceptions.all
}
def show(tpe: Value) = tpe match { case Skipped=> "skipped"; case Error => "error" }
}

View File

@ -3,33 +3,16 @@
*/
package sbt
import Node._
import Types._
trait Node[A[_], T]
{
type Inputs <: MList[A]
type Results = Inputs#Map[Result]
type Mixed <: MList[A]
type MixedResults = Mixed#Map[Result]
type Uniform
val inputs: Inputs
def unitDependencies: Iterable[A[_]]
val mixedIn: Mixed
val uniformIn: Seq[A[Uniform]]
def work(results: Results, units: UnitResults[A]): Either[A[T], T]
}
object Node
{
/*def pure[T](f: () => T): PureNode[T]= map[Id, T, MNil](MNil, Nil)((_,_) => f() )
def map[A[_], T, Inputs0 <: MList[A]](inputs0: Inputs0, deps0: Iterable[A[_]])(work0: (Inputs0#Map[Result], UnitResults[A]) => T):
Node[A,T] { type Inputs = Inputs0 } =
new Node[A,T] {
type Inputs = Inputs0
val inputs = inputs0
def unitDependencies = deps0
def work(results: Results, units: UnitResults[A]) = Right(work0(results, units))
}
type PureNode[T] = Node[Id, T] { type Inputs = MNil; type Results = MNil }*/
type UnitResults[A[_]] = Iterable[(A[_], Incomplete)]
def work(mixed: MixedResults, uniform: Seq[Result[Uniform]]): Either[A[T], T]
}

View File

@ -1,114 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
import Types._
import Node._
import Task._
import Execute._
sealed trait Task[+T]
sealed case class Pure[+T](eval: () => T) extends Task[T]
sealed case class Mapped[+T, In <: MList[Task]](in: In, f: In#Map[Result] => T) extends Task[T]
sealed case class MapAll[+T, In <: MList[Task]](in: In, f: In#Map[Result]#Raw => T) extends Task[T]
sealed case class FlatMapAll[+T, In <: MList[Task]](in: In, f: In#Map[Result]#Raw => Task[T]) extends Task[T]
sealed case class FlatMapped[+T, In <: MList[Task]](in: In, f: In#Map[Result] => Task[T]) extends Task[T]
object Task
{
implicit val taskToNode = new (Task ~> NodeT[Task]#Apply) {
def apply[T](t: Task[T]): Node[Task, T] = t match {
case Pure(eval) => toNode[T, MNil](MNil, _ => Right(eval()) )
case Mapped(in, f) => toNode[T, in.type](in, right f )
case MapAll(in, f) => toNode[T, in.type](in, right (f compose all) )
case FlatMapAll(in, f) => toNode[T, in.type](in, left (f compose all) )
case FlatMapped(in, f) => toNode[T, in.type](in, left f )
}
}
def toNode[T, In <: MList[Task]](in: In, f: In#Map[Result] => Either[Task[T], T]): Node[Task, T] = new Node[Task, T] {
type Inputs = In
val inputs = in
def unitDependencies = Nil
def work(results: Results, units: UnitResults[Task]) = f(results)
}
def pure[T](name: String)(f: => T): Pure[T] = new Pure(f _) { override def toString = name }
def mapped[T, In0 <: MList[Task]](name: String)(in0: In0)(f0: In0#Map[Result] => T): Mapped[T, In0] = new Mapped(in0, f0) { override def toString = name }
def flat[T, In0 <: MList[Task]](name: String)(in0: In0)(f0: In0#Map[Result] => Task[T]): FlatMapped[T, In0] = new FlatMapped(in0, f0) { override def toString = name }
def mapAll[T, In0 <: MList[Task]](name: String)(in0: In0)(f0: In0#Map[Result]#Raw => T): MapAll[T, In0] = new MapAll(in0, f0) { override def toString = name }
def flatAll[T, In0 <: MList[Task]](name: String)(in0: In0)(f0: In0#Map[Result]#Raw => Task[T]): FlatMapAll[T, In0] = new FlatMapAll(in0, f0) { override def toString = name }
def all[In <: MList[Result]]: In => In#Raw = in =>
{
val incs = in.toList.collect { case Inc(i) => i }
if(incs.isEmpty) in.down(Result.tryValue) else throw Incomplete(causes = incs)
}
}
object Test
{
val a = pure("a")(3)
val b = pure[Boolean]("b")(error("test"))
val b2 = pure("b2")(true)
val c = pure("x")("asdf")
val i3 = a :^: b :^: c :^: MNil
val i32 = a :^: b2 :^: c :^: MNil
val fh= (_: Int :+: Boolean :+: String :+: HNil) match
{ case aa :+: bb :+: cc :+: HNil => aa + " " + bb + " " + cc }
val h1 = mapAll("h1")(i3)(fh)
val h2 = mapAll("h2")(i32)(fh)
val f: i3.Map[Result] => Any = {
case Value(aa) :^: Value(bb) :^: Value(cc) :^: MNil => aa + " " + bb + " " + cc
case x =>
val cs = x.toList.collect { case Inc(x) => x } // workaround for double definition bug
throw Incomplete(causes = cs)
}
val d2 = mapped("d2")(i32)(f)
val f2: i3.Map[Result] => Task[Any] = {
case Value(aa) :^: Value(bb) :^: Value(cc) :^: MNil => new Pure(() => aa + " " + bb + " " + cc)
case x => d3
}
lazy val d = flat("d")(i3)(f2)
val f3: i3.Map[Result] => Task[Any] = {
case Value(aa) :^: Value(bb) :^: Value(cc) :^: MNil => new Pure(() => aa + " " + bb + " " + cc)
case x => d2
}
lazy val d3= flat("d3")(i3)(f3)
def d4(i: Int): Task[Int] = flat("d4")(MNil){ _ => val x = math.random; if(x < 0.01) pure(x.toString)(i); else d4(i+1) }
lazy val pureEval =
new (Pure ~> Result) {
def apply[T](p: Pure[T]): Result[T] =
try { Value(p.eval()) }
catch { case e: Exception => throw Incomplete(Incomplete.Error, directCause = Some(e)) }
}
lazy val resultA = d.f( d.in.map(pureEval) )
def execute[T](root: Task[T]) = {
val (service, shutdown) = CompletionService[Task[_], Completed](2)
implicit val wrapped = CompletionService.manage(service)(x => println("Starting: " + x), x => println("Finished: " + x) )
val x = new Execute[Task](true)(taskToNode)
try { x.run(root) } finally { shutdown(); println(x.dump) }
}
def go()
{
def run[T](root: Task[T]) =
println("Result : " + execute(root))
run(a)
run(b)
run(b2)
run(c)
run(d)
run(d2)
run( d4(0) )
run(h1)
run(h2)
}
}

View File

@ -0,0 +1,159 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
import Types._
import Task._
import Execute._
sealed trait Task[+T]
sealed case class Pure[+T](eval: () => T) extends Task[T]
final case class Mapped[+T, In <: MList[Task]](in: In, f: In#Map[Result] => T) extends Task[T]
final case class MapAll[+T, In <: MList[Task]](in: In, f: In#Map[Result]#Raw => T) extends Task[T]
final case class FlatMapAll[+T, In <: MList[Task]](in: In, f: In#Map[Result]#Raw => Task[T]) extends Task[T]
final case class MapFailure[+T, In <: MList[Task]](in: In, f: Seq[Incomplete] => T) extends Task[T]
final case class FlatMapFailure[+T, In <: MList[Task]](in: In, f: Seq[Incomplete] => Task[T]) extends Task[T]
final case class FlatMapped[+T, In <: MList[Task]](in: In, f: In#Map[Result] => Task[T]) extends Task[T]
final case class DependsOn[+T](in: Task[T], deps: Seq[Task[_]]) extends Task[T]
final case class Join[+T, U](in: Seq[Task[U]], f: Seq[U] => Either[Task[T], T]) extends Task[T] { type Uniform = U }
trait MultiInTask[M <: MList[Task]]
{
def flatMap[T](f: M#Map[Result]#Raw => Task[T]): Task[T]
def flatMapR[T](f: M#Map[Result] => Task[T]): Task[T]
def mapH[T](f: M#Map[Result]#Raw => T): Task[T]
def mapR[T](f: M#Map[Result] => T): Task[T]
def flatFailure[T](f: Seq[Incomplete] => Task[T]): Task[T]
def mapFailure[T](f: Seq[Incomplete] => T): Task[T]
}
trait SingleInTask[S]
{
def flatMapR[T](f: Result[S] => Task[T]): Task[T]
def flatMap[T](f: S => Task[T]): Task[T]
def map[T](f: S => T): Task[T]
def mapR[T](f: Result[S] => T): Task[T]
def flatFailure[T](f: Incomplete => Task[T]): Task[T]
def mapFailure[T](f: Incomplete => T): Task[T]
def dependsOn(tasks: Task[_]*): Task[S]
}
trait ForkTask[S, CC[_]]
{
def fork[T](f: S => T): CC[Task[T]]
}
trait JoinTask[S, CC[_]]
{
def join: Task[CC[S]]
def reduce(f: (S,S) => S): Task[S]
}
object Task
{
def pure[T](f: => T): Task[T] = toPure(f _)
def pure[T](name: String, f: => T): Task[T] = new Pure(f _) { override def toString = name }
implicit def toPure[T](f: () => T): Task[T] = new Pure(f)
implicit def toTasks[S](in: Seq[S]): Seq[Task[S]] = in.map(s => pure(s))
implicit def toTasks[S](in: Seq[() => S]): Seq[Task[S]] = in.map(toPure)
implicit def iterableTask[S](in: Seq[S]): ForkTask[S, Seq] = new ForkTask[S, Seq] {
def fork[T](f: S => T): Seq[Task[T]] = in.map(x => pure(x) map f)
}
implicit def joinTasks[S](in: Seq[S]): JoinTask[S, Seq] = joinTasks(toTasks(in))
implicit def joinTasks[S](in: Seq[Task[S]]): JoinTask[S, Seq] = new JoinTask[S, Seq] {
def join: Task[Seq[S]] = new Join(in, (s: Seq[S]) => Right(s) )
//def join[T](f: Iterable[S] => T): Task[Iterable[T]] = new MapAll( MList.fromTCList[Task](in), ml => f(ml.toList))
//def joinR[T](f: Iterable[Result[S]] => T): Task[Iterable[Result[T]]] = new Mapped( MList.fromTCList[Task](in), ml => f(ml.toList))
def reduce(f: (S,S) => S): Task[S] = Task.reduce(in.toIndexedSeq, f)
}
implicit def multInputTask[M <: MList[Task]](ml: M): MultiInTask[M] = new MultiInTask[M] {
def flatMap[T](f: M#Map[Result]#Raw => Task[T]): Task[T] = new FlatMapAll(ml, f)
def flatMapR[T](f: M#Map[Result] => Task[T]): Task[T] = new FlatMapped(ml, f)
def mapH[T](f: M#Map[Result]#Raw => T): Task[T] = new MapAll(ml, f)
def mapR[T](f: M#Map[Result] => T): Task[T] = new Mapped(ml, f)
def flatFailure[T](f: Seq[Incomplete] => Task[T]): Task[T] = new FlatMapFailure(ml, f)
def mapFailure[T](f: Seq[Incomplete] => T): Task[T] = new MapFailure(ml, f)
}
implicit def singleInputTask[S](in: Task[S]): SingleInTask[S] = new SingleInTask[S] {
private val ml = in :^: MNil
private def headM = (_: ml.Map[Result]).head
private def headH = (_: S :+: HNil).head
private def headS = (_: Seq[Incomplete]).head
def flatMapR[T](f: Result[S] => Task[T]): Task[T] = new FlatMapped[T, ml.type](ml, f headM)
def flatMap[T](f: S => Task[T]): Task[T] = new FlatMapAll[T, ml.type](ml, f headH)
def map[T](f: S => T): Task[T] = new MapAll[T, ml.type](ml, f headH)
def mapR[T](f: Result[S] => T): Task[T] = new Mapped[T, ml.type](ml, f headM)
def flatFailure[T](f: Incomplete => Task[T]): Task[T] = new FlatMapFailure(ml, f headS)
def mapFailure[T](f: Incomplete => T): Task[T] = new MapFailure(ml, f headS)
def dependsOn(tasks: Task[_]*): Task[S] = new DependsOn(in, tasks)
}
implicit val taskToNode = new (Task ~> NodeT[Task]#Apply) {
def apply[T](t: Task[T]): Node[Task, T] = t match {
case Pure(eval) => toNode[T, MNil](MNil, _ => Right(eval()) )
case Mapped(in, f) => toNode[T, in.type](in, right f )
case MapAll(in, f) => toNode[T, in.type](in, right (f compose allM) )
case MapFailure(in, f) => toNode[T, in.type](in, right (f compose failuresM))
case FlatMapped(in, f) => toNode[T, in.type](in, left f )
case FlatMapAll(in, f) => toNode[T, in.type](in, left (f compose allM) )
case FlatMapFailure(in, f) => toNode[T, in.type](in, left (f compose failuresM))
case DependsOn(in, tasks) => join[T, Any](tasks, (_: Seq[Result[_]]) => Left(in))
case j@ Join(in, f) => join[T, j.Uniform](in, f compose all)
}
}
def join[T, D](tasks: Seq[Task[D]], f: Seq[Result[D]] => Either[Task[T], T]): Node[Task, T] = new Node[Task, T] {
type Mixed = MNil
val mixedIn = MNil
type Uniform = D
val uniformIn = tasks
def work(mixed: MNil, uniform: Seq[Result[Uniform]]) = {
val inc = failures(uniform)
if(inc.isEmpty) f(uniform) else throw Incomplete(causes = inc)
}
}
def toNode[T, In <: MList[Task]](in: In, f: In#Map[Result] => Either[Task[T], T]): Node[Task, T] = new Node[Task, T] {
type Mixed = In
val mixedIn = in
type Uniform = Nothing
val uniformIn = Nil
def work(results: Mixed#Map[Result], units: Seq[Result[Uniform]]) = f(results)
}
def allM[In <: MList[Result]]: In => In#Raw = in =>
{
val incs = failuresM(in)
if(incs.isEmpty) in.down(Result.tryValue) else throw Incomplete(causes = incs)
}
def all[D]: Seq[Result[D]] => Seq[D] = in =>
{
val incs = failures(in)
if(incs.isEmpty) in.map(Result.tryValue.apply[D]) else throw Incomplete(causes = incs)
}
def failuresM[In <: MList[Result]]: In => Seq[Incomplete] = x => failures[Any](x.toList)
def failures[A]: Seq[Result[A]] => Seq[Incomplete] = _.collect { case Inc(i) => i }
def run[T](root: Task[T], checkCycles: Boolean, maxWorkers: Int): Result[T] =
{
val (service, shutdown) = CompletionService[Task[_], Completed](maxWorkers)
val x = new Execute[Task](checkCycles)(taskToNode)
try { x.run(root)(service) } finally { shutdown() }
}
def tryRun[T](root: Task[T], checkCycles: Boolean, maxWorkers: Int): T =
run(root, checkCycles, maxWorkers) match {
case Value(v) => v
case Inc(i) => throw i
}
def reduce[S](i: IndexedSeq[Task[S]], f: (S, S) => S): Task[S] =
i match
{
case Seq() => error("Cannot reduce empty sequence")
case Seq(x) => x
case Seq(x, y) => reducePair(x, y, f)
case z =>
val (a, b) = i.splitAt(i.size / 2)
reducePair( reduce(a, f), reduce(b, f), f )
}
def reducePair[S](a: Task[S], b: Task[S], f: (S, S) => S): Task[S] =
(a :^: b :^: MNil) mapH { case x :+: y :+: HNil => f(x,y) }
}

View File

@ -1,44 +1,49 @@
import xsbt._
/* sbt -- Simple Build Tool
* Copyright 2009, 2010 Mark Harrah
*/
package sbt
import org.scalacheck._
import Prop._
import TaskGen._
import Task._
object TaskRunnerSpec extends Properties("TaskRunner")
object ExecuteSpec extends Properties("Execute")
{
val iGen = Arbitrary.arbInt.arbitrary
property("evaluates simple task") = forAll(iGen, MaxWorkersGen) { (i: Int, workers: Int) =>
("Workers: " + workers) |:
checkResult(TaskRunner(Task(i), workers), i)
checkResult(tryRun(pure(i), false, workers), i)
}
property("evaluates simple static graph") = forAll(iGen, MaxWorkersGen) { (i: Int, workers: Int) =>
// no direct dependencies currently
/*property("evaluates simple static graph") = forAll(iGen, MaxWorkersGen) { (i: Int, workers: Int) =>
("Workers: " + workers) |:
{
def result = TaskRunner(Task(i) dependsOn(Task(false),Task("a")), workers)
def result = tryRun(Task(i) dependsOn(pure(false),pure("a")), false, workers)
checkResult(result, i)
}
}
}*/
property("evaluates simple mapped task") = forAll(iGen, MaxTasksGen, MaxWorkersGen) { (i: Int, times: Int, workers: Int) =>
("Workers: " + workers) |: ("Value: " + i) |: ("Times: " + times) |:
{
def result = TaskRunner(Task(i).map(_*times), workers)
def result = tryRun(pure(i).map(_*times), false, workers)
checkResult(result, i*times)
}
}
property("evaluates chained mapped task") = forAllNoShrink(iGen, Gen.choose(0, 1000), MaxWorkersGen) { (i: Int, times: Int, workers: Int) =>
property("evaluates chained mapped task") = forAllNoShrink(iGen, MaxTasksGen, MaxWorkersGen) { (i: Int, times: Int, workers: Int) =>
("Workers: " + workers) |: ("Value: " + i) |: ("Times: " + times) |:
{
val initial = Task(0) map(identity[Int])
val initial = pure(0) map(identity[Int])
def task = ( initial /: (0 until times) )( (t,ignore) => t.map(_ + i))
checkResult(TaskRunner(task, workers), i*times)
checkResult(tryRun(task, false, workers), i*times)
}
}
property("evaluates simple bind") = forAll(iGen, MaxTasksGen, MaxWorkersGen) { (i: Int, times: Int, workers: Int) =>
("Workers: " + workers) |: ("Value: " + i) |: ("Times: " + times) |:
{
def result = TaskRunner(Task(i).bind(x => Task(x*times)), workers)
def result = tryRun(pure(i).flatMap(x => pure(x*times)), false, workers)
checkResult(result, i*times)
}
}

View File

@ -1,17 +0,0 @@
package xsbt
import HLists._
import Task._
/** This test just verifies that the HList support compiles.*/
object TListCompileTest
{
val n = Task(1)
val s = Task("3")
val t = Task(true)
val mapped = (n :: s :: t :: TNil) map { case n :: s :: t :: HNil => n }
val bound = (n :: s :: t :: TNil) bind { case n :: s :: t :: HNil => (Task(n*4) :: Task("Hi " + t) :: TNil).join }
val plusOne = mapped map { _ + 1 }
val forkN = plusOne bind { count => (0 until count) fork { i => Task(println(i)) } join }
}

View File

@ -1,11 +1,16 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt
import org.scalacheck._
import Gen.choose
object TaskGen
{
// upper bounds to make the tests finish in reasonable time
val MaxTasks = 10000
val MaxWorkers = 257
val MaxTasks = 100
val MaxWorkers = 29
val MaxJoin = 100
val MaxTasksGen = choose(0, MaxTasks)

View File

@ -1,8 +1,9 @@
import xsbt._
package sbt
import org.scalacheck._
import Prop._
import TaskGen._
import Task._
object TaskRunnerCircularTest extends Properties("TaskRunner Circular")
{
@ -10,32 +11,33 @@ object TaskRunnerCircularTest extends Properties("TaskRunner Circular")
property("Allows references to completed tasks") = forAllNoShrink(MaxTasksGen, MaxWorkersGen) { allowedReference _ }
final def allowedReference(intermediate: Int, workers: Int) =
{
val top = Task(intermediate) named("top")
val top = pure("top", intermediate)
def iterate(task: Task[Int]): Task[Int] =
task bind { t =>
task flatMap { t =>
if(t <= 0)
top
else
iterate(Task(t-1) named (t-1).toString)
iterate(pure((t-1).toString, t-1) )
}
try { checkResult(TaskRunner(iterate(top), workers), intermediate) }
catch { case e: CircularDependency => ("Unexpected exception: " + e) |: false }
try { checkResult(tryRun(iterate(top), true, workers), intermediate) }
catch { case i: Incomplete if cyclic(i) => ("Unexpected cyclic exception: " + i) |: false }
}
final def checkCircularReferences(intermediate: Int, workers: Int) =
{
lazy val top = iterate(Task(intermediate) named"bottom", intermediate)
lazy val top = iterate(pure("bottom", intermediate), intermediate)
def iterate(task: Task[Int], i: Int): Task[Int] =
{
lazy val it: Task[Int] =
task bind { t =>
task flatMap { t =>
if(t <= 0)
top
else
iterate(Task(t-1) named (t-1).toString, i-1)
} named("it_" + i)
iterate(pure((t-1).toString, t-1), i-1)
}
it
}
try { TaskRunner(top, workers); false }
catch { case TasksFailed(failures) => failures.exists(_.exception.isInstanceOf[CircularDependency]) }
try { tryRun(top, true, workers); false }
catch { case i: Incomplete => cyclic(i) }
}
def cyclic(i: Incomplete) = Incomplete.allExceptions(i).exists(_.isInstanceOf[Execute[Task]#CyclicException[_]])
}

View File

@ -1,22 +1,22 @@
import xsbt._
import sbt._
import org.scalacheck._
import Prop._
import Task._
import TaskGen._
import Math.abs
import math.abs
object TaskRunnerForkTest extends Properties("TaskRunner Fork")
{
property("fork m tasks and wait for all to complete") = forAll(MaxTasksGen, MaxWorkersGen) { (m: Int, workers: Int) =>
val values = (0 until m).toList
checkResult(TaskRunner(values.fork(f => () ).join.map(_.toList),workers), values)
checkResult(tryRun(values.fork(f => () ).join.map(_.toList),false, workers), values)
true
}
property("Fork and reduce 2") = forAll(MaxTasksGen, MaxWorkersGen) { (m: Int, workers: Int) =>
(m > 1) ==> {
val task = (0 to m) fork {_ * 10} reduce{_ + _}
checkResult(TaskRunner(task, workers), 5*(m+1)*m)
checkResult(tryRun(task, false, workers), 5*(m+1)*m)
}
}
property("Double join") = forAll(MaxJoinGen, MaxJoinGen, MaxWorkersGen) { (a: Int, b: Int, workers: Int) =>
@ -25,13 +25,13 @@ object TaskRunnerForkTest extends Properties("TaskRunner Fork")
}
def runDoubleJoin(a: Int, b: Int, workers: Int)
{
def inner(i: Int) = List.range(0, b).map(j => Task(j) named(j.toString)).join.named("Join " + i)
TaskRunner( List.range(0,a).map(inner).join.named("Outermost join"), workers)
def inner(i: Int) = List.range(0, b).map(j => pure(j.toString, j)).join
tryRun( List.range(0,a).map(inner).join, false, workers)
}
property("fork and reduce") = forAll(TaskListGen, MaxWorkersGen) { (m: List[Int], workers: Int) =>
(!m.isEmpty) ==> {
val expected = m.reduceLeft(_+_)
checkResult(TaskRunner( m.reduce(_ + _), workers), expected)
checkResult(tryRun( m.reduce(_ + _), false, workers), expected)
}
}
}

View File

@ -0,0 +1,59 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
import Types._
import Task._
import Execute._
object Test
{
val a = pure(3)
val b = pure[Boolean](error("test"))
val b2 = pure(true)
val c = pure("asdf")
val i3 = a :^: b :^: c :^: MNil
val i32 = a :^: b2 :^: c :^: MNil
val fh= (_: Int :+: Boolean :+: String :+: HNil) match
{ case aa :+: bb :+: cc :+: HNil => aa + " " + bb + " " + cc }
val h1 = i3 mapH fh
val h2 = i32 mapH fh
val f: i3.Map[Result] => Any = {
case Value(aa) :^: Value(bb) :^: Value(cc) :^: MNil => aa + " " + bb + " " + cc
case x =>
val cs = x.toList.collect { case Inc(x) => x } // workaround for double definition bug
throw Incomplete(causes = cs)
}
val d2 = i32 mapR f
val f2: i3.Map[Result] => Task[Any] = {
case Value(aa) :^: Value(bb) :^: Value(cc) :^: MNil => new Pure(() => aa + " " + bb + " " + cc)
case x => d3
}
lazy val d = i3 flatMapR f2
val f3: i3.Map[Result] => Task[Any] = {
case Value(aa) :^: Value(bb) :^: Value(cc) :^: MNil => new Pure(() => aa + " " + bb + " " + cc)
case x => d2
}
lazy val d3= i3 flatMapR f3
def d4(i: Int): Task[Int] = MNil flatMap { _ => val x = math.random; if(x < 0.01) pure(i); else d4(i+1) }
def go()
{
def run[T](root: Task[T]) =
println("Result : " + Task.run(root, true, 2))
run(a)
run(b)
run(b2)
run(c)
run(d)
run(d2)
run( d4(0) )
run(h1)
run(h2)
}
}

View File

@ -1,8 +1,9 @@
import xsbt._
import sbt._
import org.scalacheck._
import Prop._
import TaskGen._
import Task._
object TaskRunnerCallTest extends Properties("TaskRunner Call")
{
@ -11,7 +12,7 @@ object TaskRunnerCallTest extends Properties("TaskRunner Call")
val f = fibDirect(i)
("Workers: " + workers) |: ("i: " + i) |: ("fib(i): " + f) |:
{
def result = TaskRunner( fibTask(i), workers)
def result = tryRun( fibTask(i), false, workers)
checkResult(result, f)
}
}
@ -23,11 +24,11 @@ object TaskRunnerCallTest extends Properties("TaskRunner Call")
(index, x1, x2) =>
{
if(index == i)
Task(x2)
pure(x2)
else
iterate( (index+1, x2, x1+x2) )
}
def iterate(iteration: (Int,Int,Int)) = Task( iteration ) bind Function.tupled(next)
def iterate(iteration: (Int,Int,Int)) = pure( iteration ) flatMap next.tupled
iterate( (1, 0, 1) )
}
final def fibDirect(i: Int): Int =

View File

@ -1,8 +1,13 @@
import xsbt._
/* sbt -- Simple Build Tool
* Copyright 2009, 2010 Mark Harrah
*/
package sbt
import org.scalacheck._
import Prop._
import TaskGen._
import Task._
import Types._
object TaskRunnerSortTest extends Properties("TaskRunnerSort")
{
@ -12,31 +17,33 @@ object TaskRunnerSortTest extends Properties("TaskRunnerSort")
java.util.Arrays.sort(sorted)
("Workers: " + workers) |: ("Array: " + a.toList) |:
{
def result = TaskRunner( sort(a.toArray), if(workers > 0) workers else 1)
def result = tryRun( sort(a.toSeq), false, if(workers > 0) workers else 1)
checkResult(result.toList, sorted.toList)
}
}
final def sortDirect(a: RandomAccessSeq[Int]): RandomAccessSeq[Int] =
final def sortDirect(a: Seq[Int]): Seq[Int] =
{
if(a.length < 2)
a
else
{
val pivot = a(0)
val (lt,gte) = a.projection.drop(1).partition(_ < pivot)
val (lt,gte) = a.view.drop(1).partition(_ < pivot)
sortDirect(lt) ++ List(pivot) ++ sortDirect(gte)
}
}
final def sort(a: RandomAccessSeq[Int]): Task[RandomAccessSeq[Int]] =
final def sort(a: Seq[Int]): Task[Seq[Int]] =
{
if(a.length < 200)
Task(sortDirect(a))
pure(sortDirect(a))
else
{
Task(a) bind { a =>
pure(a) flatMap { a =>
val pivot = a(0)
val (lt,gte) = a.projection.drop(1).partition(_ < pivot)
(sort(lt), sort(gte)) map { _ ++ List(pivot) ++ _ }
val (lt,gte) = a.view.drop(1).partition(_ < pivot)
sort(lt) :^: sort(gte) :^: MNil mapH {
case l :+: g :+: HNil => l ++ List(pivot) ++ g
}
}
}
}

View File

@ -1,4 +1,7 @@
package xsbt
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt
import org.scalacheck.Prop._
@ -14,8 +17,8 @@ object checkResult
}
catch
{
case TasksFailed(failures) =>
failures.foreach(f => f.exception.printStackTrace)
case i: Incomplete =>
println(Incomplete.show(i, true))
"One or more tasks failed" |: false
case e =>
e.printStackTrace

View File

@ -25,4 +25,10 @@ final case class HCons[H, T <: HList](head : H, tail : T) extends HList
type Up = MCons[H, tail.Up, Id]
def up = MCons[H,tail.Up, Id](head, tail.up)
def :+: [G](g: G): G :+: H :+: T = HCons(g, this)
}
object HList
{
// contains no type information: not even A
implicit def fromList[A](list: Traversable[A]): HList = ((HNil: HList) /: list) ( (hl,v) => HCons(v, hl) )
}

View File

@ -41,4 +41,11 @@ sealed class MNil extends MList[Nothing]
def toList = Nil
}
object MNil extends MNil
object MNil extends MNil
object MList
{
implicit def fromTCList[A[_]](list: Traversable[A[_]]): MList[A] = ((MNil: MList[A]) /: list) ( (hl,v) => MCons(v, hl) )
implicit def fromList[A](list: Traversable[A]): MList[Const[A]#Apply] = ((MNil: MList[Const[A]#Apply]) /: list) ( (hl,v) => MCons[A, hl.type, Const[A]#Apply](v, hl) )
}

View File

@ -13,6 +13,10 @@ trait TypeFunctions
final val left = new (Id ~> P1of2[Left, Nothing]#Flip) { def apply[T](t: T) = Left(t) }
final val right = new (Id ~> P1of2[Right, Nothing]#Apply) { def apply[T](t: T) = Right(t) }
final val some = new (Id ~> Some) { def apply[T](t: T) = Some(t) }
implicit def toFn1[A,B](f: A => B): Fn1[A,B] = new Fn1[A,B] {
def [C](g: C => A) = f compose g
}
}
object TypeFunctions extends TypeFunctions
@ -29,4 +33,7 @@ object ~>
import TypeFunctions._
val Id: Id ~> Id = new (Id ~> Id) { def apply[T](a: T): T = a }
implicit def tcIdEquals: (Id ~> Id) = Id
}
trait Fn1[A, B] {
def [C](g: C => A): C => B
}

View File

@ -7,10 +7,17 @@ object ErrorHandling
{
def translate[T](msg: => String)(f: => T) =
try { f }
catch { case e => throw new TranslatedException(msg + e.toString, e) }
catch { case e: Exception => throw new TranslatedException(msg + e.toString, e) }
def wideConvert[T](f: => T): Either[Throwable, T] =
try { Right(f) }
catch { case e => Left(e) } // TODO: restrict type of e
catch
{
case ex @ (_: Exception | _: StackOverflowError) => Left(ex)
case err @ (_: ThreadDeath | _: VirtualMachineError) => throw err
case x => Left(x)
}
def convert[T](f: => T): Either[Exception, T] =
try { Right(f) }
catch { case e: Exception => Left(e) }

View File

@ -1,7 +1,8 @@
/* sbt -- Simple Build Tool
* Copyright 2009, 2010 Mark Harrah
*/
package xsbt.api
package xsbt
package api
import java.io.File
import xsbt.FileUtilities

View File

@ -399,4 +399,36 @@ object IO
/** Splits a String around path separator characters. */
def pathSplit(s: String) = PathSeparatorPattern.split(s)
/** Move the provided files to a temporary location.
* If 'f' returns normally, delete the files.
* If 'f' throws an Exception, return the files to their original location.*/
def stash[T](files: Set[File])(f: => T): T =
withTemporaryDirectory { dir =>
val stashed = stashLocations(dir, files.toArray)
move(stashed)
try { f } catch { case e: Exception =>
try { move(stashed.map(_.swap)); throw e }
catch { case _: Exception => throw e }
}
}
private def stashLocations(dir: File, files: Array[File]) =
for( (file, index) <- files.zipWithIndex) yield
(file, new File(dir, index.toHexString))
def move(files: Iterable[(File, File)]): Unit =
files.foreach(Function.tupled(move))
def move(a: File, b: File): Unit =
{
if(b.exists)
delete(b)
if(!a.renameTo(b))
{
copyFile(a, b)
delete(a)
}
}
}

View File

@ -0,0 +1,81 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah */
package xsbt
import org.specs._
import FileUtilities._
import java.io.File
import Function.tupled
object CheckStash extends Specification
{
"stash" should {
"handle empty files" in {
stash(Set()) { }
}
"move files during execution" in {
WithFiles(TestFiles : _*) ( checkMove )
}
"restore files on exceptions but not errors" in {
WithFiles(TestFiles : _*) ( checkRestore )
}
}
def checkRestore(seq: Seq[File])
{
allCorrect(seq)
stash0(seq, throw new TestRuntimeException) must beFalse
allCorrect(seq)
stash0(seq, throw new TestException) must beFalse
allCorrect(seq)
stash0(seq, throw new TestError) must beFalse
noneExist(seq)
}
def checkMove(seq: Seq[File])
{
allCorrect(seq)
stash0(seq, ()) must beTrue
noneExist(seq)
}
def stash0(seq: Seq[File], post: => Unit): Boolean =
try
{
stash(Set() ++ seq) {
noneExist(seq)
post
}
true
}
catch {
case _: TestError | _: TestException | _: TestRuntimeException => false
}
def allCorrect(s: Seq[File]) = (s.toList zip TestFiles.toList).forall(tupled(correct))
def correct(check: File, ref: (File, String)) =
{
check.exists must beTrue
read(check) must beEqual(ref._2)
}
def noneExist(s: Seq[File]) = s.forall(!_.exists) must beTrue
lazy val TestFiles =
Seq(
"a/b/c" -> "content1",
"a/b/e" -> "content1",
"c" -> "",
"e/g" -> "asdf",
"a/g/c" -> "other"
) map {
case (f, c) => (new File(f), c)
}
}
class TestError extends Error
class TestRuntimeException extends RuntimeException
class TestException extends Exception

View File

@ -1,15 +1,15 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package xsbt
package sbt
/** Implements the level-setting methods of Logger.*/
abstract class BasicLogger extends Logger
abstract class BasicLogger extends AbstractLogger
{
private var traceEnabledVar = true
private var traceEnabledVar = java.lang.Integer.MAX_VALUE
private var level: Level.Value = Level.Info
def getLevel = level
def setLevel(newLevel: Level.Value) { level = newLevel }
def enableTrace(flag: Boolean) { traceEnabledVar = flag }
def traceEnabled = traceEnabledVar
}
def setTrace(level: Int) { traceEnabledVar = level }
def getTrace = traceEnabledVar
}

View File

@ -1,8 +1,9 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package xsbt
import sbt.{AbstractLogger, ControlEvent, Level, Log, LogEvent, SetLevel, SetTrace, Success, Trace}
import scala.collection.mutable.ListBuffer
/** A logger that can buffer the logging done on it and then can flush the buffer
@ -13,7 +14,7 @@
*
* This class assumes that it is the only client of the delegate logger.
* */
class BufferedLogger(delegate: Logger) extends Logger
class BufferedLogger(delegate: AbstractLogger) extends AbstractLogger
{
private[this] val buffer = new ListBuffer[LogEvent]
private[this] var recording = false
@ -54,10 +55,10 @@ class BufferedLogger(delegate: Logger) extends Logger
}
def getLevel = delegate.getLevel
def traceEnabled = delegate.traceEnabled
def enableTrace(flag: Boolean)
def setTrace(level: Int)
{
buffer += new SetTrace(flag)
delegate.enableTrace(flag)
buffer += new SetTrace(level)
delegate.setTrace(level)
}
def trace(t: => Throwable): Unit =
@ -73,9 +74,9 @@ class BufferedLogger(delegate: Logger) extends Logger
delegate.logAll(events)
def control(event: ControlEvent.Value, message: => String): Unit =
doBufferable(Level.Info, new ControlEvent(event, message), _.control(event, message))
private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: Logger => Unit): Unit =
private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit =
doBufferableIf(atLevel(level), appendIfBuffered, doUnbuffered)
private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: Logger => Unit): Unit =
private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit =
if(condition)
{
if(recording)

View File

@ -1,7 +1,7 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package xsbt
package sbt
object ConsoleLogger
{
@ -17,10 +17,12 @@ object ConsoleLogger
}
/** A logger that logs to the console. On supported systems, the level labels are
* colored. */
* colored.
*
* This logger is not thread-safe.*/
class ConsoleLogger extends BasicLogger
{
import ConsoleLogger.formatEnabled
override def ansiCodesSupported = ConsoleLogger.formatEnabled
def messageColor(level: Level.Value) = Console.RESET
def labelColor(level: Level.Value) =
level match
@ -39,8 +41,9 @@ class ConsoleLogger extends BasicLogger
def trace(t: => Throwable): Unit =
System.out.synchronized
{
if(traceEnabled)
t.printStackTrace
val traceLevel = getTrace
if(traceLevel >= 0)
System.out.synchronized { System.out.print(StackTrace.trimmed(t, traceLevel)) }
}
def log(level: Level.Value, message: => String)
{
@ -49,7 +52,7 @@ class ConsoleLogger extends BasicLogger
}
private def setColor(color: String)
{
if(formatEnabled)
if(ansiCodesSupported)
System.out.synchronized { System.out.print(color) }
}
private def log(labelColor: String, label: String, messageColor: String, message: String): Unit =

View File

@ -0,0 +1,35 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt
/** A filter logger is used to delegate messages but not the logging level to another logger. This means
* that messages are logged at the higher of the two levels set by this logger and its delegate.
* */
class FilterLogger(delegate: AbstractLogger) extends BasicLogger
{
override lazy val ansiCodesSupported = delegate.ansiCodesSupported
def trace(t: => Throwable)
{
if(traceEnabled)
delegate.trace(t)
}
override def setTrace(level: Int) { delegate.setTrace(level) }
override def getTrace = delegate.getTrace
def log(level: Level.Value, message: => String)
{
if(atLevel(level))
delegate.log(level, message)
}
def success(message: => String)
{
if(atLevel(Level.Info))
delegate.success(message)
}
def control(event: ControlEvent.Value, message: => String)
{
if(atLevel(Level.Info))
delegate.control(event, message)
}
def logAll(events: Seq[LogEvent]): Unit = delegate.logAll(events)
}

View File

@ -1,11 +1,11 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package xsbt
package sbt
/** An enumeration defining the levels available for logging. A level includes all of the levels
* with id larger than its own id. For example, Warn (id=3) includes Error (id=4).*/
object Level extends Enumeration with NotNull
object Level extends Enumeration
{
val Debug = Value(1, "debug")
val Info = Value(2, "info")
@ -16,10 +16,8 @@ object Level extends Enumeration with NotNull
* label is also defined here. */
val SuccessLabel = "success"
// added because elements was renamed to iterator in 2.8.0 nightly
def levels = Debug :: Info :: Warn :: Error :: Nil
/** Returns the level with the given name wrapped in Some, or None if no level exists for that name. */
def apply(s: String) = levels.find(s == _.toString)
def apply(s: String) = values.find(s == _.toString)
/** Same as apply, defined for use in pattern matching. */
private[xsbt] def unapply(s: String) = apply(s)
private[sbt] def unapply(s: String) = apply(s)
}

View File

@ -1,14 +1,14 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package xsbt
package sbt
sealed trait LogEvent extends NotNull
final class Success(val msg: String) extends LogEvent
final class Log(val level: Level.Value, val msg: String) extends LogEvent
final class Trace(val exception: Throwable) extends LogEvent
final class SetLevel(val newLevel: Level.Value) extends LogEvent
final class SetTrace(val enabled: Boolean) extends LogEvent
final class SetTrace(val level: Int) extends LogEvent
final class ControlEvent(val event: ControlEvent.Value, val msg: String) extends LogEvent
object ControlEvent extends Enumeration

View File

@ -1,18 +1,22 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package xsbt
package sbt
import xsbti.{Logger => xLogger, F0}
abstract class Logger extends xLogger with NotNull
abstract class AbstractLogger extends xLogger with NotNull
{
def getLevel: Level.Value
def setLevel(newLevel: Level.Value)
def enableTrace(flag: Boolean)
def traceEnabled: Boolean
def setTrace(flag: Int)
def getTrace: Int
final def traceEnabled = getTrace >= 0
def ansiCodesSupported = false
def atLevel(level: Level.Value) = level.id >= getLevel.id
def trace(t: => Throwable): Unit
final def verbose(message: => String): Unit = debug(message)
final def debug(message: => String): Unit = log(Level.Debug, message)
final def info(message: => String): Unit = log(Level.Info, message)
final def warn(message: => String): Unit = log(Level.Warn, message)
@ -31,7 +35,7 @@ abstract class Logger extends xLogger with NotNull
case l: Log => log(l.level, l.msg)
case t: Trace => trace(t.exception)
case setL: SetLevel => setLevel(setL.newLevel)
case setT: SetTrace => enableTrace(setT.enabled)
case setT: SetTrace => setTrace(setT.level)
case c: ControlEvent => control(c.event, c.msg)
}
}

View File

@ -0,0 +1,40 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt
/** Provides a `java.io.Writer` interface to a `Logger`. Content is line-buffered and logged at `level`.
* A line is delimited by `nl`, which is by default the platform line separator.*/
class LoggerWriter(delegate: AbstractLogger, level: Level.Value, nl: String) extends java.io.Writer
{
def this(delegate: AbstractLogger, level: Level.Value) = this(delegate, level, System.getProperty("line.separator"))
private[this] val buffer = new StringBuilder
override def close() = flush()
override def flush(): Unit =
synchronized {
if(buffer.length > 0)
{
log(buffer.toString)
buffer.clear()
}
}
override def write(content: Array[Char], offset: Int, length: Int): Unit =
synchronized {
buffer.append(content, offset, length)
process()
}
private[this] def process()
{
val i = buffer.indexOf(nl)
if(i >= 0)
{
log(buffer.substring(0, i))
buffer.delete(0, i + nl.length)
process()
}
}
private[this] def log(s: String): Unit = delegate.log(level, s)
}

View File

@ -0,0 +1,27 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt
class MultiLogger(delegates: List[AbstractLogger]) extends BasicLogger
{
override lazy val ansiCodesSupported = delegates.forall(_.ansiCodesSupported)
override def setLevel(newLevel: Level.Value)
{
super.setLevel(newLevel)
dispatch(new SetLevel(newLevel))
}
override def setTrace(level: Int)
{
super.setTrace(level)
dispatch(new SetTrace(level))
}
def trace(t: => Throwable) { dispatch(new Trace(t)) }
def log(level: Level.Value, message: => String) { dispatch(new Log(level, message)) }
def success(message: => String) { dispatch(new Success(message)) }
def logAll(events: Seq[LogEvent]) { delegates.foreach(_.logAll(events)) }
def control(event: ControlEvent.Value, message: => String) { delegates.foreach(_.control(event, message)) }
private def dispatch(event: LogEvent) { delegates.foreach(_.log(event)) }
}