mirror of https://github.com/sbt/sbt.git
Merge branch '0.9' of github.com:harrah/xsbt into 0.9
This commit is contained in:
commit
1ee470282d
|
|
@ -2,7 +2,6 @@ package xsbt
|
|||
|
||||
import sbinary.{CollectionTypes, Format, JavaFormats}
|
||||
import java.io.File
|
||||
import scala.reflect.Manifest
|
||||
|
||||
trait Cache[I,O]
|
||||
{
|
||||
|
|
@ -23,13 +22,6 @@ object Cache extends BasicCacheImplicits with SBinaryFormats with HListCacheImpl
|
|||
def wrapOutputCache[O,DO](implicit convert: O => DO, reverse: DO => O, base: OutputCache[DO]): OutputCache[O] =
|
||||
new WrappedOutputCache[O,DO](convert, reverse, base)
|
||||
|
||||
def apply[I,O](file: File)(f: I => Task[O])(implicit cache: Cache[I,O]): I => Task[O] =
|
||||
in =>
|
||||
cache(file)(in) match
|
||||
{
|
||||
case Left(value) => Task(value)
|
||||
case Right(store) => f(in) map { out => store(out); out }
|
||||
}
|
||||
def cached[I,O](file: File)(f: I => O)(implicit cache: Cache[I,O]): I => O =
|
||||
in =>
|
||||
cache(file)(in) match
|
||||
|
|
@ -61,4 +53,4 @@ trait HListCacheImplicits extends HLists
|
|||
implicit def hConsOutputCache[H,T<:HList](implicit headCache: OutputCache[H], tailCache: OutputCache[T]): OutputCache[HCons[H,T]] =
|
||||
new HConsOutputCache(headCache, tailCache)
|
||||
implicit lazy val hNilOutputCache: OutputCache[HNil] = new HNilOutputCache
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ sealed trait ModifiedFileInfo extends FileInfo
|
|||
{
|
||||
val lastModified: Long
|
||||
}
|
||||
sealed trait PlainFileInfo extends FileInfo
|
||||
sealed trait HashModifiedFileInfo extends HashFileInfo with ModifiedFileInfo
|
||||
|
||||
private final case class PlainFile(file: File) extends PlainFileInfo
|
||||
private final case class FileHash(file: File, hash: List[Byte]) extends HashFileInfo
|
||||
private final case class FileModified(file: File, lastModified: Long) extends ModifiedFileInfo
|
||||
private final case class FileHashModified(file: File, hash: List[Byte], lastModified: Long) extends HashModifiedFileInfo
|
||||
|
|
@ -32,8 +34,6 @@ object FileInfo
|
|||
implicit def apply(file: File): F
|
||||
implicit def unapply(info: F): File = info.file
|
||||
implicit val format: Format[F]
|
||||
/*val manifest: Manifest[F]
|
||||
def formatManifest: Manifest[Format[F]] = CacheIO.manifest[Format[F]]*/
|
||||
import Cache._
|
||||
implicit def infoInputCache: InputCache[File] = wrapInputCache[File,F]
|
||||
implicit def infoOutputCache: OutputCache[File] = wrapOutputCache[File,F]
|
||||
|
|
@ -41,7 +41,6 @@ object FileInfo
|
|||
object full extends Style
|
||||
{
|
||||
type F = HashModifiedFileInfo
|
||||
//val manifest: Manifest[F] = CacheIO.manifest[HashModifiedFileInfo]
|
||||
implicit def apply(file: File): HashModifiedFileInfo = make(file, Hash(file).toList, file.lastModified)
|
||||
def make(file: File, hash: List[Byte], lastModified: Long): HashModifiedFileInfo = FileHashModified(file.getAbsoluteFile, hash, lastModified)
|
||||
implicit val format: Format[HashModifiedFileInfo] = wrap(f => (f.file, f.hash, f.lastModified), tupled(make _))
|
||||
|
|
@ -49,7 +48,6 @@ object FileInfo
|
|||
object hash extends Style
|
||||
{
|
||||
type F = HashFileInfo
|
||||
//val manifest: Manifest[F] = CacheIO.manifest[HashFileInfo]
|
||||
implicit def apply(file: File): HashFileInfo = make(file, computeHash(file).toList)
|
||||
def make(file: File, hash: List[Byte]): HashFileInfo = FileHash(file.getAbsoluteFile, hash)
|
||||
implicit val format: Format[HashFileInfo] = wrap(f => (f.file, f.hash), tupled(make _))
|
||||
|
|
@ -58,11 +56,17 @@ object FileInfo
|
|||
object lastModified extends Style
|
||||
{
|
||||
type F = ModifiedFileInfo
|
||||
//val manifest: Manifest[F] = CacheIO.manifest[ModifiedFileInfo]
|
||||
implicit def apply(file: File): ModifiedFileInfo = make(file, file.lastModified)
|
||||
def make(file: File, lastModified: Long): ModifiedFileInfo = FileModified(file.getAbsoluteFile, lastModified)
|
||||
implicit val format: Format[ModifiedFileInfo] = wrap(f => (f.file, f.lastModified), tupled(make _))
|
||||
}
|
||||
object exists extends Style
|
||||
{
|
||||
type F = PlainFileInfo
|
||||
implicit def apply(file: File): PlainFileInfo = make(file)
|
||||
def make(file: File): PlainFileInfo = PlainFile(file.getAbsoluteFile)
|
||||
implicit val format: Format[PlainFileInfo] = wrap(_.file, make)
|
||||
}
|
||||
}
|
||||
|
||||
final case class FilesInfo[F <: FileInfo] private(files: Set[F]) extends NotNull
|
||||
|
|
@ -92,4 +96,5 @@ object FilesInfo
|
|||
lazy val full: Style = new BasicStyle(FileInfo.full)
|
||||
lazy val hash: Style = new BasicStyle(FileInfo.hash)
|
||||
lazy val lastModified: Style = new BasicStyle(FileInfo.lastModified)
|
||||
lazy val exists: Style = new BasicStyle(FileInfo.exists)
|
||||
}
|
||||
|
|
@ -7,22 +7,23 @@ object CacheTest// extends Properties("Cache test")
|
|||
val lengthCache = new File("/tmp/length-cache")
|
||||
val cCache = new File("/tmp/c-cache")
|
||||
|
||||
import Task._
|
||||
import Cache._
|
||||
import FileInfo.hash._
|
||||
def test
|
||||
{
|
||||
val createTask = Task { new File("test") }
|
||||
lazy val create = new File("test")
|
||||
|
||||
val length = (f: File) => { println("File length: " + f.length); f.length }
|
||||
val cachedLength = cached(lengthCache) ( length )
|
||||
val length = cached(lengthCache) {
|
||||
(f: File) => { println("File length: " + f.length); f.length }
|
||||
}
|
||||
|
||||
val lengthTask = createTask map cachedLength
|
||||
lazy val fileLength = length(create)
|
||||
|
||||
val c = (file: File, len: Long) => { println("File: " + file + ", length: " + len); len :: file :: HNil }
|
||||
val cTask = (createTask :: lengthTask :: TNil) map cached(cCache) { case (file :: len :: HNil) => c(file, len) }
|
||||
|
||||
try { TaskRunner(cTask) }
|
||||
catch { case TasksFailed(failures) => failures.foreach(_.exception.printStackTrace) }
|
||||
val c = cached(cCache) { (in: (File :: Long :: HNil)) =>
|
||||
val file :: len :: HNil = in
|
||||
println("File: " + file + " (" + file.exists + "), length: " + len)
|
||||
(len+1) :: file :: HNil
|
||||
}
|
||||
c(create :: fileLength :: HNil)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,112 +7,152 @@ import java.io.{File,IOException}
|
|||
import CacheIO.{fromFile, toFile}
|
||||
import sbinary.Format
|
||||
import scala.reflect.Manifest
|
||||
import Task.{iterableToBuilder, iterableToForkBuilder}
|
||||
import xsbt.FileUtilities.{delete, read, write}
|
||||
|
||||
/* A proper implementation of fileTask that tracks inputs and outputs properly
|
||||
|
||||
def fileTask(cacheBaseDirectory: Path)(inputs: PathFinder, outputs: PathFinder)(action: => Unit): Task =
|
||||
fileTask(cacheBaseDirectory, FilesInfo.hash, FilesInfo.lastModified)
|
||||
def fileTask(cacheBaseDirectory: Path, inStyle: FilesInfo.Style, outStyle: FilesInfo.Style)(inputs: PathFinder, outputs: PathFinder)(action: => Unit): Task =
|
||||
{
|
||||
lazy val inCache = diffInputs(base / "in-cache", inStyle)(inputs)
|
||||
lazy val outCache = diffOutputs(base / "out-cache", outStyle)(outputs)
|
||||
task
|
||||
{
|
||||
inCache { inReport =>
|
||||
outCache { outReport =>
|
||||
if(inReport.modified.isEmpty && outReport.modified.isEmpty) () else action
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
object Tracked
|
||||
{
|
||||
/** Creates a tracker that provides the last time it was evaluated.
|
||||
* If 'useStartTime' is true, the recorded time is the start of the evaluated function.
|
||||
* If 'useStartTime' is false, the recorded time is when the evaluated function completes.
|
||||
* In both cases, the timestamp is not updated if the function throws an exception.*/
|
||||
def tstamp(cacheFile: File, useStartTime: Boolean): Timestamp = new Timestamp(cacheFile)
|
||||
/** Creates a tracker that only evaluates a function when the input has changed.*/
|
||||
def changed[O](cacheFile: File)(getValue: => O)(implicit input: InputCache[O]): Changed[O] =
|
||||
new Changed[O](getValue, cacheFile)
|
||||
|
||||
/** Creates a tracker that provides the difference between the set of input files provided for successive invocations.*/
|
||||
def diffInputs(cache: File, style: FilesInfo.Style)(files: => Set[File]): Difference =
|
||||
Difference.inputs(files, style, cache)
|
||||
/** Creates a tracker that provides the difference between the set of output files provided for successive invocations.*/
|
||||
def diffOutputs(cache: File, style: FilesInfo.Style)(files: => Set[File]): Difference =
|
||||
Difference.outputs(files, style, cache)
|
||||
}
|
||||
|
||||
trait Tracked extends NotNull
|
||||
{
|
||||
/** Cleans outputs. This operation might require information from the cache, so it should be called first if clear is also called.*/
|
||||
def clean: Task[Unit]
|
||||
/** Clears the cache. If also cleaning, 'clean' should be called first as it might require information from the cache.*/
|
||||
def clear: Task[Unit]
|
||||
/** Cleans outputs and clears the cache.*/
|
||||
def clean: Unit
|
||||
}
|
||||
class Timestamp(val cacheFile: File) extends Tracked
|
||||
class Timestamp(val cacheFile: File, useStartTime: Boolean) extends Tracked
|
||||
{
|
||||
val clean = Clean(cacheFile)
|
||||
def clear = Task.empty
|
||||
def apply[T](f: Long => Task[T]): Task[T] =
|
||||
def clean = delete(cacheFile)
|
||||
/** Reads the previous timestamp, evaluates the provided function, and then updates the timestamp.*/
|
||||
def apply[T](f: Long => T): T =
|
||||
{
|
||||
val getTimestamp = Task { readTimestamp }
|
||||
getTimestamp bind f map { result =>
|
||||
FileUtilities.write(cacheFile, System.currentTimeMillis.toString)
|
||||
result
|
||||
}
|
||||
val start = now()
|
||||
val result = f(readTimestamp)
|
||||
write(cacheFile, (if(useStartTime) start else now()).toString)
|
||||
result
|
||||
}
|
||||
private def now() = System.currentTimeMillis
|
||||
def readTimestamp: Long =
|
||||
try { FileUtilities.read(cacheFile).toLong }
|
||||
try { read(cacheFile).toLong }
|
||||
catch { case _: NumberFormatException | _: java.io.FileNotFoundException => 0 }
|
||||
}
|
||||
object Clean
|
||||
{
|
||||
def apply(src: Task[Set[File]]): Task[Unit] = src map FileUtilities.delete
|
||||
def apply(srcs: File*): Task[Unit] = Task(FileUtilities.delete(srcs))
|
||||
def apply(srcs: Set[File]): Task[Unit] = Task(FileUtilities.delete(srcs))
|
||||
}
|
||||
|
||||
class Changed[O](val task: Task[O], val cacheFile: File)(implicit input: InputCache[O]) extends Tracked
|
||||
class Changed[O](getValue: => O, val cacheFile: File)(implicit input: InputCache[O]) extends Tracked
|
||||
{
|
||||
val clean = Clean(cacheFile)
|
||||
def clear = Task.empty
|
||||
def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): Task[O2] =
|
||||
task map { value =>
|
||||
val cache =
|
||||
try { OpenResource.fileInputStream(cacheFile)(input.uptodate(value)) }
|
||||
catch { case _: IOException => new ForceResult(input)(value) }
|
||||
if(cache.uptodate)
|
||||
ifUnchanged(value)
|
||||
else
|
||||
{
|
||||
OpenResource.fileOutputStream(false)(cacheFile)(cache.update)
|
||||
ifChanged(value)
|
||||
}
|
||||
def clean = delete(cacheFile)
|
||||
def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O2 =
|
||||
{
|
||||
val value = getValue
|
||||
val cache =
|
||||
try { OpenResource.fileInputStream(cacheFile)(input.uptodate(value)) }
|
||||
catch { case _: IOException => new ForceResult(input)(value) }
|
||||
if(cache.uptodate)
|
||||
ifUnchanged(value)
|
||||
else
|
||||
{
|
||||
OpenResource.fileOutputStream(false)(cacheFile)(cache.update)
|
||||
ifChanged(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
object Difference
|
||||
{
|
||||
sealed class Constructor private[Difference](defineClean: Boolean, filesAreOutputs: Boolean) extends NotNull
|
||||
{
|
||||
def apply(filesTask: Task[Set[File]], style: FilesInfo.Style, cache: File): Difference = new Difference(filesTask, style, cache, defineClean, filesAreOutputs)
|
||||
def apply(files: Set[File], style: FilesInfo.Style, cache: File): Difference = apply(Task(files), style, cache)
|
||||
def apply(files: => Set[File], style: FilesInfo.Style, cache: File): Difference = new Difference(files, style, cache, defineClean, filesAreOutputs)
|
||||
}
|
||||
/** Provides a constructor for a Difference that removes the files from the previous run on a call to 'clean' and saves the
|
||||
* hash/last modified time of the files as they are after running the function. This means that this information must be evaluated twice:
|
||||
* before and after running the function.*/
|
||||
object outputs extends Constructor(true, true)
|
||||
/** Provides a constructor for a Difference that does nothing on a call to 'clean' and saves the
|
||||
* hash/last modified time of the files as they were prior to running the function.*/
|
||||
object inputs extends Constructor(false, false)
|
||||
}
|
||||
class Difference(val filesTask: Task[Set[File]], val style: FilesInfo.Style, val cache: File, val defineClean: Boolean, val filesAreOutputs: Boolean) extends Tracked
|
||||
class Difference(getFiles: => Set[File], val style: FilesInfo.Style, val cache: File, val defineClean: Boolean, val filesAreOutputs: Boolean) extends Tracked
|
||||
{
|
||||
val clean = if(defineClean) Clean(Task(raw(cachedFilesInfo))) else Task.empty
|
||||
val clear = Clean(cache)
|
||||
def clean =
|
||||
{
|
||||
if(defineClean) delete(raw(cachedFilesInfo)) else ()
|
||||
clearCache()
|
||||
}
|
||||
private def clearCache = delete(cache)
|
||||
|
||||
private def cachedFilesInfo = fromFile(style.formats, style.empty)(cache)(style.manifest).files
|
||||
private def raw(fs: Set[style.F]): Set[File] = fs.map(_.file)
|
||||
|
||||
def apply[T](f: ChangeReport[File] => Task[T]): Task[T] =
|
||||
filesTask bind { files =>
|
||||
val lastFilesInfo = cachedFilesInfo
|
||||
val lastFiles = raw(lastFilesInfo)
|
||||
val currentFiles = files.map(_.getAbsoluteFile)
|
||||
val currentFilesInfo = style(currentFiles)
|
||||
def apply[T](f: ChangeReport[File] => T): T =
|
||||
{
|
||||
val files = getFiles
|
||||
val lastFilesInfo = cachedFilesInfo
|
||||
val lastFiles = raw(lastFilesInfo)
|
||||
val currentFiles = files.map(_.getAbsoluteFile)
|
||||
val currentFilesInfo = style(currentFiles)
|
||||
|
||||
val report = new ChangeReport[File]
|
||||
{
|
||||
lazy val checked = currentFiles
|
||||
lazy val removed = lastFiles -- checked // all files that were included previously but not this time. This is independent of whether the files exist.
|
||||
lazy val added = checked -- lastFiles // all files included now but not previously. This is independent of whether the files exist.
|
||||
lazy val modified = raw(lastFilesInfo -- currentFilesInfo.files) ++ added
|
||||
lazy val unmodified = checked -- modified
|
||||
}
|
||||
|
||||
f(report) map { result =>
|
||||
val info = if(filesAreOutputs) style(currentFiles) else currentFilesInfo
|
||||
toFile(style.formats)(info)(cache)(style.manifest)
|
||||
result
|
||||
}
|
||||
val report = new ChangeReport[File]
|
||||
{
|
||||
lazy val checked = currentFiles
|
||||
lazy val removed = lastFiles -- checked // all files that were included previously but not this time. This is independent of whether the files exist.
|
||||
lazy val added = checked -- lastFiles // all files included now but not previously. This is independent of whether the files exist.
|
||||
lazy val modified = raw(lastFilesInfo -- currentFilesInfo.files) ++ added
|
||||
lazy val unmodified = checked -- modified
|
||||
}
|
||||
|
||||
val result = f(report)
|
||||
val info = if(filesAreOutputs) style(currentFiles) else currentFilesInfo
|
||||
toFile(style.formats)(info)(cache)(style.manifest)
|
||||
result
|
||||
}
|
||||
}
|
||||
class DependencyTracked[T](val cacheDirectory: File, val translateProducts: Boolean, cleanT: T => Unit)(implicit format: Format[T], mf: Manifest[T]) extends Tracked
|
||||
{
|
||||
private val trackFormat = new TrackingFormat[T](cacheDirectory, translateProducts)
|
||||
private def cleanAll(fs: Set[T]) = fs.foreach(cleanT)
|
||||
|
||||
val clean = Task(cleanAll(trackFormat.read.allProducts))
|
||||
val clear = Clean(cacheDirectory)
|
||||
def clean =
|
||||
{
|
||||
cleanAll(trackFormat.read.allProducts)
|
||||
delete(cacheDirectory)
|
||||
}
|
||||
|
||||
def apply[R](f: UpdateTracking[T] => Task[R]): Task[R] =
|
||||
def apply[R](f: UpdateTracking[T] => R): R =
|
||||
{
|
||||
val tracker = trackFormat.read
|
||||
f(tracker) map { result =>
|
||||
trackFormat.write(tracker)
|
||||
result
|
||||
}
|
||||
val result = f(tracker)
|
||||
trackFormat.write(tracker)
|
||||
result
|
||||
}
|
||||
}
|
||||
object InvalidateFiles
|
||||
|
|
@ -179,30 +219,29 @@ class InvalidateTransitive[T](cacheDirectory: File, translateProducts: Boolean,
|
|||
this(cacheDirectory, translateProducts, (_: T) => ())
|
||||
|
||||
private val tracked = new DependencyTracked(cacheDirectory, translateProducts, cleanT)
|
||||
def clean = tracked.clean
|
||||
def clear = tracked.clear
|
||||
|
||||
def apply[R](changes: ChangeReport[T])(f: (InvalidationReport[T], UpdateTracking[T]) => Task[R]): Task[R] =
|
||||
apply(Task(changes))(f)
|
||||
def apply[R](changesTask: Task[ChangeReport[T]])(f: (InvalidationReport[T], UpdateTracking[T]) => Task[R]): Task[R] =
|
||||
def clean
|
||||
{
|
||||
changesTask bind { changes =>
|
||||
tracked { tracker =>
|
||||
val report = InvalidateTransitive.andClean[T](tracker, _.foreach(cleanT), changes.modified)
|
||||
f(report, tracker)
|
||||
}
|
||||
tracked.clean
|
||||
tracked.clear
|
||||
}
|
||||
|
||||
def apply[R](getChanges: => ChangeReport[T])(f: (InvalidationReport[T], UpdateTracking[T]) => R): R =
|
||||
{
|
||||
val changes = getChanges
|
||||
tracked { tracker =>
|
||||
val report = InvalidateTransitive.andClean[T](tracker, _.foreach(cleanT), changes.modified)
|
||||
f(report, tracker)
|
||||
}
|
||||
}
|
||||
}
|
||||
class BasicTracked(filesTask: Task[Set[File]], style: FilesInfo.Style, cacheDirectory: File) extends Tracked
|
||||
class BasicTracked(files: => Set[File], style: FilesInfo.Style, cacheDirectory: File) extends Tracked
|
||||
{
|
||||
private val changed = Difference.inputs(filesTask, style, new File(cacheDirectory, "files"))
|
||||
private val changed = Difference.inputs(files, style, new File(cacheDirectory, "files"))
|
||||
private val invalidation = InvalidateFiles(new File(cacheDirectory, "invalidation"))
|
||||
private def onTracked(f: Tracked => Task[Unit]) = Seq(invalidation, changed).forkTasks(f).joinIgnore
|
||||
val clear = onTracked(_.clear)
|
||||
val clean = onTracked(_.clean)
|
||||
private def onTracked(f: Tracked => Unit) = { f(invalidation); f(changed) }
|
||||
def clean = onTracked(_.clean)
|
||||
|
||||
def apply[R](f: (ChangeReport[File], InvalidationReport[File], UpdateTracking[File]) => Task[R]): Task[R] =
|
||||
def apply[R](f: (ChangeReport[File], InvalidationReport[File], UpdateTracking[File]) => R): R =
|
||||
changed { sourceChanges =>
|
||||
invalidation(sourceChanges) { (report, tracking) =>
|
||||
f(sourceChanges, report, tracking)
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ private class SameAPI(a: Source, b: Source, includePrivate: Boolean)
|
|||
import SameAPI._
|
||||
/** de Bruijn levels for type parameters in source `a`*/
|
||||
private lazy val tagsA = TagTypeVariables(a)
|
||||
/** de Bruijn levels for type parameters in source `a`*/
|
||||
/** de Bruijn levels for type parameters in source `b`*/
|
||||
private lazy val tagsB = TagTypeVariables(b)
|
||||
|
||||
def debug(flag: Boolean, msg: => String): Boolean =
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class ConfigurationParser extends NotNull
|
|||
val (properties, m6) = processSection(m5, "app-properties", getAppProperties)
|
||||
val (cacheDir, m7) = processSection(m6, "ivy", getIvy)
|
||||
check(m7, "section")
|
||||
val classifiers = Classifiers("" :: scalaClassifiers, "" :: appClassifiers)
|
||||
val classifiers = Classifiers("" :: scalaClassifiers, "" :: appClassifiers) // the added "" ensures that the main jars are retrieved
|
||||
new LaunchConfiguration(scalaVersion, IvyOptions(cacheDir, classifiers, repositories), app, boot, logging, properties)
|
||||
}
|
||||
def getScala(m: LabelMap) =
|
||||
|
|
@ -43,7 +43,7 @@ class ConfigurationParser extends NotNull
|
|||
val (scalaVersion, m1) = getVersion(m, "Scala version", "scala.version")
|
||||
val (scalaClassifiers, m2) = ids(m1, "classifiers", Nil)
|
||||
check(m2, "label")
|
||||
(scalaVersion, scalaClassifiers) // the added "" ensures that the main jars are retrieved
|
||||
(scalaVersion, scalaClassifiers)
|
||||
}
|
||||
def getVersion(m: LabelMap, label: String, defaultName: String): (Version, LabelMap) = process(m, "version", processVersion(label, defaultName))
|
||||
def processVersion(label: String, defaultName: String)(value: Option[String]): Version =
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths
|
|||
val classpathSub = baseProject(utilPath / "classpath", "Classpath")
|
||||
|
||||
val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub, launchInterfaceSub)
|
||||
val logSub = baseProject(utilPath / "log", "Logging", interfaceSub)
|
||||
val logSub = project(utilPath / "log", "Logging", new LogProject(_), interfaceSub)
|
||||
val datatypeSub = baseProject("util" /"datatype", "Datatype Generator", ioSub)
|
||||
|
||||
val testSub = project("scripted", "Test", new TestProject(_), ioSub)
|
||||
|
|
@ -26,17 +26,19 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths
|
|||
val compileInterfaceSub = project(compilePath / "interface", "Compiler Interface", new CompilerInterfaceProject(_), interfaceSub)
|
||||
|
||||
val taskSub = project(tasksPath, "Tasks", new TaskProject(_), controlSub, collectionSub)
|
||||
val cacheSub = project(cachePath, "Cache", new CacheProject(_), taskSub, ioSub)
|
||||
val cacheSub = project(cachePath, "Cache", new CacheProject(_), ioSub, collectionSub)
|
||||
val trackingSub = baseProject(cachePath / "tracking", "Tracking", cacheSub)
|
||||
val compilerSub = project(compilePath, "Compile", new CompileProject(_),
|
||||
launchInterfaceSub, interfaceSub, ivySub, ioSub, classpathSub, compileInterfaceSub)
|
||||
val stdTaskSub = project(tasksPath / "standard", "Standard Tasks", new StandardTaskProject(_), trackingSub, compilerSub, apiSub)
|
||||
val stdTaskSub = project(tasksPath / "standard", "Standard Tasks", new StandardTaskProject(_), trackingSub, taskSub, compilerSub, apiSub)
|
||||
|
||||
val altCompilerSub = baseProject("main", "Alternate Compiler Test", stdTaskSub, logSub)
|
||||
|
||||
val sbtSub = project(sbtPath, "Simple Build Tool", new SbtProject(_) {}, compilerSub, launchInterfaceSub)
|
||||
val installerSub = project(sbtPath / "install", "Installer", new InstallerProject(_) {}, sbtSub)
|
||||
|
||||
lazy val dist = task { None } dependsOn(launchSub.proguard, sbtSub.publishLocal, installerSub.publishLocal)
|
||||
|
||||
def baseProject(path: Path, name: String, deps: Project*) = project(path, name, new Base(_), deps : _*)
|
||||
|
||||
/* Multi-subproject paths */
|
||||
|
|
@ -90,6 +92,10 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths
|
|||
{
|
||||
override def testClasspath = super.testClasspath +++ compilerSub.testClasspath --- compilerInterfaceClasspath
|
||||
}
|
||||
class LogProject(info: ProjectInfo) extends Base(info)
|
||||
{
|
||||
val jline = jlineDep
|
||||
}
|
||||
|
||||
class IOProject(info: ProjectInfo) extends Base(info) with TestDependencies
|
||||
class TaskProject(info: ProjectInfo) extends Base(info) with TestDependencies
|
||||
|
|
|
|||
|
|
@ -322,8 +322,6 @@ trait BasicManagedProject extends ManagedProject with ReflectiveManagedProject w
|
|||
def deliverIvyModule = newIvyModule(deliverModuleSettings)
|
||||
def publishModuleSettings = deliverModuleSettings
|
||||
def publishIvyModule = newIvyModule(publishModuleSettings)
|
||||
/** True if the 'provided' configuration should be included on the 'compile' classpath. The default value is true.*/
|
||||
def includeProvidedWithCompile = true
|
||||
/** True if the default implicit extensions should be used when determining classpaths. The default value is true. */
|
||||
def defaultConfigurationExtensions = true
|
||||
/** If true, verify that explicit dependencies on Scala libraries use the same version as scala.version. */
|
||||
|
|
@ -343,22 +341,15 @@ trait BasicManagedProject extends ManagedProject with ReflectiveManagedProject w
|
|||
case _ => None
|
||||
}
|
||||
}
|
||||
/** Includes the Provided configuration on the Compile classpath, the Compile configuration on the Runtime classpath,
|
||||
* and Compile and Runtime on the Test classpath. Including Provided can be disabled by setting
|
||||
* includeProvidedWithCompile to false. Including Compile and Runtime can be disabled by setting
|
||||
* defaultConfigurationExtensions to false.*/
|
||||
/** Includes the Compile configuration on the Runtime classpath, and Compile and Runtime on the Test classpath.
|
||||
* Including Compile and Runtime can be disabled by setting defaultConfigurationExtensions to false.*/
|
||||
override def managedClasspath(config: Configuration) =
|
||||
{
|
||||
import Configurations.{Compile, CompilerPlugin, Default, Provided, Runtime, Test}
|
||||
import Configurations.{Compile, Default, Runtime, Test}
|
||||
val baseClasspath = configurationClasspath(config)
|
||||
config match
|
||||
{
|
||||
case Compile =>
|
||||
val baseCompileClasspath = baseClasspath +++ managedClasspath(Default)
|
||||
if(includeProvidedWithCompile)
|
||||
baseCompileClasspath +++ managedClasspath(Provided)
|
||||
else
|
||||
baseCompileClasspath
|
||||
case Compile => baseClasspath +++ managedClasspath(Default)
|
||||
case Runtime if defaultConfigurationExtensions => baseClasspath +++ managedClasspath(Compile)
|
||||
case Test if defaultConfigurationExtensions => baseClasspath +++ managedClasspath(Runtime)
|
||||
case _ => baseClasspath
|
||||
|
|
|
|||
|
|
@ -5,140 +5,8 @@ package sbt
|
|||
|
||||
import scala.collection.mutable.{Buffer, HashMap, ListBuffer}
|
||||
|
||||
sealed trait LogEvent extends NotNull
|
||||
final class Success(val msg: String) extends LogEvent
|
||||
final class Log(val level: Level.Value, val msg: String) extends LogEvent
|
||||
final class Trace(val exception: Throwable) extends LogEvent
|
||||
final class SetLevel(val newLevel: Level.Value) extends LogEvent
|
||||
final class SetTrace(val level: Int) extends LogEvent
|
||||
final class ControlEvent(val event: ControlEvent.Value, val msg: String) extends LogEvent
|
||||
|
||||
object ControlEvent extends Enumeration
|
||||
{
|
||||
val Start, Header, Finish = Value
|
||||
}
|
||||
|
||||
abstract class Logger extends xsbt.CompileLogger with IvyLogger
|
||||
{
|
||||
def getLevel: Level.Value
|
||||
def setLevel(newLevel: Level.Value)
|
||||
def setTrace(flag: Int)
|
||||
def getTrace: Int
|
||||
final def traceEnabled = getTrace >= 0
|
||||
def ansiCodesSupported = false
|
||||
|
||||
def atLevel(level: Level.Value) = level.id >= getLevel.id
|
||||
def trace(t: => Throwable): Unit
|
||||
final def verbose(message: => String): Unit = debug(message)
|
||||
final def debug(message: => String): Unit = log(Level.Debug, message)
|
||||
final def info(message: => String): Unit = log(Level.Info, message)
|
||||
final def warn(message: => String): Unit = log(Level.Warn, message)
|
||||
final def error(message: => String): Unit = log(Level.Error, message)
|
||||
def success(message: => String): Unit
|
||||
def log(level: Level.Value, message: => String): Unit
|
||||
def control(event: ControlEvent.Value, message: => String): Unit
|
||||
|
||||
def logAll(events: Seq[LogEvent]): Unit
|
||||
/** Defined in terms of other methods in Logger and should not be called from them. */
|
||||
final def log(event: LogEvent)
|
||||
{
|
||||
event match
|
||||
{
|
||||
case s: Success => success(s.msg)
|
||||
case l: Log => log(l.level, l.msg)
|
||||
case t: Trace => trace(t.exception)
|
||||
case setL: SetLevel => setLevel(setL.newLevel)
|
||||
case setT: SetTrace => setTrace(setT.level)
|
||||
case c: ControlEvent => control(c.event, c.msg)
|
||||
}
|
||||
}
|
||||
|
||||
import xsbti.F0
|
||||
def debug(msg: F0[String]): Unit = log(Level.Debug, msg)
|
||||
def warn(msg: F0[String]): Unit = log(Level.Warn, msg)
|
||||
def info(msg: F0[String]): Unit = log(Level.Info, msg)
|
||||
def error(msg: F0[String]): Unit = log(Level.Error, msg)
|
||||
def trace(msg: F0[Throwable]) = trace(msg.apply)
|
||||
def log(level: Level.Value, msg: F0[String]): Unit = log(level, msg.apply)
|
||||
}
|
||||
|
||||
/** Implements the level-setting methods of Logger.*/
|
||||
abstract class BasicLogger extends Logger
|
||||
{
|
||||
private var traceEnabledVar = java.lang.Integer.MAX_VALUE
|
||||
private var level: Level.Value = Level.Info
|
||||
def getLevel = level
|
||||
def setLevel(newLevel: Level.Value) { level = newLevel }
|
||||
def setTrace(level: Int) { traceEnabledVar = level }
|
||||
def getTrace = traceEnabledVar
|
||||
}
|
||||
|
||||
final class SynchronizedLogger(delegate: Logger) extends Logger
|
||||
{
|
||||
override lazy val ansiCodesSupported = delegate.ansiCodesSupported
|
||||
def getLevel = { synchronized { delegate.getLevel } }
|
||||
def setLevel(newLevel: Level.Value) { synchronized { delegate.setLevel(newLevel) } }
|
||||
def setTrace(level: Int) { synchronized { delegate.setTrace(level) } }
|
||||
def getTrace: Int = { synchronized { delegate.getTrace } }
|
||||
|
||||
def trace(t: => Throwable) { synchronized { delegate.trace(t) } }
|
||||
def log(level: Level.Value, message: => String) { synchronized { delegate.log(level, message) } }
|
||||
def success(message: => String) { synchronized { delegate.success(message) } }
|
||||
def control(event: ControlEvent.Value, message: => String) { synchronized { delegate.control(event, message) } }
|
||||
def logAll(events: Seq[LogEvent]) { synchronized { delegate.logAll(events) } }
|
||||
}
|
||||
|
||||
final class MultiLogger(delegates: List[Logger]) extends BasicLogger
|
||||
{
|
||||
override lazy val ansiCodesSupported = delegates.forall(_.ansiCodesSupported)
|
||||
override def setLevel(newLevel: Level.Value)
|
||||
{
|
||||
super.setLevel(newLevel)
|
||||
dispatch(new SetLevel(newLevel))
|
||||
}
|
||||
override def setTrace(level: Int)
|
||||
{
|
||||
super.setTrace(level)
|
||||
dispatch(new SetTrace(level))
|
||||
}
|
||||
def trace(t: => Throwable) { dispatch(new Trace(t)) }
|
||||
def log(level: Level.Value, message: => String) { dispatch(new Log(level, message)) }
|
||||
def success(message: => String) { dispatch(new Success(message)) }
|
||||
def logAll(events: Seq[LogEvent]) { delegates.foreach(_.logAll(events)) }
|
||||
def control(event: ControlEvent.Value, message: => String) { delegates.foreach(_.control(event, message)) }
|
||||
private def dispatch(event: LogEvent) { delegates.foreach(_.log(event)) }
|
||||
}
|
||||
|
||||
/** A filter logger is used to delegate messages but not the logging level to another logger. This means
|
||||
* that messages are logged at the higher of the two levels set by this logger and its delegate.
|
||||
* */
|
||||
final class FilterLogger(delegate: Logger) extends BasicLogger
|
||||
{
|
||||
override lazy val ansiCodesSupported = delegate.ansiCodesSupported
|
||||
def trace(t: => Throwable)
|
||||
{
|
||||
if(traceEnabled)
|
||||
delegate.trace(t)
|
||||
}
|
||||
override def setTrace(level: Int) { delegate.setTrace(level) }
|
||||
override def getTrace = delegate.getTrace
|
||||
def log(level: Level.Value, message: => String)
|
||||
{
|
||||
if(atLevel(level))
|
||||
delegate.log(level, message)
|
||||
}
|
||||
def success(message: => String)
|
||||
{
|
||||
if(atLevel(Level.Info))
|
||||
delegate.success(message)
|
||||
}
|
||||
def control(event: ControlEvent.Value, message: => String)
|
||||
{
|
||||
if(atLevel(Level.Info))
|
||||
delegate.control(event, message)
|
||||
}
|
||||
def logAll(events: Seq[LogEvent]): Unit = delegate.logAll(events)
|
||||
}
|
||||
trait Logger extends AbstractLogger with xsbt.CompileLogger with IvyLogger
|
||||
|
||||
/** A logger that can buffer the logging done on it by currently executing Thread and
|
||||
* then can flush the buffer to the delegate logger provided in the constructor. Use
|
||||
|
|
@ -151,7 +19,7 @@ final class FilterLogger(delegate: Logger) extends BasicLogger
|
|||
*
|
||||
* This logger is thread-safe.
|
||||
* */
|
||||
final class BufferedLogger(delegate: Logger) extends Logger
|
||||
final class BufferedLogger(delegate: AbstractLogger) extends Logger
|
||||
{
|
||||
override lazy val ansiCodesSupported = delegate.ansiCodesSupported
|
||||
private[this] val buffers = wrap.Wrappers.weakMap[Thread, Buffer[LogEvent]]
|
||||
|
|
@ -244,9 +112,9 @@ final class BufferedLogger(delegate: Logger) extends Logger
|
|||
}
|
||||
def control(event: ControlEvent.Value, message: => String): Unit =
|
||||
doBufferable(Level.Info, new ControlEvent(event, message), _.control(event, message))
|
||||
private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: Logger => Unit): Unit =
|
||||
private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit =
|
||||
doBufferableIf(atLevel(level), appendIfBuffered, doUnbuffered)
|
||||
private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: Logger => Unit): Unit =
|
||||
private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit =
|
||||
synchronized
|
||||
{
|
||||
if(condition)
|
||||
|
|
@ -259,134 +127,3 @@ final class BufferedLogger(delegate: Logger) extends Logger
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ConsoleLogger
|
||||
{
|
||||
private val formatEnabled = ansiSupported && !formatExplicitlyDisabled
|
||||
|
||||
private[this] def formatExplicitlyDisabled = java.lang.Boolean.getBoolean("sbt.log.noformat")
|
||||
private[this] def ansiSupported =
|
||||
try { jline.Terminal.getTerminal.isANSISupported }
|
||||
catch { case e: Exception => !isWindows }
|
||||
|
||||
private[this] def os = System.getProperty("os.name")
|
||||
private[this] def isWindows = os.toLowerCase.indexOf("windows") >= 0
|
||||
}
|
||||
|
||||
/** A logger that logs to the console. On supported systems, the level labels are
|
||||
* colored.
|
||||
*
|
||||
* This logger is not thread-safe.*/
|
||||
class ConsoleLogger extends BasicLogger
|
||||
{
|
||||
override def ansiCodesSupported = ConsoleLogger.formatEnabled
|
||||
def messageColor(level: Level.Value) = Console.RESET
|
||||
def labelColor(level: Level.Value) =
|
||||
level match
|
||||
{
|
||||
case Level.Error => Console.RED
|
||||
case Level.Warn => Console.YELLOW
|
||||
case _ => Console.RESET
|
||||
}
|
||||
def successLabelColor = Console.GREEN
|
||||
def successMessageColor = Console.RESET
|
||||
override def success(message: => String)
|
||||
{
|
||||
if(atLevel(Level.Info))
|
||||
log(successLabelColor, Level.SuccessLabel, successMessageColor, message)
|
||||
}
|
||||
def trace(t: => Throwable): Unit =
|
||||
System.out.synchronized
|
||||
{
|
||||
val traceLevel = getTrace
|
||||
if(traceLevel >= 0)
|
||||
System.out.synchronized { System.out.print(StackTrace.trimmed(t, traceLevel)) }
|
||||
}
|
||||
def log(level: Level.Value, message: => String)
|
||||
{
|
||||
if(atLevel(level))
|
||||
log(labelColor(level), level.toString, messageColor(level), message)
|
||||
}
|
||||
private def setColor(color: String)
|
||||
{
|
||||
if(ansiCodesSupported)
|
||||
System.out.synchronized { System.out.print(color) }
|
||||
}
|
||||
private def log(labelColor: String, label: String, messageColor: String, message: String): Unit =
|
||||
System.out.synchronized
|
||||
{
|
||||
for(line <- message.split("""\n"""))
|
||||
{
|
||||
setColor(Console.RESET)
|
||||
System.out.print('[')
|
||||
setColor(labelColor)
|
||||
System.out.print(label)
|
||||
setColor(Console.RESET)
|
||||
System.out.print("] ")
|
||||
setColor(messageColor)
|
||||
System.out.print(line)
|
||||
setColor(Console.RESET)
|
||||
System.out.println()
|
||||
}
|
||||
}
|
||||
|
||||
def logAll(events: Seq[LogEvent]) = System.out.synchronized { events.foreach(log) }
|
||||
def control(event: ControlEvent.Value, message: => String)
|
||||
{ log(labelColor(Level.Info), Level.Info.toString, Console.BLUE, message) }
|
||||
}
|
||||
|
||||
/** An enumeration defining the levels available for logging. A level includes all of the levels
|
||||
* with id larger than its own id. For example, Warn (id=3) includes Error (id=4).*/
|
||||
object Level extends Enumeration with NotNull
|
||||
{
|
||||
val Debug = Value(1, "debug")
|
||||
val Info = Value(2, "info")
|
||||
val Warn = Value(3, "warn")
|
||||
val Error = Value(4, "error")
|
||||
/** Defines the label to use for success messages. A success message is logged at the info level but
|
||||
* uses this label. Because the label for levels is defined in this module, the success
|
||||
* label is also defined here. */
|
||||
val SuccessLabel = "success"
|
||||
|
||||
// added because elements was renamed to iterator in 2.8.0 nightly
|
||||
def levels = Debug :: Info :: Warn :: Error :: Nil
|
||||
/** Returns the level with the given name wrapped in Some, or None if no level exists for that name. */
|
||||
def apply(s: String) = levels.find(s == _.toString)
|
||||
/** Same as apply, defined for use in pattern matching. */
|
||||
private[sbt] def unapply(s: String) = apply(s)
|
||||
}
|
||||
/** Provides a `java.io.Writer` interface to a `Logger`. Content is line-buffered and logged at `level`.
|
||||
* A line is delimited by `nl`, which is by default the platform line separator.*/
|
||||
final class LoggerWriter(delegate: Logger, level: Level.Value, nl: String) extends java.io.Writer
|
||||
{
|
||||
def this(delegate: Logger, level: Level.Value) = this(delegate, level, FileUtilities.Newline)
|
||||
|
||||
private[this] val buffer = new StringBuilder
|
||||
|
||||
override def close() = flush()
|
||||
override def flush(): Unit =
|
||||
synchronized {
|
||||
if(buffer.length > 0)
|
||||
{
|
||||
log(buffer.toString)
|
||||
buffer.clear()
|
||||
}
|
||||
}
|
||||
override def write(content: Array[Char], offset: Int, length: Int): Unit =
|
||||
synchronized {
|
||||
buffer.append(content, offset, length)
|
||||
process()
|
||||
}
|
||||
|
||||
private[this] def process()
|
||||
{
|
||||
val i = buffer.indexOf(nl)
|
||||
if(i >= 0)
|
||||
{
|
||||
log(buffer.substring(0, i))
|
||||
buffer.delete(0, i + nl.length)
|
||||
process()
|
||||
}
|
||||
}
|
||||
private[this] def log(s: String): Unit = delegate.log(level, s)
|
||||
}
|
||||
|
|
@ -416,7 +416,7 @@ class xMain extends xsbti.AppMain
|
|||
val ContinuousCompilePollDelaySeconds = 1
|
||||
|
||||
/** The list of logging levels.*/
|
||||
private def logLevels: Iterable[String] = TreeSet.empty[String] ++ Level.levels.map(_.toString)
|
||||
private def logLevels: Iterable[String] = TreeSet.empty[String] ++ Level.values.map(_.toString)
|
||||
/** The list of all interactive commands other than logging level.*/
|
||||
private def basicCommands: Iterable[String] = TreeSet(ShowProjectsAction, ShowActions, ShowCurrent, HelpAction,
|
||||
RebootCommand, TraceCommand, ContinuousCompileCommand, ProjectConsoleAction, BuilderCommand) ++
|
||||
|
|
@ -464,7 +464,7 @@ class xMain extends xsbti.AppMain
|
|||
printCmd(RebootCommand, "Reloads sbt, picking up modifications to sbt.version or scala.version and recompiling modified project definitions.")
|
||||
printCmd(HelpAction, "Displays this help message.")
|
||||
printCmd(ShowCurrent, "Shows the current project, Scala version, and logging level.")
|
||||
printCmd(Level.levels.mkString(", "), "Set logging for the current project to the specified level.")
|
||||
printCmd(Level.values.mkString(", "), "Set logging for the current project to the specified level.")
|
||||
printCmd(TraceCommand + " " + validTraceArguments, "Configures stack trace logging. " + traceExplanation)
|
||||
printCmd(ProjectAction + " <project name>", "Sets the currently active project.")
|
||||
printCmd(ShowProjectsAction, "Shows all available projects.")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
import sbinary._
|
||||
|
||||
trait A
|
||||
{
|
||||
def format: Format[A]
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import sbinary._
|
||||
|
||||
trait B
|
||||
{
|
||||
def format(a: A): Format[A]
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
project.name=Multi Project Provided
|
||||
project.version=1.0
|
||||
|
|
@ -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
|
||||
|
|
@ -93,7 +93,7 @@ object LogWriterTest extends Properties("Log Writer")
|
|||
for(ls <- arbList[List[ToLog]].arbitrary; lv <- genLevel) yield
|
||||
new Output(ls, lv)
|
||||
|
||||
def levelsGen: Seq[Gen[Level.Value]] = Level.elements.toList.map(x => value(x))
|
||||
def levelsGen: Seq[Gen[Level.Value]] = Level.values.toList.map(x => value(x))
|
||||
|
||||
def removeNewlines(s: String) = s.replaceAll("""[\n\r]+""", "")
|
||||
def addNewline(l: ToLog): ToLog =
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@
|
|||
*/
|
||||
package sbt
|
||||
|
||||
// TODO: Incomplete needs to be parameterized with A[_] and have val node
|
||||
|
||||
import Node._
|
||||
import ErrorHandling.wideConvert
|
||||
import Types._
|
||||
import Execute._
|
||||
|
||||
|
|
@ -87,13 +85,21 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: A ~> No
|
|||
readyInv( node )
|
||||
}
|
||||
|
||||
state(node) = Calling
|
||||
addChecked(target)
|
||||
addCaller(node, target)
|
||||
results.get(target) match {
|
||||
case Some(result) => retire(node, result)
|
||||
case None =>
|
||||
state(node) = Calling
|
||||
addChecked(target)
|
||||
addCaller(node, target)
|
||||
}
|
||||
|
||||
post {
|
||||
assert( calling(node) )
|
||||
assert( callers(target) contains node )
|
||||
if(done(target))
|
||||
assert(done(node))
|
||||
else {
|
||||
assert(calling(node) )
|
||||
assert( callers(target) contains node )
|
||||
}
|
||||
readyInv( node )
|
||||
}
|
||||
}
|
||||
|
|
@ -200,20 +206,18 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: A ~> No
|
|||
def submit[T]( node: A[T] )(implicit strategy: Strategy)
|
||||
{
|
||||
val v = view(node)
|
||||
val rs: v.Inputs#Map[Result] = v.inputs.map(results)
|
||||
val ud = v.unitDependencies.flatMap(incomplete)
|
||||
val rs: v.Mixed#Map[Result] = v.mixedIn.map(results)
|
||||
val ud = v.uniformIn.map(results.apply[v.Uniform])
|
||||
strategy.submit( node, () => work(node, v.work(rs, ud)) )
|
||||
}
|
||||
/** Evaluates the computation 'f' for 'node'.
|
||||
* This returns a Completed instance, which contains the post-processing to perform after the result is retrieved from the Strategy.*/
|
||||
def work[T](node: A[T], f: => Either[A[T], T])(implicit strategy: Strategy): Completed =
|
||||
{
|
||||
val result =
|
||||
try { Right(f) }
|
||||
catch {
|
||||
case i: Incomplete => Left(i)
|
||||
case e => Left( Incomplete(Incomplete.Error, directCause = Some(e)) )
|
||||
}
|
||||
val result = wideConvert(f).left.map {
|
||||
case i: Incomplete => i
|
||||
case e => Incomplete(Incomplete.Error, directCause = Some(e))
|
||||
}
|
||||
completed {
|
||||
result match {
|
||||
case Left(i) => retire(node, Inc(i))
|
||||
|
|
@ -229,7 +233,7 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: A ~> No
|
|||
def addCaller[T](caller: A[T], target: A[T]): Unit = callers.getOrUpdate(target, IDSet.create[A[T]]) += caller
|
||||
|
||||
def dependencies(node: A[_]): Iterable[A[_]] = dependencies(view(node))
|
||||
def dependencies(v: Node[A, _]): Iterable[A[_]] = v.unitDependencies ++ v.inputs.toList
|
||||
def dependencies(v: Node[A, _]): Iterable[A[_]] = v.uniformIn ++ v.mixedIn.toList
|
||||
|
||||
// Contracts
|
||||
|
||||
|
|
|
|||
|
|
@ -9,4 +9,24 @@ final case class Incomplete(tpe: IValue = Error, message: Option[String] = None,
|
|||
|
||||
object Incomplete extends Enumeration {
|
||||
val Skipped, Error = Value
|
||||
def show(i: Incomplete, traces: Boolean): String =
|
||||
{
|
||||
val exceptions = allExceptions(i)
|
||||
val traces = exceptions.map(_.getStackTrace).mkString("\n")
|
||||
val causeStr = if(i.causes.isEmpty) "" else (i.causes.length + " cause(s)")
|
||||
"Incomplete (" + show(i.tpe) + ") " + i.message.getOrElse("") + causeStr + "\n" + traces
|
||||
}
|
||||
def allExceptions(i: Incomplete): Iterable[Throwable] =
|
||||
{
|
||||
val exceptions = IDSet.create[Throwable]
|
||||
val visited = IDSet.create[Incomplete]
|
||||
def visit(inc: Incomplete): Unit =
|
||||
visited.process(inc)( () ) {
|
||||
exceptions ++= inc.directCause.toList
|
||||
inc.causes.foreach(visit)
|
||||
}
|
||||
visit(i)
|
||||
exceptions.all
|
||||
}
|
||||
def show(tpe: Value) = tpe match { case Skipped=> "skipped"; case Error => "error" }
|
||||
}
|
||||
|
|
@ -3,33 +3,16 @@
|
|||
*/
|
||||
package sbt
|
||||
|
||||
import Node._
|
||||
import Types._
|
||||
|
||||
trait Node[A[_], T]
|
||||
{
|
||||
type Inputs <: MList[A]
|
||||
type Results = Inputs#Map[Result]
|
||||
type Mixed <: MList[A]
|
||||
type MixedResults = Mixed#Map[Result]
|
||||
type Uniform
|
||||
|
||||
val inputs: Inputs
|
||||
def unitDependencies: Iterable[A[_]]
|
||||
val mixedIn: Mixed
|
||||
val uniformIn: Seq[A[Uniform]]
|
||||
|
||||
def work(results: Results, units: UnitResults[A]): Either[A[T], T]
|
||||
}
|
||||
|
||||
object Node
|
||||
{
|
||||
/*def pure[T](f: () => T): PureNode[T]= map[Id, T, MNil](MNil, Nil)((_,_) => f() )
|
||||
|
||||
def map[A[_], T, Inputs0 <: MList[A]](inputs0: Inputs0, deps0: Iterable[A[_]])(work0: (Inputs0#Map[Result], UnitResults[A]) => T):
|
||||
Node[A,T] { type Inputs = Inputs0 } =
|
||||
new Node[A,T] {
|
||||
type Inputs = Inputs0
|
||||
val inputs = inputs0
|
||||
def unitDependencies = deps0
|
||||
def work(results: Results, units: UnitResults[A]) = Right(work0(results, units))
|
||||
}
|
||||
|
||||
type PureNode[T] = Node[Id, T] { type Inputs = MNil; type Results = MNil }*/
|
||||
type UnitResults[A[_]] = Iterable[(A[_], Incomplete)]
|
||||
def work(mixed: MixedResults, uniform: Seq[Result[Uniform]]): Either[A[T], T]
|
||||
}
|
||||
|
|
|
|||
114
tasks/Test.scala
114
tasks/Test.scala
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
}
|
||||
|
|
@ -1,44 +1,49 @@
|
|||
import xsbt._
|
||||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009, 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import org.scalacheck._
|
||||
import Prop._
|
||||
import TaskGen._
|
||||
import Task._
|
||||
|
||||
object TaskRunnerSpec extends Properties("TaskRunner")
|
||||
object ExecuteSpec extends Properties("Execute")
|
||||
{
|
||||
val iGen = Arbitrary.arbInt.arbitrary
|
||||
property("evaluates simple task") = forAll(iGen, MaxWorkersGen) { (i: Int, workers: Int) =>
|
||||
("Workers: " + workers) |:
|
||||
checkResult(TaskRunner(Task(i), workers), i)
|
||||
checkResult(tryRun(pure(i), false, workers), i)
|
||||
}
|
||||
property("evaluates simple static graph") = forAll(iGen, MaxWorkersGen) { (i: Int, workers: Int) =>
|
||||
// no direct dependencies currently
|
||||
/*property("evaluates simple static graph") = forAll(iGen, MaxWorkersGen) { (i: Int, workers: Int) =>
|
||||
("Workers: " + workers) |:
|
||||
{
|
||||
def result = TaskRunner(Task(i) dependsOn(Task(false),Task("a")), workers)
|
||||
def result = tryRun(Task(i) dependsOn(pure(false),pure("a")), false, workers)
|
||||
checkResult(result, i)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
property("evaluates simple mapped task") = forAll(iGen, MaxTasksGen, MaxWorkersGen) { (i: Int, times: Int, workers: Int) =>
|
||||
("Workers: " + workers) |: ("Value: " + i) |: ("Times: " + times) |:
|
||||
{
|
||||
def result = TaskRunner(Task(i).map(_*times), workers)
|
||||
def result = tryRun(pure(i).map(_*times), false, workers)
|
||||
checkResult(result, i*times)
|
||||
}
|
||||
}
|
||||
property("evaluates chained mapped task") = forAllNoShrink(iGen, Gen.choose(0, 1000), MaxWorkersGen) { (i: Int, times: Int, workers: Int) =>
|
||||
property("evaluates chained mapped task") = forAllNoShrink(iGen, MaxTasksGen, MaxWorkersGen) { (i: Int, times: Int, workers: Int) =>
|
||||
("Workers: " + workers) |: ("Value: " + i) |: ("Times: " + times) |:
|
||||
{
|
||||
val initial = Task(0) map(identity[Int])
|
||||
val initial = pure(0) map(identity[Int])
|
||||
def task = ( initial /: (0 until times) )( (t,ignore) => t.map(_ + i))
|
||||
checkResult(TaskRunner(task, workers), i*times)
|
||||
checkResult(tryRun(task, false, workers), i*times)
|
||||
}
|
||||
}
|
||||
|
||||
property("evaluates simple bind") = forAll(iGen, MaxTasksGen, MaxWorkersGen) { (i: Int, times: Int, workers: Int) =>
|
||||
("Workers: " + workers) |: ("Value: " + i) |: ("Times: " + times) |:
|
||||
{
|
||||
def result = TaskRunner(Task(i).bind(x => Task(x*times)), workers)
|
||||
def result = tryRun(pure(i).flatMap(x => pure(x*times)), false, workers)
|
||||
checkResult(result, i*times)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
|
|
@ -1,11 +1,16 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import org.scalacheck._
|
||||
import Gen.choose
|
||||
|
||||
object TaskGen
|
||||
{
|
||||
// upper bounds to make the tests finish in reasonable time
|
||||
val MaxTasks = 10000
|
||||
val MaxWorkers = 257
|
||||
val MaxTasks = 100
|
||||
val MaxWorkers = 29
|
||||
val MaxJoin = 100
|
||||
|
||||
val MaxTasksGen = choose(0, MaxTasks)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import xsbt._
|
||||
package sbt
|
||||
|
||||
import org.scalacheck._
|
||||
import Prop._
|
||||
import TaskGen._
|
||||
import Task._
|
||||
|
||||
object TaskRunnerCircularTest extends Properties("TaskRunner Circular")
|
||||
{
|
||||
|
|
@ -10,32 +11,33 @@ object TaskRunnerCircularTest extends Properties("TaskRunner Circular")
|
|||
property("Allows references to completed tasks") = forAllNoShrink(MaxTasksGen, MaxWorkersGen) { allowedReference _ }
|
||||
final def allowedReference(intermediate: Int, workers: Int) =
|
||||
{
|
||||
val top = Task(intermediate) named("top")
|
||||
val top = pure("top", intermediate)
|
||||
def iterate(task: Task[Int]): Task[Int] =
|
||||
task bind { t =>
|
||||
task flatMap { t =>
|
||||
if(t <= 0)
|
||||
top
|
||||
else
|
||||
iterate(Task(t-1) named (t-1).toString)
|
||||
iterate(pure((t-1).toString, t-1) )
|
||||
}
|
||||
try { checkResult(TaskRunner(iterate(top), workers), intermediate) }
|
||||
catch { case e: CircularDependency => ("Unexpected exception: " + e) |: false }
|
||||
try { checkResult(tryRun(iterate(top), true, workers), intermediate) }
|
||||
catch { case i: Incomplete if cyclic(i) => ("Unexpected cyclic exception: " + i) |: false }
|
||||
}
|
||||
final def checkCircularReferences(intermediate: Int, workers: Int) =
|
||||
{
|
||||
lazy val top = iterate(Task(intermediate) named"bottom", intermediate)
|
||||
lazy val top = iterate(pure("bottom", intermediate), intermediate)
|
||||
def iterate(task: Task[Int], i: Int): Task[Int] =
|
||||
{
|
||||
lazy val it: Task[Int] =
|
||||
task bind { t =>
|
||||
task flatMap { t =>
|
||||
if(t <= 0)
|
||||
top
|
||||
else
|
||||
iterate(Task(t-1) named (t-1).toString, i-1)
|
||||
} named("it_" + i)
|
||||
iterate(pure((t-1).toString, t-1), i-1)
|
||||
}
|
||||
it
|
||||
}
|
||||
try { TaskRunner(top, workers); false }
|
||||
catch { case TasksFailed(failures) => failures.exists(_.exception.isInstanceOf[CircularDependency]) }
|
||||
try { tryRun(top, true, workers); false }
|
||||
catch { case i: Incomplete => cyclic(i) }
|
||||
}
|
||||
def cyclic(i: Incomplete) = Incomplete.allExceptions(i).exists(_.isInstanceOf[Execute[Task]#CyclicException[_]])
|
||||
}
|
||||
|
|
@ -1,22 +1,22 @@
|
|||
import xsbt._
|
||||
import sbt._
|
||||
|
||||
import org.scalacheck._
|
||||
import Prop._
|
||||
import Task._
|
||||
import TaskGen._
|
||||
import Math.abs
|
||||
import math.abs
|
||||
|
||||
object TaskRunnerForkTest extends Properties("TaskRunner Fork")
|
||||
{
|
||||
property("fork m tasks and wait for all to complete") = forAll(MaxTasksGen, MaxWorkersGen) { (m: Int, workers: Int) =>
|
||||
val values = (0 until m).toList
|
||||
checkResult(TaskRunner(values.fork(f => () ).join.map(_.toList),workers), values)
|
||||
checkResult(tryRun(values.fork(f => () ).join.map(_.toList),false, workers), values)
|
||||
true
|
||||
}
|
||||
property("Fork and reduce 2") = forAll(MaxTasksGen, MaxWorkersGen) { (m: Int, workers: Int) =>
|
||||
(m > 1) ==> {
|
||||
val task = (0 to m) fork {_ * 10} reduce{_ + _}
|
||||
checkResult(TaskRunner(task, workers), 5*(m+1)*m)
|
||||
checkResult(tryRun(task, false, workers), 5*(m+1)*m)
|
||||
}
|
||||
}
|
||||
property("Double join") = forAll(MaxJoinGen, MaxJoinGen, MaxWorkersGen) { (a: Int, b: Int, workers: Int) =>
|
||||
|
|
@ -25,13 +25,13 @@ object TaskRunnerForkTest extends Properties("TaskRunner Fork")
|
|||
}
|
||||
def runDoubleJoin(a: Int, b: Int, workers: Int)
|
||||
{
|
||||
def inner(i: Int) = List.range(0, b).map(j => Task(j) named(j.toString)).join.named("Join " + i)
|
||||
TaskRunner( List.range(0,a).map(inner).join.named("Outermost join"), workers)
|
||||
def inner(i: Int) = List.range(0, b).map(j => pure(j.toString, j)).join
|
||||
tryRun( List.range(0,a).map(inner).join, false, workers)
|
||||
}
|
||||
property("fork and reduce") = forAll(TaskListGen, MaxWorkersGen) { (m: List[Int], workers: Int) =>
|
||||
(!m.isEmpty) ==> {
|
||||
val expected = m.reduceLeft(_+_)
|
||||
checkResult(TaskRunner( m.reduce(_ + _), workers), expected)
|
||||
checkResult(tryRun( m.reduce(_ + _), false, workers), expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import xsbt._
|
||||
import sbt._
|
||||
|
||||
import org.scalacheck._
|
||||
import Prop._
|
||||
import TaskGen._
|
||||
import Task._
|
||||
|
||||
object TaskRunnerCallTest extends Properties("TaskRunner Call")
|
||||
{
|
||||
|
|
@ -11,7 +12,7 @@ object TaskRunnerCallTest extends Properties("TaskRunner Call")
|
|||
val f = fibDirect(i)
|
||||
("Workers: " + workers) |: ("i: " + i) |: ("fib(i): " + f) |:
|
||||
{
|
||||
def result = TaskRunner( fibTask(i), workers)
|
||||
def result = tryRun( fibTask(i), false, workers)
|
||||
checkResult(result, f)
|
||||
}
|
||||
}
|
||||
|
|
@ -23,11 +24,11 @@ object TaskRunnerCallTest extends Properties("TaskRunner Call")
|
|||
(index, x1, x2) =>
|
||||
{
|
||||
if(index == i)
|
||||
Task(x2)
|
||||
pure(x2)
|
||||
else
|
||||
iterate( (index+1, x2, x1+x2) )
|
||||
}
|
||||
def iterate(iteration: (Int,Int,Int)) = Task( iteration ) bind Function.tupled(next)
|
||||
def iterate(iteration: (Int,Int,Int)) = pure( iteration ) flatMap next.tupled
|
||||
iterate( (1, 0, 1) )
|
||||
}
|
||||
final def fibDirect(i: Int): Int =
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
import xsbt._
|
||||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009, 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import org.scalacheck._
|
||||
import Prop._
|
||||
import TaskGen._
|
||||
import Task._
|
||||
import Types._
|
||||
|
||||
object TaskRunnerSortTest extends Properties("TaskRunnerSort")
|
||||
{
|
||||
|
|
@ -12,31 +17,33 @@ object TaskRunnerSortTest extends Properties("TaskRunnerSort")
|
|||
java.util.Arrays.sort(sorted)
|
||||
("Workers: " + workers) |: ("Array: " + a.toList) |:
|
||||
{
|
||||
def result = TaskRunner( sort(a.toArray), if(workers > 0) workers else 1)
|
||||
def result = tryRun( sort(a.toSeq), false, if(workers > 0) workers else 1)
|
||||
checkResult(result.toList, sorted.toList)
|
||||
}
|
||||
}
|
||||
final def sortDirect(a: RandomAccessSeq[Int]): RandomAccessSeq[Int] =
|
||||
final def sortDirect(a: Seq[Int]): Seq[Int] =
|
||||
{
|
||||
if(a.length < 2)
|
||||
a
|
||||
else
|
||||
{
|
||||
val pivot = a(0)
|
||||
val (lt,gte) = a.projection.drop(1).partition(_ < pivot)
|
||||
val (lt,gte) = a.view.drop(1).partition(_ < pivot)
|
||||
sortDirect(lt) ++ List(pivot) ++ sortDirect(gte)
|
||||
}
|
||||
}
|
||||
final def sort(a: RandomAccessSeq[Int]): Task[RandomAccessSeq[Int]] =
|
||||
final def sort(a: Seq[Int]): Task[Seq[Int]] =
|
||||
{
|
||||
if(a.length < 200)
|
||||
Task(sortDirect(a))
|
||||
pure(sortDirect(a))
|
||||
else
|
||||
{
|
||||
Task(a) bind { a =>
|
||||
pure(a) flatMap { a =>
|
||||
val pivot = a(0)
|
||||
val (lt,gte) = a.projection.drop(1).partition(_ < pivot)
|
||||
(sort(lt), sort(gte)) map { _ ++ List(pivot) ++ _ }
|
||||
val (lt,gte) = a.view.drop(1).partition(_ < pivot)
|
||||
sort(lt) :^: sort(gte) :^: MNil mapH {
|
||||
case l :+: g :+: HNil => l ++ List(pivot) ++ g
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
package xsbt
|
||||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import org.scalacheck.Prop._
|
||||
|
||||
|
|
@ -14,8 +17,8 @@ object checkResult
|
|||
}
|
||||
catch
|
||||
{
|
||||
case TasksFailed(failures) =>
|
||||
failures.foreach(f => f.exception.printStackTrace)
|
||||
case i: Incomplete =>
|
||||
println(Incomplete.show(i, true))
|
||||
"One or more tasks failed" |: false
|
||||
case e =>
|
||||
e.printStackTrace
|
||||
|
|
|
|||
|
|
@ -25,4 +25,10 @@ final case class HCons[H, T <: HList](head : H, tail : T) extends HList
|
|||
type Up = MCons[H, tail.Up, Id]
|
||||
def up = MCons[H,tail.Up, Id](head, tail.up)
|
||||
def :+: [G](g: G): G :+: H :+: T = HCons(g, this)
|
||||
}
|
||||
|
||||
object HList
|
||||
{
|
||||
// contains no type information: not even A
|
||||
implicit def fromList[A](list: Traversable[A]): HList = ((HNil: HList) /: list) ( (hl,v) => HCons(v, hl) )
|
||||
}
|
||||
|
|
@ -41,4 +41,11 @@ sealed class MNil extends MList[Nothing]
|
|||
|
||||
def toList = Nil
|
||||
}
|
||||
object MNil extends MNil
|
||||
object MNil extends MNil
|
||||
|
||||
|
||||
object MList
|
||||
{
|
||||
implicit def fromTCList[A[_]](list: Traversable[A[_]]): MList[A] = ((MNil: MList[A]) /: list) ( (hl,v) => MCons(v, hl) )
|
||||
implicit def fromList[A](list: Traversable[A]): MList[Const[A]#Apply] = ((MNil: MList[Const[A]#Apply]) /: list) ( (hl,v) => MCons[A, hl.type, Const[A]#Apply](v, hl) )
|
||||
}
|
||||
|
|
@ -13,6 +13,10 @@ trait TypeFunctions
|
|||
final val left = new (Id ~> P1of2[Left, Nothing]#Flip) { def apply[T](t: T) = Left(t) }
|
||||
final val right = new (Id ~> P1of2[Right, Nothing]#Apply) { def apply[T](t: T) = Right(t) }
|
||||
final val some = new (Id ~> Some) { def apply[T](t: T) = Some(t) }
|
||||
|
||||
implicit def toFn1[A,B](f: A => B): Fn1[A,B] = new Fn1[A,B] {
|
||||
def ∙[C](g: C => A) = f compose g
|
||||
}
|
||||
}
|
||||
object TypeFunctions extends TypeFunctions
|
||||
|
||||
|
|
@ -29,4 +33,7 @@ object ~>
|
|||
import TypeFunctions._
|
||||
val Id: Id ~> Id = new (Id ~> Id) { def apply[T](a: T): T = a }
|
||||
implicit def tcIdEquals: (Id ~> Id) = Id
|
||||
}
|
||||
trait Fn1[A, B] {
|
||||
def ∙[C](g: C => A): C => B
|
||||
}
|
||||
|
|
@ -7,10 +7,17 @@ object ErrorHandling
|
|||
{
|
||||
def translate[T](msg: => String)(f: => T) =
|
||||
try { f }
|
||||
catch { case e => throw new TranslatedException(msg + e.toString, e) }
|
||||
catch { case e: Exception => throw new TranslatedException(msg + e.toString, e) }
|
||||
|
||||
def wideConvert[T](f: => T): Either[Throwable, T] =
|
||||
try { Right(f) }
|
||||
catch { case e => Left(e) } // TODO: restrict type of e
|
||||
catch
|
||||
{
|
||||
case ex @ (_: Exception | _: StackOverflowError) => Left(ex)
|
||||
case err @ (_: ThreadDeath | _: VirtualMachineError) => throw err
|
||||
case x => Left(x)
|
||||
}
|
||||
|
||||
def convert[T](f: => T): Either[Exception, T] =
|
||||
try { Right(f) }
|
||||
catch { case e: Exception => Left(e) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009, 2010 Mark Harrah
|
||||
*/
|
||||
package xsbt.api
|
||||
package xsbt
|
||||
package api
|
||||
|
||||
import java.io.File
|
||||
import xsbt.FileUtilities
|
||||
|
|
|
|||
|
|
@ -399,4 +399,36 @@ object IO
|
|||
|
||||
/** Splits a String around path separator characters. */
|
||||
def pathSplit(s: String) = PathSeparatorPattern.split(s)
|
||||
|
||||
/** Move the provided files to a temporary location.
|
||||
* If 'f' returns normally, delete the files.
|
||||
* If 'f' throws an Exception, return the files to their original location.*/
|
||||
def stash[T](files: Set[File])(f: => T): T =
|
||||
withTemporaryDirectory { dir =>
|
||||
val stashed = stashLocations(dir, files.toArray)
|
||||
move(stashed)
|
||||
|
||||
try { f } catch { case e: Exception =>
|
||||
try { move(stashed.map(_.swap)); throw e }
|
||||
catch { case _: Exception => throw e }
|
||||
}
|
||||
}
|
||||
|
||||
private def stashLocations(dir: File, files: Array[File]) =
|
||||
for( (file, index) <- files.zipWithIndex) yield
|
||||
(file, new File(dir, index.toHexString))
|
||||
|
||||
def move(files: Iterable[(File, File)]): Unit =
|
||||
files.foreach(Function.tupled(move))
|
||||
|
||||
def move(a: File, b: File): Unit =
|
||||
{
|
||||
if(b.exists)
|
||||
delete(b)
|
||||
if(!a.renameTo(b))
|
||||
{
|
||||
copyFile(a, b)
|
||||
delete(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009 Mark Harrah
|
||||
* Copyright 2008, 2009, 2010 Mark Harrah
|
||||
*/
|
||||
package xsbt
|
||||
package sbt
|
||||
|
||||
/** Implements the level-setting methods of Logger.*/
|
||||
abstract class BasicLogger extends Logger
|
||||
abstract class BasicLogger extends AbstractLogger
|
||||
{
|
||||
private var traceEnabledVar = true
|
||||
private var traceEnabledVar = java.lang.Integer.MAX_VALUE
|
||||
private var level: Level.Value = Level.Info
|
||||
def getLevel = level
|
||||
def setLevel(newLevel: Level.Value) { level = newLevel }
|
||||
def enableTrace(flag: Boolean) { traceEnabledVar = flag }
|
||||
def traceEnabled = traceEnabledVar
|
||||
}
|
||||
def setTrace(level: Int) { traceEnabledVar = level }
|
||||
def getTrace = traceEnabledVar
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009 Mark Harrah
|
||||
* Copyright 2008, 2009, 2010 Mark Harrah
|
||||
*/
|
||||
package xsbt
|
||||
|
||||
import sbt.{AbstractLogger, ControlEvent, Level, Log, LogEvent, SetLevel, SetTrace, Success, Trace}
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
/** A logger that can buffer the logging done on it and then can flush the buffer
|
||||
|
|
@ -13,7 +14,7 @@
|
|||
*
|
||||
* This class assumes that it is the only client of the delegate logger.
|
||||
* */
|
||||
class BufferedLogger(delegate: Logger) extends Logger
|
||||
class BufferedLogger(delegate: AbstractLogger) extends AbstractLogger
|
||||
{
|
||||
private[this] val buffer = new ListBuffer[LogEvent]
|
||||
private[this] var recording = false
|
||||
|
|
@ -54,10 +55,10 @@ class BufferedLogger(delegate: Logger) extends Logger
|
|||
}
|
||||
def getLevel = delegate.getLevel
|
||||
def traceEnabled = delegate.traceEnabled
|
||||
def enableTrace(flag: Boolean)
|
||||
def setTrace(level: Int)
|
||||
{
|
||||
buffer += new SetTrace(flag)
|
||||
delegate.enableTrace(flag)
|
||||
buffer += new SetTrace(level)
|
||||
delegate.setTrace(level)
|
||||
}
|
||||
|
||||
def trace(t: => Throwable): Unit =
|
||||
|
|
@ -73,9 +74,9 @@ class BufferedLogger(delegate: Logger) extends Logger
|
|||
delegate.logAll(events)
|
||||
def control(event: ControlEvent.Value, message: => String): Unit =
|
||||
doBufferable(Level.Info, new ControlEvent(event, message), _.control(event, message))
|
||||
private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: Logger => Unit): Unit =
|
||||
private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit =
|
||||
doBufferableIf(atLevel(level), appendIfBuffered, doUnbuffered)
|
||||
private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: Logger => Unit): Unit =
|
||||
private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit =
|
||||
if(condition)
|
||||
{
|
||||
if(recording)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009 Mark Harrah
|
||||
* Copyright 2008, 2009, 2010 Mark Harrah
|
||||
*/
|
||||
package xsbt
|
||||
package sbt
|
||||
|
||||
object ConsoleLogger
|
||||
{
|
||||
|
|
@ -17,10 +17,12 @@ object ConsoleLogger
|
|||
}
|
||||
|
||||
/** A logger that logs to the console. On supported systems, the level labels are
|
||||
* colored. */
|
||||
* colored.
|
||||
*
|
||||
* This logger is not thread-safe.*/
|
||||
class ConsoleLogger extends BasicLogger
|
||||
{
|
||||
import ConsoleLogger.formatEnabled
|
||||
override def ansiCodesSupported = ConsoleLogger.formatEnabled
|
||||
def messageColor(level: Level.Value) = Console.RESET
|
||||
def labelColor(level: Level.Value) =
|
||||
level match
|
||||
|
|
@ -39,8 +41,9 @@ class ConsoleLogger extends BasicLogger
|
|||
def trace(t: => Throwable): Unit =
|
||||
System.out.synchronized
|
||||
{
|
||||
if(traceEnabled)
|
||||
t.printStackTrace
|
||||
val traceLevel = getTrace
|
||||
if(traceLevel >= 0)
|
||||
System.out.synchronized { System.out.print(StackTrace.trimmed(t, traceLevel)) }
|
||||
}
|
||||
def log(level: Level.Value, message: => String)
|
||||
{
|
||||
|
|
@ -49,7 +52,7 @@ class ConsoleLogger extends BasicLogger
|
|||
}
|
||||
private def setColor(color: String)
|
||||
{
|
||||
if(formatEnabled)
|
||||
if(ansiCodesSupported)
|
||||
System.out.synchronized { System.out.print(color) }
|
||||
}
|
||||
private def log(labelColor: String, label: String, messageColor: String, message: String): Unit =
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009 Mark Harrah
|
||||
*/
|
||||
package xsbt
|
||||
package sbt
|
||||
|
||||
/** An enumeration defining the levels available for logging. A level includes all of the levels
|
||||
* with id larger than its own id. For example, Warn (id=3) includes Error (id=4).*/
|
||||
object Level extends Enumeration with NotNull
|
||||
object Level extends Enumeration
|
||||
{
|
||||
val Debug = Value(1, "debug")
|
||||
val Info = Value(2, "info")
|
||||
|
|
@ -16,10 +16,8 @@ object Level extends Enumeration with NotNull
|
|||
* label is also defined here. */
|
||||
val SuccessLabel = "success"
|
||||
|
||||
// added because elements was renamed to iterator in 2.8.0 nightly
|
||||
def levels = Debug :: Info :: Warn :: Error :: Nil
|
||||
/** Returns the level with the given name wrapped in Some, or None if no level exists for that name. */
|
||||
def apply(s: String) = levels.find(s == _.toString)
|
||||
def apply(s: String) = values.find(s == _.toString)
|
||||
/** Same as apply, defined for use in pattern matching. */
|
||||
private[xsbt] def unapply(s: String) = apply(s)
|
||||
private[sbt] def unapply(s: String) = apply(s)
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009 Mark Harrah
|
||||
*/
|
||||
package xsbt
|
||||
package sbt
|
||||
|
||||
sealed trait LogEvent extends NotNull
|
||||
final class Success(val msg: String) extends LogEvent
|
||||
final class Log(val level: Level.Value, val msg: String) extends LogEvent
|
||||
final class Trace(val exception: Throwable) extends LogEvent
|
||||
final class SetLevel(val newLevel: Level.Value) extends LogEvent
|
||||
final class SetTrace(val enabled: Boolean) extends LogEvent
|
||||
final class SetTrace(val level: Int) extends LogEvent
|
||||
final class ControlEvent(val event: ControlEvent.Value, val msg: String) extends LogEvent
|
||||
|
||||
object ControlEvent extends Enumeration
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009 Mark Harrah
|
||||
*/
|
||||
package xsbt
|
||||
package sbt
|
||||
|
||||
import xsbti.{Logger => xLogger, F0}
|
||||
abstract class Logger extends xLogger with NotNull
|
||||
|
||||
abstract class AbstractLogger extends xLogger with NotNull
|
||||
{
|
||||
def getLevel: Level.Value
|
||||
def setLevel(newLevel: Level.Value)
|
||||
def enableTrace(flag: Boolean)
|
||||
def traceEnabled: Boolean
|
||||
def setTrace(flag: Int)
|
||||
def getTrace: Int
|
||||
final def traceEnabled = getTrace >= 0
|
||||
def ansiCodesSupported = false
|
||||
|
||||
def atLevel(level: Level.Value) = level.id >= getLevel.id
|
||||
def trace(t: => Throwable): Unit
|
||||
final def verbose(message: => String): Unit = debug(message)
|
||||
final def debug(message: => String): Unit = log(Level.Debug, message)
|
||||
final def info(message: => String): Unit = log(Level.Info, message)
|
||||
final def warn(message: => String): Unit = log(Level.Warn, message)
|
||||
|
|
@ -31,7 +35,7 @@ abstract class Logger extends xLogger with NotNull
|
|||
case l: Log => log(l.level, l.msg)
|
||||
case t: Trace => trace(t.exception)
|
||||
case setL: SetLevel => setLevel(setL.newLevel)
|
||||
case setT: SetTrace => enableTrace(setT.enabled)
|
||||
case setT: SetTrace => setTrace(setT.level)
|
||||
case c: ControlEvent => control(c.event, c.msg)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)) }
|
||||
}
|
||||
Loading…
Reference in New Issue