2010-01-06 01:50:43 +01:00
|
|
|
/* sbt -- Simple Build Tool
|
|
|
|
|
* Copyright 2009, 2010 Mark Harrah
|
|
|
|
|
*/
|
2010-06-14 04:59:29 +02:00
|
|
|
package sbt
|
2009-08-30 19:01:02 +02:00
|
|
|
|
2009-08-31 16:43:41 +02:00
|
|
|
import java.io.{File,IOException}
|
2009-08-30 19:01:02 +02:00
|
|
|
import CacheIO.{fromFile, toFile}
|
2010-10-27 00:02:27 +02:00
|
|
|
import sbinary.{Format, JavaIO}
|
2009-08-30 19:01:02 +02:00
|
|
|
import scala.reflect.Manifest
|
2010-06-14 04:59:29 +02:00
|
|
|
import IO.{delete, read, write}
|
2010-06-07 16:50:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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.*/
|
2010-06-14 04:59:29 +02:00
|
|
|
def tstamp(cacheFile: File, useStartTime: Boolean = true): Timestamp = new Timestamp(cacheFile, useStartTime)
|
2010-06-07 16:50:51 +02:00
|
|
|
/** Creates a tracker that only evaluates a function when the input has changed.*/
|
2010-10-27 00:02:27 +02:00
|
|
|
def changed[O](cacheFile: File)(implicit format: Format[O], equiv: Equiv[O]): Changed[O] =
|
|
|
|
|
new Changed[O](cacheFile)
|
2010-06-07 16:50:51 +02:00
|
|
|
|
2010-09-04 14:08:17 +02:00
|
|
|
/** Creates a tracker that provides the difference between a set of input files for successive invocations.*/
|
|
|
|
|
def diffInputs(cache: File, style: FilesInfo.Style): Difference =
|
|
|
|
|
Difference.inputs(cache, style)
|
|
|
|
|
/** Creates a tracker that provides the difference between a set of output files for successive invocations.*/
|
|
|
|
|
def diffOutputs(cache: File, style: FilesInfo.Style): Difference =
|
|
|
|
|
Difference.outputs(cache, style)
|
2010-06-07 16:50:51 +02:00
|
|
|
}
|
2009-08-30 19:01:02 +02:00
|
|
|
|
|
|
|
|
trait Tracked extends NotNull
|
|
|
|
|
{
|
2010-06-07 16:50:51 +02:00
|
|
|
/** Cleans outputs and clears the cache.*/
|
|
|
|
|
def clean: Unit
|
2009-08-30 19:01:02 +02:00
|
|
|
}
|
2010-06-07 16:50:51 +02:00
|
|
|
class Timestamp(val cacheFile: File, useStartTime: Boolean) extends Tracked
|
2010-01-23 15:33:42 +01:00
|
|
|
{
|
2010-06-07 16:50:51 +02:00
|
|
|
def clean = delete(cacheFile)
|
2010-09-04 14:08:17 +02:00
|
|
|
/** Reads the previous timestamp, evaluates the provided function,
|
|
|
|
|
* and then updates the timestamp if the function completes normally.*/
|
2010-06-07 16:50:51 +02:00
|
|
|
def apply[T](f: Long => T): T =
|
2010-01-23 15:33:42 +01:00
|
|
|
{
|
2010-06-07 16:50:51 +02:00
|
|
|
val start = now()
|
|
|
|
|
val result = f(readTimestamp)
|
|
|
|
|
write(cacheFile, (if(useStartTime) start else now()).toString)
|
|
|
|
|
result
|
2010-01-23 15:33:42 +01:00
|
|
|
}
|
2010-06-07 16:50:51 +02:00
|
|
|
private def now() = System.currentTimeMillis
|
2010-01-23 15:33:42 +01:00
|
|
|
def readTimestamp: Long =
|
2010-06-07 16:50:51 +02:00
|
|
|
try { read(cacheFile).toLong }
|
2010-01-23 15:33:42 +01:00
|
|
|
catch { case _: NumberFormatException | _: java.io.FileNotFoundException => 0 }
|
|
|
|
|
}
|
2009-08-30 19:01:02 +02:00
|
|
|
|
2010-10-27 00:02:27 +02:00
|
|
|
class Changed[O](val cacheFile: File)(implicit equiv: Equiv[O], format: Format[O]) extends Tracked
|
2009-08-30 19:01:02 +02:00
|
|
|
{
|
2010-06-07 16:50:51 +02:00
|
|
|
def clean = delete(cacheFile)
|
2010-09-04 14:08:17 +02:00
|
|
|
def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O => O2 = value =>
|
2010-06-07 16:50:51 +02:00
|
|
|
{
|
2010-10-27 00:02:27 +02:00
|
|
|
if(uptodate(value))
|
2010-06-07 16:50:51 +02:00
|
|
|
ifUnchanged(value)
|
|
|
|
|
else
|
|
|
|
|
{
|
2010-10-27 00:02:27 +02:00
|
|
|
update(value)
|
2010-06-07 16:50:51 +02:00
|
|
|
ifChanged(value)
|
2009-08-30 19:01:02 +02:00
|
|
|
}
|
2010-06-07 16:50:51 +02:00
|
|
|
}
|
2010-10-27 00:02:27 +02:00
|
|
|
import JavaIO._
|
|
|
|
|
def update(value: O): Unit = Using.fileOutputStream(false)(cacheFile)(stream => format.writes(stream, value))
|
|
|
|
|
def uptodate(value: O): Boolean =
|
|
|
|
|
try {
|
|
|
|
|
Using.fileInputStream(cacheFile) {
|
|
|
|
|
stream => equiv.equiv(value, format.reads(stream))
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
case _: IOException => false
|
|
|
|
|
}
|
2009-08-30 19:01:02 +02:00
|
|
|
}
|
2009-08-31 03:53:38 +02:00
|
|
|
object Difference
|
2009-08-30 19:01:02 +02:00
|
|
|
{
|
2009-08-31 03:53:38 +02:00
|
|
|
sealed class Constructor private[Difference](defineClean: Boolean, filesAreOutputs: Boolean) extends NotNull
|
|
|
|
|
{
|
2010-09-04 14:08:17 +02:00
|
|
|
def apply(cache: File, style: FilesInfo.Style): Difference = new Difference(cache, style, defineClean, filesAreOutputs)
|
2009-08-31 03:53:38 +02:00
|
|
|
}
|
2010-06-07 16:50:51 +02:00
|
|
|
/** 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.*/
|
2009-08-31 03:53:38 +02:00
|
|
|
object outputs extends Constructor(true, true)
|
2010-06-07 16:50:51 +02:00
|
|
|
/** 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.*/
|
2009-08-31 03:53:38 +02:00
|
|
|
object inputs extends Constructor(false, false)
|
|
|
|
|
}
|
2010-09-04 14:08:17 +02:00
|
|
|
class Difference(val cache: File, val style: FilesInfo.Style, val defineClean: Boolean, val filesAreOutputs: Boolean) extends Tracked
|
2009-08-31 03:53:38 +02:00
|
|
|
{
|
2010-06-07 16:50:51 +02:00
|
|
|
def clean =
|
|
|
|
|
{
|
|
|
|
|
if(defineClean) delete(raw(cachedFilesInfo)) else ()
|
|
|
|
|
clearCache()
|
|
|
|
|
}
|
2010-06-14 04:59:29 +02:00
|
|
|
private def clearCache() = delete(cache)
|
2009-08-30 19:01:02 +02:00
|
|
|
|
2009-08-31 03:53:38 +02:00
|
|
|
private def cachedFilesInfo = fromFile(style.formats, style.empty)(cache)(style.manifest).files
|
2010-09-04 14:08:17 +02:00
|
|
|
private def raw(fs: Set[style.F]): Set[File] = fs.map(_.file)
|
2009-08-30 19:01:02 +02:00
|
|
|
|
2010-09-04 14:08:17 +02:00
|
|
|
def apply[T](files: Set[File])(f: ChangeReport[File] => T): T =
|
2010-06-07 16:50:51 +02:00
|
|
|
{
|
|
|
|
|
val lastFilesInfo = cachedFilesInfo
|
2010-09-04 14:08:17 +02:00
|
|
|
apply(files, lastFilesInfo)(f)(_ => files)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def apply[T](f: ChangeReport[File] => T)(implicit toFiles: T => Set[File]): T =
|
|
|
|
|
{
|
|
|
|
|
val lastFilesInfo = cachedFilesInfo
|
|
|
|
|
apply(raw(lastFilesInfo), lastFilesInfo)(f)(toFiles)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private def abs(files: Set[File]) = files.map(_.getAbsoluteFile)
|
|
|
|
|
private[this] def apply[T](files: Set[File], lastFilesInfo: Set[style.F])(f: ChangeReport[File] => T)(extractFiles: T => Set[File]): T =
|
|
|
|
|
{
|
2010-06-07 16:50:51 +02:00
|
|
|
val lastFiles = raw(lastFilesInfo)
|
2010-09-04 14:08:17 +02:00
|
|
|
val currentFiles = abs(files)
|
2010-06-07 16:50:51 +02:00
|
|
|
val currentFilesInfo = style(currentFiles)
|
2009-08-30 19:01:02 +02:00
|
|
|
|
2010-06-07 16:50:51 +02:00
|
|
|
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
|
2009-08-30 19:01:02 +02:00
|
|
|
}
|
2010-06-07 16:50:51 +02:00
|
|
|
|
|
|
|
|
val result = f(report)
|
2010-09-04 14:08:17 +02:00
|
|
|
val info = if(filesAreOutputs) style(abs(extractFiles(result))) else currentFilesInfo
|
2010-06-07 16:50:51 +02:00
|
|
|
toFile(style.formats)(info)(cache)(style.manifest)
|
|
|
|
|
result
|
|
|
|
|
}
|
2009-08-30 19:01:02 +02:00
|
|
|
}
|
2010-09-04 14:08:17 +02:00
|
|
|
|
|
|
|
|
object FileFunction {
|
|
|
|
|
type UpdateFunction = (ChangeReport[File], ChangeReport[File]) => Set[File]
|
2010-01-06 01:50:43 +01:00
|
|
|
|
2010-09-04 14:08:17 +02:00
|
|
|
def cached(cacheBaseDirectory: File, inStyle: FilesInfo.Style = FilesInfo.lastModified, outStyle: FilesInfo.Style = FilesInfo.exists)(action: Set[File] => Set[File]): Set[File] => Set[File] =
|
|
|
|
|
cached(cacheBaseDirectory)(inStyle, outStyle)( (in, out) => action(in.checked) )
|
2010-01-06 01:50:43 +01:00
|
|
|
|
2010-09-04 14:08:17 +02:00
|
|
|
def cached(cacheBaseDirectory: File)(inStyle: FilesInfo.Style, outStyle: FilesInfo.Style)(action: UpdateFunction): Set[File] => Set[File] =
|
2010-01-06 01:50:43 +01:00
|
|
|
{
|
2010-09-04 14:08:17 +02:00
|
|
|
import Path._
|
|
|
|
|
lazy val inCache = Difference.inputs(cacheBaseDirectory / "in-cache", inStyle)
|
|
|
|
|
lazy val outCache = Difference.outputs(cacheBaseDirectory / "out-cache", outStyle)
|
|
|
|
|
inputs =>
|
2010-01-06 01:50:43 +01:00
|
|
|
{
|
2010-09-04 14:08:17 +02:00
|
|
|
inCache(inputs) { inReport =>
|
|
|
|
|
outCache { outReport =>
|
|
|
|
|
if(inReport.modified.isEmpty && outReport.modified.isEmpty)
|
|
|
|
|
outReport.checked
|
|
|
|
|
else
|
|
|
|
|
action(inReport, outReport)
|
|
|
|
|
}
|
2010-01-06 01:50:43 +01:00
|
|
|
}
|
2009-08-30 19:01:02 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|