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

8
cache/Cache.scala vendored
View File

@ -2,7 +2,6 @@ package xsbt
import sbinary.{CollectionTypes, Format, JavaFormats} import sbinary.{CollectionTypes, Format, JavaFormats}
import java.io.File import java.io.File
import scala.reflect.Manifest
trait Cache[I,O] 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] = def wrapOutputCache[O,DO](implicit convert: O => DO, reverse: DO => O, base: OutputCache[DO]): OutputCache[O] =
new WrappedOutputCache[O,DO](convert, reverse, base) 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 = def cached[I,O](file: File)(f: I => O)(implicit cache: Cache[I,O]): I => O =
in => in =>
cache(file)(in) match cache(file)(in) match

15
cache/FileInfo.scala vendored
View File

@ -18,8 +18,10 @@ sealed trait ModifiedFileInfo extends FileInfo
{ {
val lastModified: Long val lastModified: Long
} }
sealed trait PlainFileInfo extends FileInfo
sealed trait HashModifiedFileInfo extends HashFileInfo with ModifiedFileInfo 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 FileHash(file: File, hash: List[Byte]) extends HashFileInfo
private final case class FileModified(file: File, lastModified: Long) extends ModifiedFileInfo 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 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 apply(file: File): F
implicit def unapply(info: F): File = info.file implicit def unapply(info: F): File = info.file
implicit val format: Format[F] implicit val format: Format[F]
/*val manifest: Manifest[F]
def formatManifest: Manifest[Format[F]] = CacheIO.manifest[Format[F]]*/
import Cache._ import Cache._
implicit def infoInputCache: InputCache[File] = wrapInputCache[File,F] implicit def infoInputCache: InputCache[File] = wrapInputCache[File,F]
implicit def infoOutputCache: OutputCache[File] = wrapOutputCache[File,F] implicit def infoOutputCache: OutputCache[File] = wrapOutputCache[File,F]
@ -41,7 +41,6 @@ object FileInfo
object full extends Style object full extends Style
{ {
type F = HashModifiedFileInfo type F = HashModifiedFileInfo
//val manifest: Manifest[F] = CacheIO.manifest[HashModifiedFileInfo]
implicit def apply(file: File): HashModifiedFileInfo = make(file, Hash(file).toList, file.lastModified) 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) 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), tupled(make _))
@ -49,7 +48,6 @@ object FileInfo
object hash extends Style object hash extends Style
{ {
type F = HashFileInfo type F = HashFileInfo
//val manifest: Manifest[F] = CacheIO.manifest[HashFileInfo]
implicit def apply(file: File): HashFileInfo = make(file, computeHash(file).toList) implicit def apply(file: File): HashFileInfo = make(file, computeHash(file).toList)
def make(file: File, hash: List[Byte]): HashFileInfo = FileHash(file.getAbsoluteFile, hash) 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 _)) implicit val format: Format[HashFileInfo] = wrap(f => (f.file, f.hash), tupled(make _))
@ -58,11 +56,17 @@ object FileInfo
object lastModified extends Style object lastModified extends Style
{ {
type F = ModifiedFileInfo type F = ModifiedFileInfo
//val manifest: Manifest[F] = CacheIO.manifest[ModifiedFileInfo]
implicit def apply(file: File): ModifiedFileInfo = make(file, file.lastModified) implicit def apply(file: File): ModifiedFileInfo = make(file, file.lastModified)
def make(file: File, lastModified: Long): ModifiedFileInfo = FileModified(file.getAbsoluteFile, 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), 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 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 full: Style = new BasicStyle(FileInfo.full)
lazy val hash: Style = new BasicStyle(FileInfo.hash) lazy val hash: Style = new BasicStyle(FileInfo.hash)
lazy val lastModified: Style = new BasicStyle(FileInfo.lastModified) 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 lengthCache = new File("/tmp/length-cache")
val cCache = new File("/tmp/c-cache") val cCache = new File("/tmp/c-cache")
import Task._
import Cache._ import Cache._
import FileInfo.hash._ import FileInfo.hash._
def test 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 length = cached(lengthCache) {
val cachedLength = cached(lengthCache) ( length ) (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 c = cached(cCache) { (in: (File :: Long :: HNil)) =>
val cTask = (createTask :: lengthTask :: TNil) map cached(cCache) { case (file :: len :: HNil) => c(file, len) } val file :: len :: HNil = in
println("File: " + file + " (" + file.exists + "), length: " + len)
try { TaskRunner(cTask) } (len+1) :: file :: HNil
catch { case TasksFailed(failures) => failures.foreach(_.exception.printStackTrace) } }
c(create :: fileLength :: HNil)
} }
} }

View File

@ -7,44 +7,74 @@ import java.io.{File,IOException}
import CacheIO.{fromFile, toFile} import CacheIO.{fromFile, toFile}
import sbinary.Format import sbinary.Format
import scala.reflect.Manifest 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 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.*/ /** Cleans outputs and clears the cache.*/
def clean: Task[Unit] def clean: Unit
/** Clears the cache. If also cleaning, 'clean' should be called first as it might require information from the cache.*/
def clear: Task[Unit]
} }
class Timestamp(val cacheFile: File) extends Tracked class Timestamp(val cacheFile: File, useStartTime: Boolean) extends Tracked
{ {
val clean = Clean(cacheFile) def clean = delete(cacheFile)
def clear = Task.empty /** Reads the previous timestamp, evaluates the provided function, and then updates the timestamp.*/
def apply[T](f: Long => Task[T]): Task[T] = def apply[T](f: Long => T): T =
{ {
val getTimestamp = Task { readTimestamp } val start = now()
getTimestamp bind f map { result => val result = f(readTimestamp)
FileUtilities.write(cacheFile, System.currentTimeMillis.toString) write(cacheFile, (if(useStartTime) start else now()).toString)
result result
} }
} private def now() = System.currentTimeMillis
def readTimestamp: Long = def readTimestamp: Long =
try { FileUtilities.read(cacheFile).toLong } try { read(cacheFile).toLong }
catch { case _: NumberFormatException | _: java.io.FileNotFoundException => 0 } 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 clean = delete(cacheFile)
def clear = Task.empty def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O2 =
def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): Task[O2] = {
task map { value => val value = getValue
val cache = val cache =
try { OpenResource.fileInputStream(cacheFile)(input.uptodate(value)) } try { OpenResource.fileInputStream(cacheFile)(input.uptodate(value)) }
catch { case _: IOException => new ForceResult(input)(value) } catch { case _: IOException => new ForceResult(input)(value) }
@ -61,22 +91,31 @@ object Difference
{ {
sealed class Constructor private[Difference](defineClean: Boolean, filesAreOutputs: Boolean) extends NotNull 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 = new Difference(files, style, cache, defineClean, filesAreOutputs)
def apply(files: Set[File], style: FilesInfo.Style, cache: File): Difference = apply(Task(files), style, cache)
} }
/** 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) 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) 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 def clean =
val clear = Clean(cache) {
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 cachedFilesInfo = fromFile(style.formats, style.empty)(cache)(style.manifest).files
private def raw(fs: Set[style.F]): Set[File] = fs.map(_.file) private def raw(fs: Set[style.F]): Set[File] = fs.map(_.file)
def apply[T](f: ChangeReport[File] => Task[T]): Task[T] = def apply[T](f: ChangeReport[File] => T): T =
filesTask bind { files => {
val files = getFiles
val lastFilesInfo = cachedFilesInfo val lastFilesInfo = cachedFilesInfo
val lastFiles = raw(lastFilesInfo) val lastFiles = raw(lastFilesInfo)
val currentFiles = files.map(_.getAbsoluteFile) val currentFiles = files.map(_.getAbsoluteFile)
@ -91,29 +130,30 @@ class Difference(val filesTask: Task[Set[File]], val style: FilesInfo.Style, val
lazy val unmodified = checked -- modified lazy val unmodified = checked -- modified
} }
f(report) map { result => val result = f(report)
val info = if(filesAreOutputs) style(currentFiles) else currentFilesInfo val info = if(filesAreOutputs) style(currentFiles) else currentFilesInfo
toFile(style.formats)(info)(cache)(style.manifest) toFile(style.formats)(info)(cache)(style.manifest)
result result
} }
}
} }
class DependencyTracked[T](val cacheDirectory: File, val translateProducts: Boolean, cleanT: T => Unit)(implicit format: Format[T], mf: Manifest[T]) extends Tracked 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 val trackFormat = new TrackingFormat[T](cacheDirectory, translateProducts)
private def cleanAll(fs: Set[T]) = fs.foreach(cleanT) private def cleanAll(fs: Set[T]) = fs.foreach(cleanT)
val clean = Task(cleanAll(trackFormat.read.allProducts)) def clean =
val clear = Clean(cacheDirectory) {
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 val tracker = trackFormat.read
f(tracker) map { result => val result = f(tracker)
trackFormat.write(tracker) trackFormat.write(tracker)
result result
} }
}
} }
object InvalidateFiles object InvalidateFiles
{ {
@ -179,30 +219,29 @@ class InvalidateTransitive[T](cacheDirectory: File, translateProducts: Boolean,
this(cacheDirectory, translateProducts, (_: T) => ()) this(cacheDirectory, translateProducts, (_: T) => ())
private val tracked = new DependencyTracked(cacheDirectory, translateProducts, cleanT) private val tracked = new DependencyTracked(cacheDirectory, translateProducts, cleanT)
def clean = tracked.clean def 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] =
{ {
changesTask bind { changes => tracked.clean
tracked.clear
}
def apply[R](getChanges: => ChangeReport[T])(f: (InvalidationReport[T], UpdateTracking[T]) => R): R =
{
val changes = getChanges
tracked { tracker => tracked { tracker =>
val report = InvalidateTransitive.andClean[T](tracker, _.foreach(cleanT), changes.modified) val report = InvalidateTransitive.andClean[T](tracker, _.foreach(cleanT), changes.modified)
f(report, tracker) 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 val invalidation = InvalidateFiles(new File(cacheDirectory, "invalidation"))
private def onTracked(f: Tracked => Task[Unit]) = Seq(invalidation, changed).forkTasks(f).joinIgnore private def onTracked(f: Tracked => Unit) = { f(invalidation); f(changed) }
val clear = onTracked(_.clear) def clean = onTracked(_.clean)
val 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 => changed { sourceChanges =>
invalidation(sourceChanges) { (report, tracking) => invalidation(sourceChanges) { (report, tracking) =>
f(sourceChanges, report, tracking) f(sourceChanges, report, tracking)

View File

@ -78,7 +78,7 @@ private class SameAPI(a: Source, b: Source, includePrivate: Boolean)
import SameAPI._ import SameAPI._
/** de Bruijn levels for type parameters in source `a`*/ /** de Bruijn levels for type parameters in source `a`*/
private lazy val tagsA = TagTypeVariables(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) private lazy val tagsB = TagTypeVariables(b)
def debug(flag: Boolean, msg: => String): Boolean = 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 (properties, m6) = processSection(m5, "app-properties", getAppProperties)
val (cacheDir, m7) = processSection(m6, "ivy", getIvy) val (cacheDir, m7) = processSection(m6, "ivy", getIvy)
check(m7, "section") 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) new LaunchConfiguration(scalaVersion, IvyOptions(cacheDir, classifiers, repositories), app, boot, logging, properties)
} }
def getScala(m: LabelMap) = def getScala(m: LabelMap) =
@ -43,7 +43,7 @@ class ConfigurationParser extends NotNull
val (scalaVersion, m1) = getVersion(m, "Scala version", "scala.version") val (scalaVersion, m1) = getVersion(m, "Scala version", "scala.version")
val (scalaClassifiers, m2) = ids(m1, "classifiers", Nil) val (scalaClassifiers, m2) = ids(m1, "classifiers", Nil)
check(m2, "label") 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 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 = 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 classpathSub = baseProject(utilPath / "classpath", "Classpath")
val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub, launchInterfaceSub) 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 datatypeSub = baseProject("util" /"datatype", "Datatype Generator", ioSub)
val testSub = project("scripted", "Test", new TestProject(_), 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 compileInterfaceSub = project(compilePath / "interface", "Compiler Interface", new CompilerInterfaceProject(_), interfaceSub)
val taskSub = project(tasksPath, "Tasks", new TaskProject(_), controlSub, collectionSub) 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 trackingSub = baseProject(cachePath / "tracking", "Tracking", cacheSub)
val compilerSub = project(compilePath, "Compile", new CompileProject(_), val compilerSub = project(compilePath, "Compile", new CompileProject(_),
launchInterfaceSub, interfaceSub, ivySub, ioSub, classpathSub, compileInterfaceSub) 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 altCompilerSub = baseProject("main", "Alternate Compiler Test", stdTaskSub, logSub)
val sbtSub = project(sbtPath, "Simple Build Tool", new SbtProject(_) {}, compilerSub, launchInterfaceSub) val sbtSub = project(sbtPath, "Simple Build Tool", new SbtProject(_) {}, compilerSub, launchInterfaceSub)
val installerSub = project(sbtPath / "install", "Installer", new InstallerProject(_) {}, sbtSub) 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 : _*) def baseProject(path: Path, name: String, deps: Project*) = project(path, name, new Base(_), deps : _*)
/* Multi-subproject paths */ /* Multi-subproject paths */
@ -90,6 +92,10 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths
{ {
override def testClasspath = super.testClasspath +++ compilerSub.testClasspath --- compilerInterfaceClasspath 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 IOProject(info: ProjectInfo) extends Base(info) with TestDependencies
class TaskProject(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 deliverIvyModule = newIvyModule(deliverModuleSettings)
def publishModuleSettings = deliverModuleSettings def publishModuleSettings = deliverModuleSettings
def publishIvyModule = newIvyModule(publishModuleSettings) 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. */ /** True if the default implicit extensions should be used when determining classpaths. The default value is true. */
def defaultConfigurationExtensions = true def defaultConfigurationExtensions = true
/** If true, verify that explicit dependencies on Scala libraries use the same version as scala.version. */ /** 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 case _ => None
} }
} }
/** Includes the Provided configuration on the Compile classpath, the Compile configuration on the Runtime classpath, /** Includes the Compile configuration on the Runtime classpath, and Compile and Runtime on the Test classpath.
* and Compile and Runtime on the Test classpath. Including Provided can be disabled by setting * Including Compile and Runtime can be disabled by setting defaultConfigurationExtensions to false.*/
* includeProvidedWithCompile to false. Including Compile and Runtime can be disabled by setting
* defaultConfigurationExtensions to false.*/
override def managedClasspath(config: Configuration) = override def managedClasspath(config: Configuration) =
{ {
import Configurations.{Compile, CompilerPlugin, Default, Provided, Runtime, Test} import Configurations.{Compile, Default, Runtime, Test}
val baseClasspath = configurationClasspath(config) val baseClasspath = configurationClasspath(config)
config match config match
{ {
case Compile => case Compile => baseClasspath +++ managedClasspath(Default)
val baseCompileClasspath = baseClasspath +++ managedClasspath(Default)
if(includeProvidedWithCompile)
baseCompileClasspath +++ managedClasspath(Provided)
else
baseCompileClasspath
case Runtime if defaultConfigurationExtensions => baseClasspath +++ managedClasspath(Compile) case Runtime if defaultConfigurationExtensions => baseClasspath +++ managedClasspath(Compile)
case Test if defaultConfigurationExtensions => baseClasspath +++ managedClasspath(Runtime) case Test if defaultConfigurationExtensions => baseClasspath +++ managedClasspath(Runtime)
case _ => baseClasspath case _ => baseClasspath

View File

@ -5,140 +5,8 @@ package sbt
import scala.collection.mutable.{Buffer, HashMap, ListBuffer} 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 trait Logger extends AbstractLogger with xsbt.CompileLogger with IvyLogger
{
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)
}
/** A logger that can buffer the logging done on it by currently executing Thread and /** 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 * 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. * 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 override lazy val ansiCodesSupported = delegate.ansiCodesSupported
private[this] val buffers = wrap.Wrappers.weakMap[Thread, Buffer[LogEvent]] 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 = def control(event: ControlEvent.Value, message: => String): Unit =
doBufferable(Level.Info, new ControlEvent(event, message), _.control(event, message)) 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) 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 synchronized
{ {
if(condition) 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 val ContinuousCompilePollDelaySeconds = 1
/** The list of logging levels.*/ /** 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.*/ /** The list of all interactive commands other than logging level.*/
private def basicCommands: Iterable[String] = TreeSet(ShowProjectsAction, ShowActions, ShowCurrent, HelpAction, private def basicCommands: Iterable[String] = TreeSet(ShowProjectsAction, ShowActions, ShowCurrent, HelpAction,
RebootCommand, TraceCommand, ContinuousCompileCommand, ProjectConsoleAction, BuilderCommand) ++ 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(RebootCommand, "Reloads sbt, picking up modifications to sbt.version or scala.version and recompiling modified project definitions.")
printCmd(HelpAction, "Displays this help message.") printCmd(HelpAction, "Displays this help message.")
printCmd(ShowCurrent, "Shows the current project, Scala version, and logging level.") 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(TraceCommand + " " + validTraceArguments, "Configures stack trace logging. " + traceExplanation)
printCmd(ProjectAction + " <project name>", "Sets the currently active project.") printCmd(ProjectAction + " <project name>", "Sets the currently active project.")
printCmd(ShowProjectsAction, "Shows all available projects.") 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 for(ls <- arbList[List[ToLog]].arbitrary; lv <- genLevel) yield
new Output(ls, lv) 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 removeNewlines(s: String) = s.replaceAll("""[\n\r]+""", "")
def addNewline(l: ToLog): ToLog = def addNewline(l: ToLog): ToLog =

View File

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

View File

@ -9,4 +9,24 @@ final case class Incomplete(tpe: IValue = Error, message: Option[String] = None,
object Incomplete extends Enumeration { object Incomplete extends Enumeration {
val Skipped, Error = Value 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 package sbt
import Node._
import Types._ import Types._
trait Node[A[_], T] trait Node[A[_], T]
{ {
type Inputs <: MList[A] type Mixed <: MList[A]
type Results = Inputs#Map[Result] type MixedResults = Mixed#Map[Result]
type Uniform
val inputs: Inputs val mixedIn: Mixed
def unitDependencies: Iterable[A[_]] val uniformIn: Seq[A[Uniform]]
def work(results: Results, units: UnitResults[A]): Either[A[T], T] def work(mixed: MixedResults, uniform: Seq[Result[Uniform]]): 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)]
} }

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 org.scalacheck._
import Prop._ import Prop._
import TaskGen._ import TaskGen._
import Task._
object TaskRunnerSpec extends Properties("TaskRunner") object ExecuteSpec extends Properties("Execute")
{ {
val iGen = Arbitrary.arbInt.arbitrary val iGen = Arbitrary.arbInt.arbitrary
property("evaluates simple task") = forAll(iGen, MaxWorkersGen) { (i: Int, workers: Int) => property("evaluates simple task") = forAll(iGen, MaxWorkersGen) { (i: Int, workers: Int) =>
("Workers: " + workers) |: ("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) |: ("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) checkResult(result, i)
} }
} }*/
property("evaluates simple mapped task") = forAll(iGen, MaxTasksGen, MaxWorkersGen) { (i: Int, times: Int, workers: Int) => property("evaluates simple mapped task") = forAll(iGen, MaxTasksGen, MaxWorkersGen) { (i: Int, times: Int, workers: Int) =>
("Workers: " + workers) |: ("Value: " + i) |: ("Times: " + times) |: ("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) 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) |: ("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)) 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) => property("evaluates simple bind") = forAll(iGen, MaxTasksGen, MaxWorkersGen) { (i: Int, times: Int, workers: Int) =>
("Workers: " + workers) |: ("Value: " + i) |: ("Times: " + times) |: ("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) 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 org.scalacheck._
import Gen.choose import Gen.choose
object TaskGen object TaskGen
{ {
// upper bounds to make the tests finish in reasonable time // upper bounds to make the tests finish in reasonable time
val MaxTasks = 10000 val MaxTasks = 100
val MaxWorkers = 257 val MaxWorkers = 29
val MaxJoin = 100 val MaxJoin = 100
val MaxTasksGen = choose(0, MaxTasks) val MaxTasksGen = choose(0, MaxTasks)

View File

@ -1,8 +1,9 @@
import xsbt._ package sbt
import org.scalacheck._ import org.scalacheck._
import Prop._ import Prop._
import TaskGen._ import TaskGen._
import Task._
object TaskRunnerCircularTest extends Properties("TaskRunner Circular") 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 _ } property("Allows references to completed tasks") = forAllNoShrink(MaxTasksGen, MaxWorkersGen) { allowedReference _ }
final def allowedReference(intermediate: Int, workers: Int) = 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] = def iterate(task: Task[Int]): Task[Int] =
task bind { t => task flatMap { t =>
if(t <= 0) if(t <= 0)
top top
else else
iterate(Task(t-1) named (t-1).toString) iterate(pure((t-1).toString, t-1) )
} }
try { checkResult(TaskRunner(iterate(top), workers), intermediate) } try { checkResult(tryRun(iterate(top), true, workers), intermediate) }
catch { case e: CircularDependency => ("Unexpected exception: " + e) |: false } catch { case i: Incomplete if cyclic(i) => ("Unexpected cyclic exception: " + i) |: false }
} }
final def checkCircularReferences(intermediate: Int, workers: Int) = 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] = def iterate(task: Task[Int], i: Int): Task[Int] =
{ {
lazy val it: Task[Int] = lazy val it: Task[Int] =
task bind { t => task flatMap { t =>
if(t <= 0) if(t <= 0)
top top
else else
iterate(Task(t-1) named (t-1).toString, i-1) iterate(pure((t-1).toString, t-1), i-1)
} named("it_" + i) }
it it
} }
try { TaskRunner(top, workers); false } try { tryRun(top, true, workers); false }
catch { case TasksFailed(failures) => failures.exists(_.exception.isInstanceOf[CircularDependency]) } 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 org.scalacheck._
import Prop._ import Prop._
import Task._ import Task._
import TaskGen._ import TaskGen._
import Math.abs import math.abs
object TaskRunnerForkTest extends Properties("TaskRunner Fork") object TaskRunnerForkTest extends Properties("TaskRunner Fork")
{ {
property("fork m tasks and wait for all to complete") = forAll(MaxTasksGen, MaxWorkersGen) { (m: Int, workers: Int) => property("fork m tasks and wait for all to complete") = forAll(MaxTasksGen, MaxWorkersGen) { (m: Int, workers: Int) =>
val values = (0 until m).toList 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 true
} }
property("Fork and reduce 2") = forAll(MaxTasksGen, MaxWorkersGen) { (m: Int, workers: Int) => property("Fork and reduce 2") = forAll(MaxTasksGen, MaxWorkersGen) { (m: Int, workers: Int) =>
(m > 1) ==> { (m > 1) ==> {
val task = (0 to m) fork {_ * 10} reduce{_ + _} 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) => 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 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) def inner(i: Int) = List.range(0, b).map(j => pure(j.toString, j)).join
TaskRunner( List.range(0,a).map(inner).join.named("Outermost join"), workers) tryRun( List.range(0,a).map(inner).join, false, workers)
} }
property("fork and reduce") = forAll(TaskListGen, MaxWorkersGen) { (m: List[Int], workers: Int) => property("fork and reduce") = forAll(TaskListGen, MaxWorkersGen) { (m: List[Int], workers: Int) =>
(!m.isEmpty) ==> { (!m.isEmpty) ==> {
val expected = m.reduceLeft(_+_) 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 org.scalacheck._
import Prop._ import Prop._
import TaskGen._ import TaskGen._
import Task._
object TaskRunnerCallTest extends Properties("TaskRunner Call") object TaskRunnerCallTest extends Properties("TaskRunner Call")
{ {
@ -11,7 +12,7 @@ object TaskRunnerCallTest extends Properties("TaskRunner Call")
val f = fibDirect(i) val f = fibDirect(i)
("Workers: " + workers) |: ("i: " + i) |: ("fib(i): " + f) |: ("Workers: " + workers) |: ("i: " + i) |: ("fib(i): " + f) |:
{ {
def result = TaskRunner( fibTask(i), workers) def result = tryRun( fibTask(i), false, workers)
checkResult(result, f) checkResult(result, f)
} }
} }
@ -23,11 +24,11 @@ object TaskRunnerCallTest extends Properties("TaskRunner Call")
(index, x1, x2) => (index, x1, x2) =>
{ {
if(index == i) if(index == i)
Task(x2) pure(x2)
else else
iterate( (index+1, x2, x1+x2) ) 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) ) iterate( (1, 0, 1) )
} }
final def fibDirect(i: Int): Int = 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 org.scalacheck._
import Prop._ import Prop._
import TaskGen._ import TaskGen._
import Task._
import Types._
object TaskRunnerSortTest extends Properties("TaskRunnerSort") object TaskRunnerSortTest extends Properties("TaskRunnerSort")
{ {
@ -12,31 +17,33 @@ object TaskRunnerSortTest extends Properties("TaskRunnerSort")
java.util.Arrays.sort(sorted) java.util.Arrays.sort(sorted)
("Workers: " + workers) |: ("Array: " + a.toList) |: ("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) 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) if(a.length < 2)
a a
else else
{ {
val pivot = a(0) 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) 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) if(a.length < 200)
Task(sortDirect(a)) pure(sortDirect(a))
else else
{ {
Task(a) bind { a => pure(a) flatMap { a =>
val pivot = a(0) val pivot = a(0)
val (lt,gte) = a.projection.drop(1).partition(_ < pivot) val (lt,gte) = a.view.drop(1).partition(_ < pivot)
(sort(lt), sort(gte)) map { _ ++ List(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._ import org.scalacheck.Prop._
@ -14,8 +17,8 @@ object checkResult
} }
catch catch
{ {
case TasksFailed(failures) => case i: Incomplete =>
failures.foreach(f => f.exception.printStackTrace) println(Incomplete.show(i, true))
"One or more tasks failed" |: false "One or more tasks failed" |: false
case e => case e =>
e.printStackTrace e.printStackTrace

View File

@ -26,3 +26,9 @@ final case class HCons[H, T <: HList](head : H, tail : T) extends HList
def up = MCons[H,tail.Up, Id](head, tail.up) def up = MCons[H,tail.Up, Id](head, tail.up)
def :+: [G](g: G): G :+: H :+: T = HCons(g, this) 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

@ -42,3 +42,10 @@ sealed class MNil extends MList[Nothing]
def toList = Nil 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 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 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) } 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 object TypeFunctions extends TypeFunctions
@ -30,3 +34,6 @@ object ~>
val Id: Id ~> Id = new (Id ~> Id) { def apply[T](a: T): T = a } val Id: Id ~> Id = new (Id ~> Id) { def apply[T](a: T): T = a }
implicit def tcIdEquals: (Id ~> Id) = Id 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) = def translate[T](msg: => String)(f: => T) =
try { f } 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] = def wideConvert[T](f: => T): Either[Throwable, T] =
try { Right(f) } 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] = def convert[T](f: => T): Either[Exception, T] =
try { Right(f) } try { Right(f) }
catch { case e: Exception => Left(e) } catch { case e: Exception => Left(e) }

View File

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

View File

@ -399,4 +399,36 @@ object IO
/** Splits a String around path separator characters. */ /** Splits a String around path separator characters. */
def pathSplit(s: String) = PathSeparatorPattern.split(s) 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 /* 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.*/ /** 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 private var level: Level.Value = Level.Info
def getLevel = level def getLevel = level
def setLevel(newLevel: Level.Value) { level = newLevel } def setLevel(newLevel: Level.Value) { level = newLevel }
def enableTrace(flag: Boolean) { traceEnabledVar = flag } def setTrace(level: Int) { traceEnabledVar = level }
def traceEnabled = traceEnabledVar def getTrace = traceEnabledVar
} }

View File

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

View File

@ -1,7 +1,7 @@
/* sbt -- Simple Build Tool /* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah * Copyright 2008, 2009, 2010 Mark Harrah
*/ */
package xsbt package sbt
object ConsoleLogger object ConsoleLogger
{ {
@ -17,10 +17,12 @@ object ConsoleLogger
} }
/** A logger that logs to the console. On supported systems, the level labels are /** 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 class ConsoleLogger extends BasicLogger
{ {
import ConsoleLogger.formatEnabled override def ansiCodesSupported = ConsoleLogger.formatEnabled
def messageColor(level: Level.Value) = Console.RESET def messageColor(level: Level.Value) = Console.RESET
def labelColor(level: Level.Value) = def labelColor(level: Level.Value) =
level match level match
@ -39,8 +41,9 @@ class ConsoleLogger extends BasicLogger
def trace(t: => Throwable): Unit = def trace(t: => Throwable): Unit =
System.out.synchronized System.out.synchronized
{ {
if(traceEnabled) val traceLevel = getTrace
t.printStackTrace if(traceLevel >= 0)
System.out.synchronized { System.out.print(StackTrace.trimmed(t, traceLevel)) }
} }
def log(level: Level.Value, message: => String) def log(level: Level.Value, message: => String)
{ {
@ -49,7 +52,7 @@ class ConsoleLogger extends BasicLogger
} }
private def setColor(color: String) private def setColor(color: String)
{ {
if(formatEnabled) if(ansiCodesSupported)
System.out.synchronized { System.out.print(color) } System.out.synchronized { System.out.print(color) }
} }
private def log(labelColor: String, label: String, messageColor: String, message: String): Unit = 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 /* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah * Copyright 2008, 2009 Mark Harrah
*/ */
package xsbt package sbt
/** An enumeration defining the levels available for logging. A level includes all of the levels /** 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).*/ * 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 Debug = Value(1, "debug")
val Info = Value(2, "info") val Info = Value(2, "info")
@ -16,10 +16,8 @@ object Level extends Enumeration with NotNull
* label is also defined here. */ * label is also defined here. */
val SuccessLabel = "success" 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. */ /** 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. */ /** 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 /* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah * Copyright 2008, 2009 Mark Harrah
*/ */
package xsbt package sbt
sealed trait LogEvent extends NotNull sealed trait LogEvent extends NotNull
final class Success(val msg: String) extends LogEvent final class Success(val msg: String) extends LogEvent
final class Log(val level: Level.Value, 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 Trace(val exception: Throwable) extends LogEvent
final class SetLevel(val newLevel: Level.Value) 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 final class ControlEvent(val event: ControlEvent.Value, val msg: String) extends LogEvent
object ControlEvent extends Enumeration object ControlEvent extends Enumeration

View File

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