mirror of https://github.com/sbt/sbt.git
Justin's changes:
* preserve modification times when unzipping * error message when products for a fileTask are empty
This commit is contained in:
parent
27922a3256
commit
fad08500a5
|
|
@ -59,6 +59,7 @@ object FileTasks
|
|||
def apply[T](label: String, files: ProductsSources, log: Logger)(ifOutofdate: => T)(ifUptodate: => T): T =
|
||||
{
|
||||
val products = files.products
|
||||
require(!products.isEmpty, "No products were specified; products must be known in advance.")
|
||||
existenceCheck[T](label, products, log)(ifOutofdate)
|
||||
{
|
||||
val sources = files.sources
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009 Mark Harrah, Nathan Hamblen
|
||||
* Copyright 2008, 2009, 2010 Mark Harrah, Nathan Hamblen, Justin Caballero
|
||||
*/
|
||||
package sbt
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ object FileUtilities
|
|||
new Preserved(readOnly(pathMap), tmp)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Gzips the file 'in' and writes it to 'out'. 'in' cannot be the same file as 'out'. */
|
||||
def gzip(in: Path, out: Path, log: Logger): Option[String] =
|
||||
{
|
||||
|
|
@ -84,7 +84,7 @@ object FileUtilities
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Creates a jar file.
|
||||
* @param sources The files to include in the jar file. The path used for the jar is
|
||||
* relative to the base directory for the source. That is, the path in the jar for source
|
||||
|
|
@ -108,7 +108,7 @@ object FileUtilities
|
|||
* @param log The Logger to use. */
|
||||
def zip(sources: Iterable[Path], outputZip: Path, recursive: Boolean, log: Logger) =
|
||||
archive(sources, outputZip, None, recursive, log)
|
||||
|
||||
|
||||
private def archive(sources: Iterable[Path], outputPath: Path, manifest: Option[Manifest], recursive: Boolean, log: Logger) =
|
||||
{
|
||||
log.info("Packaging " + outputPath + " ...")
|
||||
|
|
@ -129,7 +129,7 @@ object FileUtilities
|
|||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private def writeZip(sources: Iterable[Path], output: ZipOutputStream, recursive: Boolean, log: Logger)(createEntry: String => ZipEntry) =
|
||||
{
|
||||
def add(source: Path)
|
||||
|
|
@ -156,7 +156,7 @@ object FileUtilities
|
|||
sources.foreach(add)
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
private def withZipOutput(file: File, manifest: Option[Manifest], log: Logger)(f: ZipOutputStream => Option[String]): Option[String] =
|
||||
{
|
||||
writeStream(file, log)
|
||||
|
|
@ -194,7 +194,7 @@ object FileUtilities
|
|||
/** Unzips the contents of the zip file <code>from</code> to the <code>toDirectory</code> directory.*/
|
||||
def unzip(from: URL, toDirectory: Path, log: Logger): Either[String, Set[Path]] =
|
||||
unzip(from, toDirectory, AllPassFilter, log)
|
||||
|
||||
|
||||
/** Unzips the contents of the zip file <code>from</code> to the <code>toDirectory</code> directory.
|
||||
* Only the entries that match the given filter are extracted. */
|
||||
def unzip(from: Path, toDirectory: Path, filter: NameFilter, log: Logger): Either[String, Set[Path]] =
|
||||
|
|
@ -220,6 +220,9 @@ object FileUtilities
|
|||
private def extract(from: ZipInputStream, toDirectory: Path, filter: NameFilter, log: Logger) =
|
||||
{
|
||||
val set = new scala.collection.mutable.HashSet[Path]
|
||||
// don't touch dirs as we unzip because we don't know order of zip entires (any child will
|
||||
// update the dir's time)
|
||||
val dirTimes = new scala.collection.mutable.HashMap[Path, Long]
|
||||
def next(): Option[String] =
|
||||
{
|
||||
val entry = from.getNextEntry
|
||||
|
|
@ -228,21 +231,23 @@ object FileUtilities
|
|||
else
|
||||
{
|
||||
val name = entry.getName
|
||||
val result =
|
||||
val entryErr =
|
||||
if(filter.accept(name))
|
||||
{
|
||||
val target = Path.fromString(toDirectory, name)
|
||||
log.debug("Extracting zip entry '" + name + "' to '" + target + "'")
|
||||
val result =
|
||||
if(entry.isDirectory)
|
||||
createDirectory(target, log)
|
||||
else
|
||||
if(entry.isDirectory)
|
||||
{
|
||||
dirTimes += target -> entry.getTime
|
||||
createDirectory(target, log)
|
||||
}
|
||||
else
|
||||
writeStream(target.asFile, log) { out => FileUtilities.transfer(from, out, log) } orElse
|
||||
{
|
||||
set += target
|
||||
writeStream(target.asFile, log) { out => FileUtilities.transfer(from, out, log) }
|
||||
touchExisting(target.asFile, entry.getTime, log)
|
||||
None
|
||||
}
|
||||
//target.asFile.setLastModified(entry.getTime)
|
||||
result
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -250,12 +255,14 @@ object FileUtilities
|
|||
None
|
||||
}
|
||||
from.closeEntry()
|
||||
result match { case None => next(); case x => x }
|
||||
entryErr orElse next()
|
||||
}
|
||||
}
|
||||
next().toLeft(readOnly(set))
|
||||
val result = next()
|
||||
for ((dir, time) <- dirTimes) touchExisting(dir.asFile, time, log)
|
||||
result.toLeft(readOnly(set))
|
||||
}
|
||||
|
||||
|
||||
/** Copies all bytes from the given input stream to the given output stream.
|
||||
* Neither stream is closed.*/
|
||||
def transfer(in: InputStream, out: OutputStream, log: Logger): Option[String] =
|
||||
|
|
@ -293,15 +300,18 @@ object FileUtilities
|
|||
Control.trapUnit("Could not create file " + file + ": ", log)
|
||||
{
|
||||
if(file.exists)
|
||||
{
|
||||
def updateFailBase = "Could not update last modified for file " + file
|
||||
Control.trapUnit(updateFailBase + ": ", log)
|
||||
{ if(file.setLastModified(System.currentTimeMillis)) None else Some(updateFailBase) }
|
||||
}
|
||||
touchExisting(file, System.currentTimeMillis, log)
|
||||
else
|
||||
createDirectory(file.getParentFile, log) orElse { file.createNewFile(); None }
|
||||
}
|
||||
}
|
||||
/** Sets the last mod time on the given {@code file}, which must already exist */
|
||||
private def touchExisting(file: File, time: Long, log: Logger): Option[String] =
|
||||
{
|
||||
def updateFailBase = "Could not update last modified for file " + file
|
||||
Control.trapUnit(updateFailBase + ": ", log)
|
||||
{ if(file.setLastModified(time)) None else Some(updateFailBase) }
|
||||
}
|
||||
/** Creates a directory at the given location.*/
|
||||
def createDirectory(dir: Path, log: Logger): Option[String] = createDirectory(dir.asFile, log)
|
||||
/** Creates a directory at the given location.*/
|
||||
|
|
@ -346,7 +356,7 @@ object FileUtilities
|
|||
{
|
||||
val randomName = "sbt_" + java.lang.Integer.toHexString(random.nextInt)
|
||||
val f = new File(temporaryDirectory, randomName)
|
||||
|
||||
|
||||
if(createDirectory(f, log).isEmpty)
|
||||
Right(f)
|
||||
else
|
||||
|
|
@ -380,7 +390,7 @@ object FileUtilities
|
|||
{ file.delete() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Copies the files declared in <code>sources</code> to the <code>destinationDirectory</code>
|
||||
* directory. The source directory hierarchy is flattened so that all copies are immediate
|
||||
* children of <code>destinationDirectory</code>. Directories are not recursively entered.*/
|
||||
|
|
@ -435,18 +445,36 @@ object FileUtilities
|
|||
def download(url: URL, to: File, log: Logger) =
|
||||
{
|
||||
readStream(url, log) { inputStream =>
|
||||
writeStream(to, log) { outputStream =>
|
||||
writeStream(to, log) { outputStream =>
|
||||
transfer(inputStream, outputStream, log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@code copy(sources, destinationDirectory, false, log)}.
|
||||
*/
|
||||
def copy(sources: Iterable[Path], destinationDirectory: Path, log: Logger): Either[String, Set[Path]] =
|
||||
copy(sources, destinationDirectory, false, log)
|
||||
|
||||
/**
|
||||
* Equivalent to {@code copy(sources, destinationDirectory, overwrite, false, log)}.
|
||||
*/
|
||||
def copy(sources: Iterable[Path], destinationDirectory: Path, overwrite: Boolean, log: Logger): Either[String, Set[Path]] =
|
||||
copy(sources, destinationDirectory, overwrite, false, log)
|
||||
|
||||
/** Copies the files declared in <code>sources</code> to the <code>destinationDirectory</code>
|
||||
* directory. Directories are not recursively entered. The destination hierarchy matches the
|
||||
* source paths relative to any base directories. For example:
|
||||
*
|
||||
* A source <code>(basePath ##) / x / y</code> is copied to <code>destinationDirectory / x / y</code>.
|
||||
* */
|
||||
def copy(sources: Iterable[Path], destinationDirectory: Path, log: Logger) =
|
||||
*
|
||||
* @param overwrite if true, existing destination files are always overwritten
|
||||
* @param preserveLastModified if true, the last modified time of copied files will be set equal to
|
||||
* their corresponding source files.
|
||||
*/
|
||||
def copy(sources: Iterable[Path], destinationDirectory: Path,
|
||||
overwrite: Boolean, preserveLastModified: Boolean, log: Logger): Either[String, Set[Path]] =
|
||||
{
|
||||
val targetSet = new scala.collection.mutable.HashSet[Path]
|
||||
copyImpl(sources, destinationDirectory, log)
|
||||
|
|
@ -457,22 +485,27 @@ object FileUtilities
|
|||
val toPath = Path.fromString(destinationDirectory, source.relativePath)
|
||||
targetSet += toPath
|
||||
val to = toPath.asFile
|
||||
if(!to.exists || from.lastModified > to.lastModified)
|
||||
if(!to.exists || overwrite || from.lastModified > to.lastModified)
|
||||
{
|
||||
if(from.isDirectory)
|
||||
createDirectory(to, log)
|
||||
val result =
|
||||
if(from.isDirectory)
|
||||
createDirectory(to, log)
|
||||
else
|
||||
{
|
||||
log.debug("Copying " + source + " to " + toPath)
|
||||
copyFile(from, to, log)
|
||||
}
|
||||
if (result.isEmpty && preserveLastModified)
|
||||
touchExisting(to, from.lastModified, log)
|
||||
else
|
||||
{
|
||||
log.debug("Copying " + source + " to " + toPath)
|
||||
copyFile(from, to, log)
|
||||
}
|
||||
result
|
||||
}
|
||||
else
|
||||
None
|
||||
}
|
||||
}.toLeft(readOnly(targetSet))
|
||||
}
|
||||
|
||||
|
||||
/** Copies the files declared in <code>sources</code> to the <code>targetDirectory</code>
|
||||
* directory. The source directory hierarchy is flattened so that all copies are immediate
|
||||
* children of <code>targetDirectory</code>. Directories are not recursively entered.*/
|
||||
|
|
@ -513,7 +546,7 @@ object FileUtilities
|
|||
}
|
||||
case Nil => None
|
||||
}
|
||||
|
||||
|
||||
Control.trap("Error copying files: ", log) { copyAll(uniquelyNamedSources.toList).toLeft(readOnly(targetSet)) }
|
||||
}
|
||||
/** Copies <code>sourceFile</code> to <code>targetFile</code>. If <code>targetFile</code>
|
||||
|
|
@ -540,7 +573,7 @@ object FileUtilities
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/** Synchronizes the contents of the <code>sourceDirectory</code> directory to the
|
||||
* <code>targetDirectory</code> directory.*/
|
||||
def sync(sourceDirectory: Path, targetDirectory: Path, log: Logger): Option[String] =
|
||||
|
|
@ -559,7 +592,7 @@ object FileUtilities
|
|||
toRemove.foreach(r => log.debug("Pruning " + r))
|
||||
clean(toRemove, true, log)
|
||||
}
|
||||
|
||||
|
||||
/** Copies the contents of the <code>source</code> directory to the <code>target</code> directory .*/
|
||||
def copyDirectory(source: Path, target: Path, log: Logger): Option[String] =
|
||||
copyDirectory(source.asFile, target.asFile, log)
|
||||
|
|
@ -586,7 +619,7 @@ object FileUtilities
|
|||
copyDirectory(source, target)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Deletes the given file recursively.*/
|
||||
def clean(file: Path, log: Logger): Option[String] = clean(file :: Nil, log)
|
||||
/** Deletes the given files recursively.*/
|
||||
|
|
@ -621,7 +654,7 @@ object FileUtilities
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Appends the given <code>String content</code> to the provided <code>file</code> using the default encoding.
|
||||
* A new file is created if it does not exist.*/
|
||||
def append(file: File, content: String, log: Logger): Option[String] = append(file, content, Charset.defaultCharset, log)
|
||||
|
|
@ -629,7 +662,7 @@ object FileUtilities
|
|||
* A new file is created if it does not exist.*/
|
||||
def append(file: File, content: String, charset: Charset, log: Logger): Option[String] =
|
||||
write(file, content, charset, true, log)
|
||||
|
||||
|
||||
/** Writes the given <code>String content</code> to the provided <code>file</code> using the default encoding.
|
||||
* If the file exists, it is overwritten.*/
|
||||
def write(file: File, content: String, log: Logger): Option[String] = write(file, content, Charset.defaultCharset, log)
|
||||
|
|
@ -644,7 +677,7 @@ object FileUtilities
|
|||
else
|
||||
Some("String cannot be encoded by charset " + charset.name)
|
||||
}
|
||||
|
||||
|
||||
/** Opens a <code>Writer</code> on the given file using the default encoding,
|
||||
* passes it to the provided function, and closes the <code>Writer</code>.*/
|
||||
def write(file: File, log: Logger)(f: Writer => Option[String]): Option[String] =
|
||||
|
|
@ -655,7 +688,7 @@ object FileUtilities
|
|||
write(file, charset, false, log)(f)
|
||||
private def write(file: File, charset: Charset, append: Boolean, log: Logger)(f: Writer => Option[String]): Option[String] =
|
||||
fileWriter(charset, append).ioOption(file, Writing, log)(f)
|
||||
|
||||
|
||||
/** Opens a <code>Reader</code> on the given file using the default encoding,
|
||||
* passes it to the provided function, and closes the <code>Reader</code>.*/
|
||||
def read(file: File, log: Logger)(f: Reader => Option[String]): Option[String] =
|
||||
|
|
@ -672,14 +705,14 @@ object FileUtilities
|
|||
* passes it to the provided function, and closes the <code>Reader</code>.*/
|
||||
def readValue[R](file: File, charset: Charset, log: Logger)(f: Reader => Either[String, R]): Either[String, R] =
|
||||
fileReader(charset).io(file, Reading, log)(f)
|
||||
|
||||
|
||||
/** Reads the contents of the given file into a <code>String</code> using the default encoding.
|
||||
* The resulting <code>String</code> is wrapped in <code>Right</code>.*/
|
||||
def readString(file: File, log: Logger): Either[String, String] = readString(file, Charset.defaultCharset, log)
|
||||
/** Reads the contents of the given file into a <code>String</code> using the given encoding.
|
||||
* The resulting <code>String</code> is wrapped in <code>Right</code>.*/
|
||||
def readString(file: File, charset: Charset, log: Logger): Either[String, String] = readValue(file, charset, log)(readString)
|
||||
|
||||
|
||||
def readString(in: InputStream, log: Logger): Either[String, String] = readString(in, Charset.defaultCharset, log)
|
||||
def readString(in: InputStream, charset: Charset, log: Logger): Either[String, String] =
|
||||
streamReader.io((in, charset), Reading, log)(readString)
|
||||
|
|
@ -713,7 +746,7 @@ object FileUtilities
|
|||
writeBytes(file, bytes, false, log)
|
||||
private def writeBytes(file: File, bytes: Array[Byte], append: Boolean, log: Logger): Option[String] =
|
||||
writeStream(file, append, log) { out => out.write(bytes); None }
|
||||
|
||||
|
||||
/** Reads the entire file into a byte array. */
|
||||
def readBytes(file: File, log: Logger): Either[String, Array[Byte]] = readStreamValue(file, log)(readBytes)
|
||||
def readBytes(in: InputStream, log: Logger): Either[String, Array[Byte]] =
|
||||
|
|
@ -736,7 +769,7 @@ object FileUtilities
|
|||
readNext()
|
||||
Right(out.toByteArray)
|
||||
}
|
||||
|
||||
|
||||
/** Opens an <code>OutputStream</code> on the given file with append=true and passes the stream
|
||||
* to the provided function. The stream is closed before this function returns.*/
|
||||
def appendStream(file: File, log: Logger)(f: OutputStream => Option[String]): Option[String] =
|
||||
|
|
@ -763,7 +796,7 @@ object FileUtilities
|
|||
* to the provided function. The stream is closed before this function returns.*/
|
||||
def readStreamValue[R](url: URL, log: Logger)(f: InputStream => Either[String, R]): Either[String, R] =
|
||||
urlInputStream.io(url, Reading, log)(f)
|
||||
|
||||
|
||||
/** Opens a <code>FileChannel</code> on the given file for writing and passes the channel
|
||||
* to the given function. The channel is closed before this function returns.*/
|
||||
def writeChannel(file: File, log: Logger)(f: FileChannel => Option[String]): Option[String] =
|
||||
|
|
@ -776,13 +809,13 @@ object FileUtilities
|
|||
* to the given function. The channel is closed before this function returns.*/
|
||||
def readChannelValue[R](file: File, log: Logger)(f: FileChannel => Either[String, R]): Either[String, R] =
|
||||
fileInputChannel.io(file, Reading, log)(f)
|
||||
|
||||
|
||||
private[sbt] def wrapNull(a: Array[File]): Array[File] =
|
||||
if(a == null)
|
||||
new Array[File](0)
|
||||
else
|
||||
a
|
||||
|
||||
|
||||
/** Writes the given string to the writer followed by a newline.*/
|
||||
private[sbt] def writeLine(writer: Writer, line: String)
|
||||
{
|
||||
|
|
@ -793,7 +826,7 @@ object FileUtilities
|
|||
def toFile(url: URL) =
|
||||
try { new File(url.toURI) }
|
||||
catch { case _: URISyntaxException => new File(url.getPath) }
|
||||
|
||||
|
||||
/** The directory in which temporary files are placed.*/
|
||||
val temporaryDirectory = new File(System.getProperty("java.io.tmpdir"))
|
||||
def classLocation(cl: Class[_]): URL =
|
||||
|
|
@ -805,11 +838,11 @@ object FileUtilities
|
|||
def classLocationFile(cl: Class[_]): File = toFile(classLocation(cl))
|
||||
def classLocation[T](implicit mf: scala.reflect.Manifest[T]): URL = classLocation(mf.erasure)
|
||||
def classLocationFile[T](implicit mf: scala.reflect.Manifest[T]): File = classLocationFile(mf.erasure)
|
||||
|
||||
|
||||
lazy val scalaLibraryJar: File = classLocationFile[scala.ScalaObject]
|
||||
lazy val scalaCompilerJar: File = classLocationFile[scala.tools.nsc.Settings]
|
||||
def scalaJars: Iterable[File] = List(scalaLibraryJar, scalaCompilerJar)
|
||||
|
||||
|
||||
/** The producer of randomness for unique name generation.*/
|
||||
private val random = new java.util.Random
|
||||
|
||||
|
|
@ -861,7 +894,7 @@ private object OpenResource
|
|||
{
|
||||
private def wrapEither[R](f: R => Option[String]): (R => Either[String, Unit]) = (r: R) => f(r).toLeft(())
|
||||
private def unwrapEither(e: Either[String, Unit]): Option[String] = e.left.toOption
|
||||
|
||||
|
||||
def fileOutputStream(append: Boolean) =
|
||||
new CloseableOpenFile[FileOutputStream] { protected def open(file: File) = new FileOutputStream(file, append) }
|
||||
def fileInputStream = new CloseableOpenFile[FileInputStream]
|
||||
|
|
|
|||
Loading…
Reference in New Issue