From 573994dd4e54f24c50db35b452e345faf14eeea2 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sat, 29 Aug 2009 10:19:00 -0400 Subject: [PATCH] tuple caches, stamped caches, Path API, another type of change detection, and copying/archiving based on (source,target) tuples --- cache/Cache.scala | 33 +++++++++- cache/CacheIO.scala | 26 ++++++++ cache/ChangeReport.scala | 34 +++++++++++ cache/DependencyTracking.scala | 32 +++++++--- cache/FileInfo.scala | 12 ++-- cache/TrackingFormat.scala | 18 +++--- cache/src/test/scala/Tracking.scala | 45 -------------- compile/ComponentCompiler.scala | 5 +- compile/src/test/scala/CompileTest.scala | 3 +- project/build.properties | 8 +-- project/build/XSbt.scala | 4 +- tasks/Task.scala | 28 +++++---- tasks/TaskScheduler.scala | 2 +- util/io/FileUtilities.scala | 77 +++++++++--------------- util/io/PathMapper.scala | 17 ++++-- util/io/Paths.scala | 44 ++++++++++++++ 16 files changed, 248 insertions(+), 140 deletions(-) create mode 100644 cache/CacheIO.scala delete mode 100644 cache/src/test/scala/Tracking.scala create mode 100644 util/io/Paths.scala diff --git a/cache/Cache.scala b/cache/Cache.scala index ae46845c9..90677f29a 100644 --- a/cache/Cache.scala +++ b/cache/Cache.scala @@ -1,6 +1,6 @@ package xsbt -import sbinary.{CollectionTypes, Format, JavaFormats} +import sbinary.{CollectionTypes, Format, JavaFormats, Operations} import java.io.File trait Cache[I,O] @@ -11,7 +11,7 @@ trait SBinaryFormats extends CollectionTypes with JavaFormats with NotNull { //TODO: add basic types from SBinary minus FileFormat } -object Cache extends BasicCacheImplicits with SBinaryFormats with HListCacheImplicits +object Cache extends BasicCacheImplicits with SBinaryFormats with HListCacheImplicits with TupleCacheImplicits { def cache[I,O](implicit c: Cache[I,O]): Cache[I,O] = c def outputCache[O](implicit c: OutputCache[O]): OutputCache[O] = c @@ -32,7 +32,7 @@ object Cache extends BasicCacheImplicits with SBinaryFormats with HListCacheImpl cache(file)(in) match { case Left(value) => Value(value) - case Right(store) => NewTask(m.map { out => store(out); out }) + case Right(store) => m.map { out => store(out); out } } } trait BasicCacheImplicits extends NotNull @@ -55,4 +55,31 @@ 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 +} +trait TupleCacheImplicits extends HLists +{ + import Cache._ + implicit def tuple2HList[A,B](t: (A,B)): A :: B :: HNil = t._1 :: t._2 :: HNil + implicit def hListTuple2[A,B](t: A :: B :: HNil): (A,B) = t match { case a :: b :: HNil => (a,b) } + + implicit def tuple2InputCache[A,B](implicit aCache: InputCache[A], bCache: InputCache[B]): InputCache[(A,B)] = + wrapInputCache[(A,B), A :: B :: HNil] + implicit def tuple2OutputCache[A,B](implicit aCache: OutputCache[A], bCache: OutputCache[B]): OutputCache[(A,B)] = + wrapOutputCache[(A,B), A :: B :: HNil] + + implicit def tuple3HList[A,B,C](t: (A,B,C)): A :: B :: C :: HNil = t._1 :: t._2 :: t._3 :: HNil + implicit def hListTuple3[A,B,C](t: A :: B :: C :: HNil): (A,B,C) = t match { case a :: b :: c :: HNil => (a,b,c) } + + implicit def tuple3InputCache[A,B,C](implicit aCache: InputCache[A], bCache: InputCache[B], cCache: InputCache[C]): InputCache[(A,B,C)] = + wrapInputCache[(A,B,C), A :: B :: C :: HNil] + implicit def tuple3OutputCache[A,B,C](implicit aCache: OutputCache[A], bCache: OutputCache[B], cCache: OutputCache[C]): OutputCache[(A,B,C)] = + wrapOutputCache[(A,B,C), A :: B :: C :: HNil] + + implicit def tuple4HList[A,B,C,D](t: (A,B,C,D)): A :: B :: C :: D :: HNil = t._1 :: t._2 :: t._3 :: t._4 :: HNil + implicit def hListTuple4[A,B,C,D](t: A :: B :: C :: D :: HNil): (A,B,C,D) = t match { case a :: b :: c :: d:: HNil => (a,b,c,d) } + + implicit def tuple4InputCache[A,B,C,D](implicit aCache: InputCache[A], bCache: InputCache[B], cCache: InputCache[C], dCache: InputCache[D]): InputCache[(A,B,C,D)] = + wrapInputCache[(A,B,C,D), A :: B :: C :: D :: HNil] + implicit def tuple4OutputCache[A,B,C,D](implicit aCache: OutputCache[A], bCache: OutputCache[B], cCache: OutputCache[C], dCache: OutputCache[D]): OutputCache[(A,B,C,D)] = + wrapOutputCache[(A,B,C,D), A :: B :: C :: D :: HNil] } \ No newline at end of file diff --git a/cache/CacheIO.scala b/cache/CacheIO.scala new file mode 100644 index 000000000..ba4cc0edc --- /dev/null +++ b/cache/CacheIO.scala @@ -0,0 +1,26 @@ +package xsbt + +import java.io.File +import sbinary.{DefaultProtocol, Format, Operations} +import scala.reflect.Manifest + +object CacheIO +{ + def fromFile[T](format: Format[T])(file: File)(implicit mf: Manifest[Format[T]]): T = + fromFile(file)(format, mf) + def fromFile[T](file: File)(implicit format: Format[T], mf: Manifest[Format[T]]): T = + Operations.fromFile(file)(stampedFormat(format)) + def toFile[T](format: Format[T])(value: T)(file: File)(implicit mf: Manifest[Format[T]]): Unit = + toFile(value)(file)(format, mf) + def toFile[T](value: T)(file: File)(implicit format: Format[T], mf: Manifest[Format[T]]): Unit = + Operations.toFile(value)(file)(stampedFormat(format)) + def stampedFormat[T](format: Format[T])(implicit mf: Manifest[Format[T]]): Format[T] = + { + import DefaultProtocol._ + withStamp(stamp(format))(format) + } + def stamp[T](format: Format[T])(implicit mf: Manifest[Format[T]]): Int = typeHash(mf) + def typeHash[T](implicit mf: Manifest[T]) = mf.toString.hashCode + def manifest[T](implicit mf: Manifest[T]): Manifest[T] = mf + def objManifest[T](t: T)(implicit mf: Manifest[T]): Manifest[T] = mf +} \ No newline at end of file diff --git a/cache/ChangeReport.scala b/cache/ChangeReport.scala index baa078034..a16b875dc 100644 --- a/cache/ChangeReport.scala +++ b/cache/ChangeReport.scala @@ -1,5 +1,21 @@ package xsbt +object ChangeReport +{ + def modified[T](files: Set[T]) = + new EmptyChangeReport[T] + { + override def allInputs = files + override def modified = files + override def markAllModified = this + } + def unmodified[T](files: Set[T]) = + new EmptyChangeReport[T] + { + override def allInputs = files + override def unmodified = files + } +} trait ChangeReport[T] extends NotNull { def allInputs: Set[T] @@ -8,6 +24,24 @@ trait ChangeReport[T] extends NotNull def added: Set[T] def removed: Set[T] def +++(other: ChangeReport[T]): ChangeReport[T] = new CompoundChangeReport(this, other) + def markAllModified: ChangeReport[T] = + new ChangeReport[T] + { + def allInputs = ChangeReport.this.allInputs + def unmodified = Set.empty[T] + def modified = ChangeReport.this.allInputs + def added = ChangeReport.this.added + def removed = ChangeReport.this.removed + override def markAllModified = this + } +} +class EmptyChangeReport[T] extends ChangeReport[T] +{ + def allInputs = Set.empty[T] + def unmodified = Set.empty[T] + def modified = Set.empty[T] + def added = Set.empty[T] + def removed = Set.empty[T] } trait InvalidationReport[T] extends NotNull { diff --git a/cache/DependencyTracking.scala b/cache/DependencyTracking.scala index 931efba1c..d7f889dc3 100644 --- a/cache/DependencyTracking.scala +++ b/cache/DependencyTracking.scala @@ -1,12 +1,14 @@ package xsbt import java.io.File -import sbinary.{Format, Operations} +import CacheIO.{fromFile, toFile} +import sbinary.Format +import scala.reflect.Manifest object DependencyTracking { def trackBasic[T, F <: FileInfo](filesTask: Task[Set[File]], style: FilesInfo.Style[F], cacheDirectory: File) - (f: (ChangeReport[File], InvalidationReport[File], UpdateTracking[File]) => Task[T]): Task[T] = + (f: (ChangeReport[File], InvalidationReport[File], UpdateTracking[File]) => Task[T])(implicit mf: Manifest[F]): Task[T] = { changed(filesTask, style, new File(cacheDirectory, "files")) { sourceChanges => invalidate(sourceChanges, cacheDirectory) { (report, tracking) => @@ -14,9 +16,24 @@ object DependencyTracking } } } - def changed[T, F <: FileInfo](filesTask: Task[Set[File]], style: FilesInfo.Style[F], cache: File)(f: ChangeReport[File] => Task[T]): Task[T] = + def changed[O,O2](task: Task[O], file: File)(ifChanged: O => O2, ifUnchanged: O => O2)(implicit input: InputCache[O]): Task[O2] { type Input = O } = + task map { value => + val cache = OpenResource.fileInputStream(file)(input.uptodate(value)) + if(cache.uptodate) + ifUnchanged(value) + else + { + OpenResource.fileOutputStream(false)(file)(cache.update) + ifChanged(value) + } + } + def changed[T, F <: FileInfo](files: Set[File], style: FilesInfo.Style[F], cache: File) + (f: ChangeReport[File] => Task[T])(implicit mf: Manifest[F]): Task[T] = + changed(Task(files), style, cache)(f) + def changed[T, F <: FileInfo](filesTask: Task[Set[File]], style: FilesInfo.Style[F], cache: File) + (f: ChangeReport[File] => Task[T])(implicit mf: Manifest[F]): Task[T] = filesTask bind { files => - val lastFilesInfo = Operations.fromFile(cache)(style.format).files + val lastFilesInfo = fromFile(style.formats)(cache).files val lastFiles = lastFilesInfo.map(_.file) val currentFiles = files.map(_.getAbsoluteFile) val currentFilesInfo = style(files) @@ -31,7 +48,7 @@ object DependencyTracking } f(report) map { result => - Operations.toFile(currentFilesInfo)(cache)(style.format) + toFile(style.formats)(currentFilesInfo)(cache) result } } @@ -41,10 +58,11 @@ object DependencyTracking report.invalidProducts.foreach(_.delete) f(report, tracking) } - invalidate(Task(changes), cacheDirectory, true)(pruneAndF)(sbinary.DefaultProtocol.FileFormat) + implicit val format = sbinary.DefaultProtocol.FileFormat + invalidate(Task(changes), cacheDirectory, true)(pruneAndF) } def invalidate[T,R](changesTask: Task[ChangeReport[T]], cacheDirectory: File, translateProducts: Boolean) - (f: (InvalidationReport[T], UpdateTracking[T]) => Task[R])(implicit format: Format[T]): Task[R] = + (f: (InvalidationReport[T], UpdateTracking[T]) => Task[R])(implicit format: Format[T], mf: Manifest[T]): Task[R] = { changesTask bind { changes => val trackFormat = new TrackingFormat[T](cacheDirectory, translateProducts) diff --git a/cache/FileInfo.scala b/cache/FileInfo.scala index 8c835fe95..0271ad0ec 100644 --- a/cache/FileInfo.scala +++ b/cache/FileInfo.scala @@ -60,13 +60,17 @@ object FilesInfo { sealed trait Style[F <: FileInfo] extends NotNull { - implicit def apply(files: Iterable[File]): FilesInfo[F] - implicit val format: Format[FilesInfo[F]] + implicit def apply(files: Set[File]): FilesInfo[F] + implicit def unapply(info: FilesInfo[F]): Set[File] = info.files.map(_.file) + implicit val formats: Format[FilesInfo[F]] + import Cache._ + implicit def infosInputCache: InputCache[Set[File]] = wrapInputCache[Set[File],FilesInfo[F]] + implicit def infosOutputCache: OutputCache[Set[File]] = wrapOutputCache[Set[File],FilesInfo[F]] } private final class BasicStyle[F <: FileInfo](fileStyle: FileInfo.Style[F])(implicit infoFormat: Format[F]) extends Style[F] { - implicit def apply(files: Iterable[File]) = FilesInfo( (Set() ++ files.map(_.getAbsoluteFile)).map(fileStyle.apply) ) - implicit val format: Format[FilesInfo[F]] = wrap(_.files, (fs: Set[F]) => new FilesInfo(fs)) + implicit def apply(files: Set[File]): FilesInfo[F] = FilesInfo( files.map(_.getAbsoluteFile).map(fileStyle.apply) ) + implicit val formats: Format[FilesInfo[F]] = wrap(_.files, (fs: Set[F]) => new FilesInfo(fs)) } lazy val full: Style[HashModifiedFileInfo] = new BasicStyle(FileInfo.full)(FileInfo.full.format) lazy val hash: Style[HashFileInfo] = new BasicStyle(FileInfo.hash)(FileInfo.hash.format) diff --git a/cache/TrackingFormat.scala b/cache/TrackingFormat.scala index 1b6c463a0..166c22df2 100644 --- a/cache/TrackingFormat.scala +++ b/cache/TrackingFormat.scala @@ -2,29 +2,31 @@ package xsbt import java.io.File import scala.collection.mutable.{HashMap, Map, MultiMap, Set} -import sbinary.{DefaultProtocol, Format, Operations} +import scala.reflect.Manifest +import sbinary.{DefaultProtocol, Format} import DefaultProtocol._ import TrackingFormat._ +import CacheIO.{fromFile, toFile} import DependencyTracking.{DependencyMap => DMap, newMap} -private class TrackingFormat[T](directory: File, translateProducts: Boolean)(implicit tFormat: Format[T]) extends NotNull +private class TrackingFormat[T](directory: File, translateProducts: Boolean)(implicit tFormat: Format[T], mf: Manifest[T]) extends NotNull { - val indexFile = new File(directory, "index") val dependencyFile = new File(directory, "dependencies") def read(): DependencyTracking[T] = { - val indexMap = Operations.fromFile[Map[Int,T]](indexFile) + val indexMap = CacheIO.fromFile[Map[Int,T]](indexFile) val indexedFormat = wrap[T,Int](ignore => error("Read-only"), indexMap.apply) - Operations.fromFile(dependencyFile)(trackingFormat(translateProducts)(indexedFormat)) + val trackFormat = trackingFormat(translateProducts)(indexedFormat) + fromFile(trackFormat)(dependencyFile) } def write(tracking: DependencyTracking[T]) { val index = new IndexMap[T] val indexedFormat = wrap[T,Int](t => index(t), ignore => error("Write-only")) - - Operations.toFile(tracking)(dependencyFile)(trackingFormat(translateProducts)(indexedFormat)) - Operations.toFile(index.indices)(indexFile) + val trackFormat = trackingFormat(translateProducts)(indexedFormat) + toFile(trackFormat)(tracking)(dependencyFile) + toFile(index.indices)(indexFile) } } private object TrackingFormat diff --git a/cache/src/test/scala/Tracking.scala b/cache/src/test/scala/Tracking.scala deleted file mode 100644 index ff952556b..000000000 --- a/cache/src/test/scala/Tracking.scala +++ /dev/null @@ -1,45 +0,0 @@ -package xsbt - -import java.io.File - -trait examples -{ - def classpathTask: Task[Set[File]] - def sourcesTask: Task[Set[File]] - import DependencyTracking._ - lazy val compile = - changed(classpathTask, FilesInfo.lastModified, new File("cache/compile/classpath/")) { classpathChanges => - changed(sourcesTask, FilesInfo.hash, new File("cache/compile/sources/")) { sourceChanges => - invalidate(classpathChanges +++ sourceChanges, new File("cache/compile/dependencies/'")) { (report, tracking) => - val recompileSources = report.invalid ** sourceChanges.allInputs - val classpath = classpathChanges.allInputs - Task() - } - } - } - - trait sync - { - def sources: Task[Set[File]] = Task(Set.empty[File]) - def mapper: Task[FileMapper] = outputDirectory map(FileMapper.basic) - def outputDirectory: Task[File] = Task(new File("test")) - - import Task._ - lazy val task = syncTask - def syncTask = - (sources, mapper) bind { (srcs,mp) => - DependencyTracking.trackBasic(sources, FilesInfo.hash, new File("cache/sync/")) { (sourceChanges, report, tracking) => - Task - { - for(src <- report.invalid ** sourceChanges.allInputs) yield - { - val target = mp(src) - FileUtilities.copyFile(src, target) - tracking.product(src, target) - target - } - } - } - } - } -} \ No newline at end of file diff --git a/compile/ComponentCompiler.scala b/compile/ComponentCompiler.scala index 369c0cb85..c9d8cbadb 100644 --- a/compile/ComponentCompiler.scala +++ b/compile/ComponentCompiler.scala @@ -35,6 +35,7 @@ class ComponentCompiler(scalaVersion: String, compiler: RawCompiler, manager: Co * any resources from the source jars into a final jar.*/ private def compileSources(sourceJars: Iterable[File], targetJar: File, id: String) { + import Paths._ withTemporaryDirectory { dir => val extractedSources = (Set[File]() /: sourceJars) { (extracted, sourceJar)=> extracted ++ unzip(sourceJar, dir) } val (sourceFiles, resources) = extractedSources.partition(_.getName.endsWith(".scala")) @@ -42,8 +43,8 @@ class ComponentCompiler(scalaVersion: String, compiler: RawCompiler, manager: Co val xsbtiJars = manager.files(xsbtiID) val arguments = Seq("-d", outputDirectory.getAbsolutePath, "-cp", xsbtiJars.mkString(File.pathSeparator)) ++ sourceFiles.toSeq.map(_.getAbsolutePath) compiler(arguments) - copy(resources, outputDirectory, PathMapper.relativeTo(dir)) - zip(Seq(outputDirectory), targetJar, true, PathMapper.relativeTo(outputDirectory)) + copy(resources x (FileMapper.rebase(dir, outputDirectory))) + zip((outputDirectory ***) x (PathMapper.relativeTo(outputDirectory)), targetJar) } } } diff --git a/compile/src/test/scala/CompileTest.scala b/compile/src/test/scala/CompileTest.scala index dbb26516a..3e63d5240 100644 --- a/compile/src/test/scala/CompileTest.scala +++ b/compile/src/test/scala/CompileTest.scala @@ -45,6 +45,7 @@ object CompileTest extends Specification error("Resource not found: " + resource) prepare(manager, id, FileUtilities.asFile(src)) } + import Paths._ private def prepare(manager: ComponentManager, id: String, file: File): Unit = - FileUtilities.copy(Seq(file), manager.location(id), PathMapper.flat) + FileUtilities.copy(file x FileMapper.flat(manager.location(id))) } \ No newline at end of file diff --git a/project/build.properties b/project/build.properties index 2597cae5b..2b57548c4 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,7 +1,7 @@ -#Project Properties -#Sat Aug 15 11:30:36 EDT 2009 -project.name=xsbt +#Project properties +#Fri Aug 28 10:19:53 EDT 2009 project.organization=org.scala-tools.sbt +project.name=xsbt sbt.version=0.5.3-p1 -scala.version=2.7.5 project.version=0.7 +scala.version=2.7.5 diff --git a/project/build/XSbt.scala b/project/build/XSbt.scala index 7e387e868..fdf0669aa 100644 --- a/project/build/XSbt.scala +++ b/project/build/XSbt.scala @@ -19,13 +19,15 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub) val logSub = project(utilPath / "log", "Logging", new Base(_)) - val taskSub = project("tasks", "Tasks", new TaskProject(_), controlSub, collectionSub) + val taskSub = project(tasksPath, "Tasks", new TaskProject(_), controlSub, collectionSub) val cacheSub = project("cache", "Cache", new CacheProject(_), taskSub, ioSub) + val stdTaskSub = project(tasksPath / "standard", "Standard Tasks", new Base(_), taskSub, cacheSub) val compilerSub = project(compilePath, "Compile", new CompileProject(_), launchInterfaceSub, interfaceSub, ivySub, ioSub, classpathSub, compileInterfaceSub) /* Multi-subproject paths */ + def tasksPath = path("tasks") def launchPath = path("launch") def utilPath = path("util") def compilePath = path("compile") diff --git a/tasks/Task.scala b/tasks/Task.scala index 0b99198ac..7ff3d2d3d 100644 --- a/tasks/Task.scala +++ b/tasks/Task.scala @@ -3,12 +3,14 @@ package xsbt import Task.{mapTask,bindTask, ITask} import scala.collection.{mutable,immutable} -sealed abstract class Task[O] extends Identity +sealed trait Result[O] extends NotNull +final case class Value[O](t: O) extends Result[O] +sealed abstract class Task[O] extends Identity with Result[O] { type Input def dependencies: TreeHashSet[Task[_]] // IMPORTANT!! immutable.HashSet is NOT suitable. It has issues with multi-threaded access def map[N](f: O => N): ITask[O,N] - def bind[N](f: O => Task[N]): ITask[O,N] + def bind[N](f: O => Result[N]): ITask[O,N] def dependsOn(addDependencies: Task[_]*): ITask[Input,O] def named(s: String): ITask[Input,O] } @@ -21,7 +23,7 @@ private final class M[I,O,R <: Result[O]](name: Option[String]) final def dependsOn(addDependencies: Task[_]*) = new M(name)(dependencies ++ addDependencies)(extract)(compute) final def map[N](f: O => N) = mapTask(this)(_(this))(f) - final def bind[N](f: O => Task[N]) = bindTask(this)(_(this))(f) + final def bind[N](f: O => Result[N]) = bindTask(this)(_(this))(f) final def named(s: String) = name match { @@ -42,9 +44,6 @@ private trait Results extends NotNull def contains(task: Task[_]): Boolean } -private sealed trait Result[O] extends NotNull -private final case class NewTask[O](t: Task[O]) extends Result[O] -private final case class Value[O](t: O) extends Result[O] object Task { @@ -52,8 +51,8 @@ object Task import Function.tupled def apply[O](o: => O): ITask[Unit,O] = new M[Unit,O,Value[O]]()(r => ())( u => Value(o) ) - def bindTask[I,O](dependencies: Task[_]*)(extract: Results => I)(compute: I => Task[O]): ITask[I,O] = - new M[I,O,NewTask[O]](dependencies : _*)(extract)(in => NewTask(compute(in))) + def bindTask[I,O](dependencies: Task[_]*)(extract: Results => I)(compute: I => Result[O]): ITask[I,O] = + new M[I,O,Result[O]](dependencies : _*)(extract)(compute) def mapTask[I,O](dependencies: Task[_]*)(extract: Results => I)(compute: I => O): ITask[I,O] = new M[I,O,Value[O]](dependencies : _*)(extract)(in => Value(compute(in))) @@ -123,7 +122,7 @@ object Task def get(results: Results) = HCons(results(head), tail.get(results)) def map[X](f: HListType => X): ITask[HListType,X] = mapTask(tasks: _*)(get)(f) - def bind[X](f: HListType => Task[X]): ITask[HListType,X] = bindTask(tasks: _*)(get)(f) + def bind[X](f: HListType => Result[X]): ITask[HListType,X] = bindTask(tasks: _*)(get)(f) def join: ITask[HListType,HListType] = map(identity[HListType]) } val TNil = new TNil @@ -133,13 +132,20 @@ object Task final class Builder2[A,B] private[Task](a: Task[A], b: Task[B]) extends NotNull { def map[X](f: (A,B) => X): ITask[(A,B),X] = mapTask(a,b)(r => (r(a), r(b)))(tupled(f)) - def bind[X](f: (A,B) => Task[X]): ITask[(A,B),X] = bindTask(a,b)( r => (r(a), r(b)) )(tupled(f)) + def bind[X](f: (A,B) => Result[X]): ITask[(A,B),X] = bindTask(a,b)( r => (r(a), r(b)) )(tupled(f)) } implicit def threeToBuilder[A,B,C](t: (Task[A], Task[B], Task[C])): Builder3[A,B,C] = t match { case (a,b,c) => new Builder3(a,b,c) } final class Builder3[A,B,C] private[Task](a: Task[A], b: Task[B], c: Task[C]) extends NotNull { def map[X](f: (A,B,C) => X): ITask[(A,B,C),X] = mapTask(a,b,c)( r => (r(a), r(b), r(c)) )(tupled(f)) - def bind[X](f: (A,B,C) => Task[X]): ITask[(A,B,C),X] = bindTask(a,b,c)( r => (r(a), r(b), r(c)) )(tupled(f)) + def bind[X](f: (A,B,C) => Result[X]): ITask[(A,B,C),X] = bindTask(a,b,c)( r => (r(a), r(b), r(c)) )(tupled(f)) + } + + implicit def fourToBuilder[A,B,C,D](t: (Task[A], Task[B], Task[C], Task[D])): Builder4[A,B,C,D] = t match { case (a,b,c,d) => new Builder4(a,b,c,d) } + final class Builder4[A,B,C,D] private[Task](a: Task[A], b: Task[B], c: Task[C], d: Task[D]) extends NotNull + { + def map[X](f: (A,B,C,D) => X): ITask[(A,B,C,D),X] = mapTask(a,b,c,d)( r => (r(a), r(b), r(c), r(d)) )(tupled(f)) + def bind[X](f: (A,B,C,D) => Result[X]): ITask[(A,B,C,D),X] = bindTask(a,b,c,d)( r => (r(a), r(b), r(c),r(d)) )(tupled(f)) } } diff --git a/tasks/TaskScheduler.scala b/tasks/TaskScheduler.scala index 9c2f47a9f..8810cfd48 100644 --- a/tasks/TaskScheduler.scala +++ b/tasks/TaskScheduler.scala @@ -176,7 +176,7 @@ private final class TaskScheduler[O](root: Task[O], strategy: ScheduleStrategy[W private def success[O](task: Task[O], value: Result[O]): Unit = value match { - case NewTask(t) => + case t: Task[O] => if(t eq task) { failureReports += WorkFailure(t, CircularDependency(t, task)) diff --git a/util/io/FileUtilities.scala b/util/io/FileUtilities.scala index 7458c4069..eff8897ed 100644 --- a/util/io/FileUtilities.scala +++ b/util/io/FileUtilities.scala @@ -13,6 +13,7 @@ import java.util.jar.{Attributes, JarEntry, JarFile, JarInputStream, JarOutputSt import java.util.zip.{GZIPOutputStream, ZipEntry, ZipFile, ZipInputStream, ZipOutputStream} import scala.collection.mutable.HashSet import scala.reflect.{Manifest => SManifest} +import Function.tupled object FileUtilities { @@ -38,7 +39,7 @@ object FileUtilities def toFile(url: URL) = try { new File(url.toURI) } catch { case _: URISyntaxException => new File(url.getPath) } - + /** Converts the given URL to a File. If the URL is for an entry in a jar, the File for the jar is returned. */ def asFile(url: URL): File = { @@ -52,7 +53,8 @@ object FileUtilities case _ => error("Invalid protocol " + url.getProtocol) } } - + def assertDirectory(file: File) { assert(file.isDirectory, (if(file.exists) "Not a directory: " else "Directory not found: ") + file) } + def assertDirectory(file: File*) { file.foreach(assertDirectory) } // "base.extension" -> (base, extension) def split(name: String): (String, String) = @@ -198,7 +200,8 @@ object FileUtilities file.delete } } - def listFiles(dir: File, filter: FileFilter): Array[File] = wrapNull(dir.listFiles(filter)) + def listFiles(filter: java.io.FileFilter)(dir: File): Array[File] = wrapNull(dir.listFiles(filter)) + def listFiles(dir: File, filter: java.io.FileFilter): Array[File] = wrapNull(dir.listFiles(filter)) def listFiles(dir: File): Array[File] = wrapNull(dir.listFiles()) private def wrapNull(a: Array[File]) = { @@ -210,23 +213,18 @@ object FileUtilities /** Creates a jar file. - * @param sources The files to include in the jar file. + * @param sources The files to include in the jar file paired with the entry name in the jar. * @param outputJar The file to write the jar to. - * @param manifest The manifest for the jar. - * @param recursive If true, any directories in sources are recursively processed. - * @param mapper The mapper that determines the name of a File in the jar. */ - def jar(sources: Iterable[File], outputJar: File, manifest: Manifest, recursive: Boolean, mapper: PathMapper): Unit = - archive(sources, outputJar, Some(manifest), recursive, mapper) + * @param manifest The manifest for the jar.*/ + def jar(sources: Iterable[(File,String)], outputJar: File, manifest: Manifest): Unit = + archive(sources, outputJar, Some(manifest)) /** Creates a zip file. - * @param sources The files to include in the jar file. - * @param outputZip The file to write the zip to. - * @param recursive If true, any directories in sources are recursively processed. Otherwise, - * they are not - * @param mapper The mapper that determines the name of a File in the jar. */ - def zip(sources: Iterable[File], outputZip: File, recursive: Boolean, mapper: PathMapper): Unit = - archive(sources, outputZip, None, recursive, mapper) + * @param sources The files to include in the zip file paired with the entry name in the zip. + * @param outputZip The file to write the zip to.*/ + def zip(sources: Iterable[(File,String)], outputZip: File): Unit = + archive(sources, outputZip, None) - private def archive(sources: Iterable[File], outputFile: File, manifest: Option[Manifest], recursive: Boolean, mapper: PathMapper) + private def archive(sources: Iterable[(File,String)], outputFile: File, manifest: Option[Manifest]) { if(outputFile.isDirectory) error("Specified output file " + outputFile + " is a directory.") @@ -237,22 +235,18 @@ object FileUtilities withZipOutput(outputFile, manifest) { output => val createEntry: (String => ZipEntry) = if(manifest.isDefined) new JarEntry(_) else new ZipEntry(_) - writeZip(sources, output, recursive, mapper)(createEntry) + writeZip(sources, output)(createEntry) } } } - private def writeZip(sources: Iterable[File], output: ZipOutputStream, recursive: Boolean, mapper: PathMapper)(createEntry: String => ZipEntry) + private def writeZip(sources: Iterable[(File,String)], output: ZipOutputStream)(createEntry: String => ZipEntry) { - def add(sourceFile: File) + def add(sourceFile: File, name: String) { if(sourceFile.isDirectory) - { - if(recursive) - listFiles(sourceFile).foreach(add) - } + () else if(sourceFile.exists) { - val name = mapper(sourceFile) val nextEntry = createEntry(name) nextEntry.setTime(sourceFile.lastModified) output.putNextEntry(nextEntry) @@ -261,7 +255,7 @@ object FileUtilities else error("Source " + sourceFile + " does not exist.") } - sources.foreach(add) + sources.foreach(tupled(add)) output.closeEntry() } @@ -314,30 +308,20 @@ object FileUtilities else None } - def copy(sources: Iterable[File], destinationDirectory: File, mapper: PathMapper) = + def copy(sources: Iterable[(File,File)]): Set[File] = Set( sources.map(tupled(copyImpl)).toSeq.toArray : _*) + private def copyImpl(from: File, to: File): File = { - val targetSet = new scala.collection.mutable.HashSet[File] - copyImpl(sources, destinationDirectory) { from => - val to = new File(destinationDirectory, mapper(from)) - targetSet += to - if(!to.exists || from.lastModified > to.lastModified) + if(!to.exists || from.lastModified > to.lastModified) + { + if(from.isDirectory) + createDirectory(to) + else { - if(from.isDirectory) - createDirectory(to) - else - { - //log.debug("Copying " + source + " to " + toPath) - copyFile(from, to) - } + createDirectory(to.getParentFile) + copyFile(from, to) } } - targetSet.readOnly - } - private def copyImpl(sources: Iterable[File], target: File)(doCopy: File => Unit) - { - if(!target.isDirectory) - createDirectory(target) - sources.toList.foreach(doCopy) + to } def copyFile(sourceFile: File, targetFile: File) { @@ -361,5 +345,4 @@ object FileUtilities else error("String cannot be encoded by charset " + charset.name) } - } diff --git a/util/io/PathMapper.scala b/util/io/PathMapper.scala index eea4c37c1..2d04b5c2e 100644 --- a/util/io/PathMapper.scala +++ b/util/io/PathMapper.scala @@ -8,22 +8,27 @@ import java.io.File trait PathMapper extends NotNull { def apply(file: File): String + def apply(files: Set[File]): Iterable[(File,String)] = files.projection.map(f => (f,apply(f))) } -class PMapper(f: File => String) extends PathMapper +final case class RelativePathMapper(base: File) extends PMapper(file => FileUtilities.relativize(base, file).getOrElse(file.getPath)) +final case object BasicPathMapper extends PMapper(_.getPath) +final case object FlatPathMapper extends PMapper(_.getName) +class PMapper(val f: File => String) extends PathMapper { - def apply(file: File) = f(file) + def apply(file: File): String = f(file) } object PathMapper { - val basic = new PMapper(_.getPath) - def relativeTo(base: File) = new PMapper(file => FileUtilities.relativize(base, file).getOrElse(file.getPath)) - val flat = new PMapper(_.getName) - def apply(f: File => String) = new PMapper(f) + val basic: PathMapper = BasicPathMapper + def relativeTo(base: File): PathMapper = RelativePathMapper(base) + val flat = FlatPathMapper + def apply(f: File => String): PathMapper = new PMapper(f) } trait FileMapper extends NotNull { def apply(file: File): File + def apply(files: Set[File]): Iterable[(File,File)] = files.projection.map(f => (f,apply(f))) } class FMapper(f: File => File) extends FileMapper { diff --git a/util/io/Paths.scala b/util/io/Paths.scala new file mode 100644 index 000000000..c4320d6fc --- /dev/null +++ b/util/io/Paths.scala @@ -0,0 +1,44 @@ +package xsbt + +import java.io.File + +object Paths +{ + implicit def fileToPath(f: File): Path = new Path(f) + implicit def pathToFile(p: Path): File = p.asFile + implicit def filesToPaths(fs: Set[File]): Paths = new Paths(fs) + implicit def filesToPaths(fs: Iterable[File]): Paths = + fs match + { + case s: Set[File] => filesToPaths(s) + case _ => new Paths(Set(fs.toSeq : _*)) + } +} + +import Paths._ +trait PathBase extends NotNull +{ + def files: Set[File] + def x(mapper: PathMapper): Iterable[(File,String)] = mapper(files) + def x(mapper: FileMapper): Iterable[(File,File)] = mapper(files) + def *(filter: java.io.FileFilter): Set[File] = files.flatMap(FileUtilities.listFiles(filter)) + def **(filter: java.io.FileFilter): Set[File] = files.filter(filter.accept) ++ files.flatMap(_ * AllPassFilter ** filter) + def *** = **(AllPassFilter) + def abs = files.map(_.getAbsoluteFile) + def descendentsExcept(include: java.io.FileFilter, intermediateExclude: java.io.FileFilter): Set[File] = + (this ** include) -- (this ** intermediateExclude ** include) +} + +final class Paths(val files: Set[File]) extends PathBase +{ + def \(subPath: String) = /(subPath) + def /(subPath: String): Set[File] = files.flatMap(FileUtilities.listFiles) +} +final class Path(val asFile: File) extends PathBase +{ + def files = Set(asFile) + def \(subPath: String) = /(subPath) + def /(subPath: String) = new File(asFile, subPath.replace('/', File.separatorChar).replace('\\', File.separatorChar)) + def ++(files: Set[File]) = files + asFile + def ++(file: File) = Set(file, asFile) +} \ No newline at end of file