diff --git a/cache/Cache.scala b/cache/Cache.scala index 2f35d2c96..e7ba310dc 100644 --- a/cache/Cache.scala +++ b/cache/Cache.scala @@ -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 -} \ No newline at end of file +} diff --git a/cache/FileInfo.scala b/cache/FileInfo.scala index 66c8c496b..d1b350fa8 100644 --- a/cache/FileInfo.scala +++ b/cache/FileInfo.scala @@ -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) } \ No newline at end of file diff --git a/cache/src/test/scala/CacheTest.scala b/cache/src/test/scala/CacheTest.scala index 7bba6ec79..65703ecaa 100644 --- a/cache/src/test/scala/CacheTest.scala +++ b/cache/src/test/scala/CacheTest.scala @@ -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) } } \ No newline at end of file diff --git a/cache/tracking/Tracked.scala b/cache/tracking/Tracked.scala index f79a2a7ee..49d33c622 100644 --- a/cache/tracking/Tracked.scala +++ b/cache/tracking/Tracked.scala @@ -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) diff --git a/compile/api/SameAPI.scala b/compile/api/SameAPI.scala index 04b842ed3..1c7d2dc2c 100644 --- a/compile/api/SameAPI.scala +++ b/compile/api/SameAPI.scala @@ -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 = diff --git a/launch/ConfigurationParser.scala b/launch/ConfigurationParser.scala index 2ee4fb55a..242681fe4 100644 --- a/launch/ConfigurationParser.scala +++ b/launch/ConfigurationParser.scala @@ -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 = diff --git a/project/build/XSbt.scala b/project/build/XSbt.scala index dc4cd1700..98221cdc9 100644 --- a/project/build/XSbt.scala +++ b/project/build/XSbt.scala @@ -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 diff --git a/sbt/src/main/scala/sbt/BasicProjectTypes.scala b/sbt/src/main/scala/sbt/BasicProjectTypes.scala index e8a646912..16b8b01e0 100644 --- a/sbt/src/main/scala/sbt/BasicProjectTypes.scala +++ b/sbt/src/main/scala/sbt/BasicProjectTypes.scala @@ -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 diff --git a/sbt/src/main/scala/sbt/Logger.scala b/sbt/src/main/scala/sbt/Logger.scala index 579bde171..29a938797 100644 --- a/sbt/src/main/scala/sbt/Logger.scala +++ b/sbt/src/main/scala/sbt/Logger.scala @@ -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) -} \ No newline at end of file diff --git a/sbt/src/main/scala/sbt/Main.scala b/sbt/src/main/scala/sbt/Main.scala index 6287c019a..49a93e568 100755 --- a/sbt/src/main/scala/sbt/Main.scala +++ b/sbt/src/main/scala/sbt/Main.scala @@ -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 + " ", "Sets the currently active project.") printCmd(ShowProjectsAction, "Shows all available projects.") diff --git a/sbt/src/sbt-test/dependency-management/provided-multi/changes/A.scala b/sbt/src/sbt-test/dependency-management/provided-multi/changes/A.scala new file mode 100644 index 000000000..40190d644 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/provided-multi/changes/A.scala @@ -0,0 +1,6 @@ +import sbinary._ + +trait A +{ + def format: Format[A] +} \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/provided-multi/changes/B.scala b/sbt/src/sbt-test/dependency-management/provided-multi/changes/B.scala new file mode 100644 index 000000000..3519cbacb --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/provided-multi/changes/B.scala @@ -0,0 +1,6 @@ +import sbinary._ + +trait B +{ + def format(a: A): Format[A] +} \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/provided-multi/changes/P.scala b/sbt/src/sbt-test/dependency-management/provided-multi/changes/P.scala new file mode 100644 index 000000000..37834dded --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/provided-multi/changes/P.scala @@ -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() + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/provided-multi/project/build.properties b/sbt/src/sbt-test/dependency-management/provided-multi/project/build.properties new file mode 100644 index 000000000..1960ba3b0 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/provided-multi/project/build.properties @@ -0,0 +1,2 @@ +project.name=Multi Project Provided +project.version=1.0 \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/provided-multi/test b/sbt/src/sbt-test/dependency-management/provided-multi/test new file mode 100644 index 000000000..9019dbd82 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/provided-multi/test @@ -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 \ No newline at end of file diff --git a/sbt/src/test/scala/sbt/LogWriterTest.scala b/sbt/src/test/scala/sbt/LogWriterTest.scala index ec03eeca2..0d4dac14d 100644 --- a/sbt/src/test/scala/sbt/LogWriterTest.scala +++ b/sbt/src/test/scala/sbt/LogWriterTest.scala @@ -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 = diff --git a/tasks/Execute.scala b/tasks/Execute.scala index 5fb362a10..3189bc079 100644 --- a/tasks/Execute.scala +++ b/tasks/Execute.scala @@ -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 diff --git a/tasks/Incomplete.scala b/tasks/Incomplete.scala index 06b551892..1600432bd 100644 --- a/tasks/Incomplete.scala +++ b/tasks/Incomplete.scala @@ -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" } } \ No newline at end of file diff --git a/tasks/Node.scala b/tasks/Node.scala index b57c5dc00..91c8f54ec 100644 --- a/tasks/Node.scala +++ b/tasks/Node.scala @@ -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] } diff --git a/tasks/Test.scala b/tasks/Test.scala deleted file mode 100644 index 8929685ae..000000000 --- a/tasks/Test.scala +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/tasks/src/test/scala/DemoTask.scala b/tasks/src/test/scala/DemoTask.scala new file mode 100644 index 000000000..8ed192794 --- /dev/null +++ b/tasks/src/test/scala/DemoTask.scala @@ -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) } +} \ No newline at end of file diff --git a/tasks/src/test/scala/TestRunner.scala b/tasks/src/test/scala/Execute.scala similarity index 57% rename from tasks/src/test/scala/TestRunner.scala rename to tasks/src/test/scala/Execute.scala index ceba8527a..891ed4352 100644 --- a/tasks/src/test/scala/TestRunner.scala +++ b/tasks/src/test/scala/Execute.scala @@ -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) } } diff --git a/tasks/src/test/scala/TListCompileTest.scala b/tasks/src/test/scala/TListCompileTest.scala deleted file mode 100644 index 068ec2ca0..000000000 --- a/tasks/src/test/scala/TListCompileTest.scala +++ /dev/null @@ -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 } -} \ No newline at end of file diff --git a/tasks/src/test/scala/TaskGen.scala b/tasks/src/test/scala/TaskGen.scala index 65ddbfbe5..f14f8eb76 100644 --- a/tasks/src/test/scala/TaskGen.scala +++ b/tasks/src/test/scala/TaskGen.scala @@ -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) diff --git a/tasks/src/test/scala/TaskRunnerCircular.scala b/tasks/src/test/scala/TaskRunnerCircular.scala index aebf163bf..7dae59c31 100644 --- a/tasks/src/test/scala/TaskRunnerCircular.scala +++ b/tasks/src/test/scala/TaskRunnerCircular.scala @@ -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[_]]) } \ No newline at end of file diff --git a/tasks/src/test/scala/TaskRunnerFork.scala b/tasks/src/test/scala/TaskRunnerFork.scala index 8d72217bd..c5f7e5a69 100644 --- a/tasks/src/test/scala/TaskRunnerFork.scala +++ b/tasks/src/test/scala/TaskRunnerFork.scala @@ -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) } } } diff --git a/tasks/src/test/scala/Test.scala b/tasks/src/test/scala/Test.scala new file mode 100644 index 000000000..71121a844 --- /dev/null +++ b/tasks/src/test/scala/Test.scala @@ -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) + } +} \ No newline at end of file diff --git a/tasks/src/test/scala/TestRunnerCall.scala b/tasks/src/test/scala/TestRunnerCall.scala index 2b8549937..6b1ea6fcc 100644 --- a/tasks/src/test/scala/TestRunnerCall.scala +++ b/tasks/src/test/scala/TestRunnerCall.scala @@ -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 = diff --git a/tasks/src/test/scala/TestRunnerSort.scala b/tasks/src/test/scala/TestRunnerSort.scala index b7d0daf8c..a96b79c8a 100644 --- a/tasks/src/test/scala/TestRunnerSort.scala +++ b/tasks/src/test/scala/TestRunnerSort.scala @@ -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 + } } } } diff --git a/tasks/src/test/scala/checkResult.scala b/tasks/src/test/scala/checkResult.scala index a4f03b799..8459b143c 100644 --- a/tasks/src/test/scala/checkResult.scala +++ b/tasks/src/test/scala/checkResult.scala @@ -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 diff --git a/util/collection/HList.scala b/util/collection/HList.scala index a475c1194..a1e595eeb 100644 --- a/util/collection/HList.scala +++ b/util/collection/HList.scala @@ -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) ) } \ No newline at end of file diff --git a/util/collection/MList.scala b/util/collection/MList.scala index b350858c3..7adfc1568 100644 --- a/util/collection/MList.scala +++ b/util/collection/MList.scala @@ -41,4 +41,11 @@ sealed class MNil extends MList[Nothing] def toList = Nil } -object MNil extends MNil \ No newline at end of file +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) ) +} \ No newline at end of file diff --git a/util/collection/TypeFunctions.scala b/util/collection/TypeFunctions.scala index 00a6fc772..942a00d72 100644 --- a/util/collection/TypeFunctions.scala +++ b/util/collection/TypeFunctions.scala @@ -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 } \ No newline at end of file diff --git a/util/control/ErrorHandling.scala b/util/control/ErrorHandling.scala index 98c1b1a73..f4d42993b 100644 --- a/util/control/ErrorHandling.scala +++ b/util/control/ErrorHandling.scala @@ -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) } diff --git a/util/datatype/Generator.scala b/util/datatype/Generator.scala index 2a1d62022..442800776 100644 --- a/util/datatype/Generator.scala +++ b/util/datatype/Generator.scala @@ -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 diff --git a/util/io/IO.scala b/util/io/IO.scala index 9b3502fc6..b506ab08d 100644 --- a/util/io/IO.scala +++ b/util/io/IO.scala @@ -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) + } + } } diff --git a/util/io/src/test/scala/StashSpec.scala b/util/io/src/test/scala/StashSpec.scala new file mode 100644 index 000000000..df76165d2 --- /dev/null +++ b/util/io/src/test/scala/StashSpec.scala @@ -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 \ No newline at end of file diff --git a/util/log/BasicLogger.scala b/util/log/BasicLogger.scala index 23607c8ed..a52d3b433 100644 --- a/util/log/BasicLogger.scala +++ b/util/log/BasicLogger.scala @@ -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 +} \ No newline at end of file diff --git a/util/log/BufferedLogger.scala b/util/log/BufferedLogger.scala index 7e8348f2d..1a72e022d 100644 --- a/util/log/BufferedLogger.scala +++ b/util/log/BufferedLogger.scala @@ -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) diff --git a/util/log/ConsoleLogger.scala b/util/log/ConsoleLogger.scala index a7217f026..49db31b66 100644 --- a/util/log/ConsoleLogger.scala +++ b/util/log/ConsoleLogger.scala @@ -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 = diff --git a/util/log/FilterLogger.scala b/util/log/FilterLogger.scala new file mode 100644 index 000000000..152f6bdd5 --- /dev/null +++ b/util/log/FilterLogger.scala @@ -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) +} diff --git a/util/log/Level.scala b/util/log/Level.scala index 86abc257d..ad4e51759 100644 --- a/util/log/Level.scala +++ b/util/log/Level.scala @@ -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) } \ No newline at end of file diff --git a/util/log/LogEvent.scala b/util/log/LogEvent.scala index 19a5b24db..ffe6049d7 100644 --- a/util/log/LogEvent.scala +++ b/util/log/LogEvent.scala @@ -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 diff --git a/util/log/Logger.scala b/util/log/Logger.scala index 153596f6f..1be353c4b 100644 --- a/util/log/Logger.scala +++ b/util/log/Logger.scala @@ -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) } } diff --git a/util/log/LoggerWriter.scala b/util/log/LoggerWriter.scala new file mode 100644 index 000000000..b3b2d54d3 --- /dev/null +++ b/util/log/LoggerWriter.scala @@ -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) +} \ No newline at end of file diff --git a/util/log/MultiLogger.scala b/util/log/MultiLogger.scala new file mode 100644 index 000000000..800d170ed --- /dev/null +++ b/util/log/MultiLogger.scala @@ -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)) } +} \ No newline at end of file