mirror of https://github.com/sbt/sbt.git
Reimplement clean
I ran into a couple of issues with the clean implementation. I changed the logging to print to stdout instead of streams if enabled. I also added a helper, Clean.deleteContents that recursively deletes all of the contents of a directory except for those that match the exclude filter parameter. Using a normal logger was a bad idea because we are actually deleting the target/streams directory when running clean. The previous implementation worked by getting the full list of files to delete, reverse sorting it and then deleting every element in the list. While this can work well it many circumstances, if the directory is still being written to during the recursive deletion, then we could miss files that were added after we fetched all of the files. The new version lazily lists the subdirectories as needed.
This commit is contained in:
parent
16afe41cc1
commit
b0c5e00c7c
|
|
@ -256,9 +256,9 @@ object Defaults extends BuildCommon {
|
|||
skip :== false,
|
||||
taskTemporaryDirectory := { val dir = IO.createTemporaryDirectory; dir.deleteOnExit(); dir },
|
||||
onComplete := {
|
||||
val dir = taskTemporaryDirectory.value;
|
||||
val tempDirectory = taskTemporaryDirectory.value
|
||||
() =>
|
||||
{ IO.delete(dir); IO.createDirectory(dir) }
|
||||
Clean.deleteContents(tempDirectory, _ => false)
|
||||
},
|
||||
useSuperShell :== sbt.internal.TaskProgress.isEnabled,
|
||||
progressReports := { (s: State) =>
|
||||
|
|
|
|||
|
|
@ -14,12 +14,31 @@ import java.nio.file.{ DirectoryNotEmptyException, Files }
|
|||
import sbt.Def._
|
||||
import sbt.Keys._
|
||||
import sbt.Project.richInitializeTask
|
||||
import sbt.internal.GlobLister._
|
||||
import sbt.io.AllPassFilter
|
||||
import sbt.io.syntax._
|
||||
import sbt.io.{ AllPassFilter, FileTreeView, TypedPath }
|
||||
import sbt.util.Level
|
||||
|
||||
object Clean {
|
||||
|
||||
def deleteContents(file: File, exclude: TypedPath => Boolean): Unit =
|
||||
deleteContents(file, exclude, FileTreeView.DEFAULT, tryDelete((_: String) => {}))
|
||||
def deleteContents(
|
||||
file: File,
|
||||
exclude: TypedPath => Boolean,
|
||||
view: FileTreeView,
|
||||
delete: File => Unit
|
||||
): Unit = {
|
||||
def deleteRecursive(file: File): Unit = {
|
||||
view.list(file * AllPassFilter).filterNot(exclude).foreach {
|
||||
case dir if dir.isDirectory =>
|
||||
deleteRecursive(dir.toPath.toFile)
|
||||
delete(dir.toPath.toFile)
|
||||
case f => delete(f.toPath.toFile)
|
||||
}
|
||||
}
|
||||
deleteRecursive(file)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an implementation for the clean task. It delegates to [[taskIn]] using the
|
||||
* resolvedScoped key to set the scope.
|
||||
|
|
@ -41,22 +60,37 @@ object Clean {
|
|||
case f if f.isDirectory => f * AllPassFilter
|
||||
case f => f.toGlob
|
||||
} ++ cleanKeepGlobs.value
|
||||
val excludeFilter: File => Boolean = excludes.toFileFilter.accept
|
||||
val globDeletions = (outputs in scope).value.unique.filterNot(excludeFilter)
|
||||
val toDelete = cleanFiles.value.filterNot(excludeFilter) match {
|
||||
case f @ Seq(_, _*) => (globDeletions ++ f).distinct
|
||||
case _ => globDeletions
|
||||
val excludeFilter: TypedPath => Boolean = excludes.toTypedPathFilter
|
||||
val debug = (logLevel in scope).?.value.orElse(state.value.get(logLevel.key)) match {
|
||||
case Some(Level.Debug) =>
|
||||
(string: String) =>
|
||||
println(s"[debug] $string")
|
||||
case _ =>
|
||||
(_: String) =>
|
||||
{}
|
||||
}
|
||||
val logger = streams.value.log
|
||||
toDelete.sorted.reverseIterator.foreach { f =>
|
||||
logger.debug(s"clean -- deleting file $f")
|
||||
try Files.deleteIfExists(f.toPath)
|
||||
catch {
|
||||
case _: DirectoryNotEmptyException =>
|
||||
logger.debug(s"clean -- unable to delete non-empty directory $f")
|
||||
case e: IOException =>
|
||||
logger.debug(s"Caught unexpected exception $e deleting $f")
|
||||
val delete = tryDelete(debug)
|
||||
cleanFiles.value.sorted.reverseIterator.foreach(delete)
|
||||
(outputs in scope).value.foreach { g =>
|
||||
val filter: TypedPath => Boolean = {
|
||||
val globFilter = g.toTypedPathFilter
|
||||
tp =>
|
||||
!globFilter(tp) || excludeFilter(tp)
|
||||
}
|
||||
deleteContents(g.base.toFile, filter, FileTreeView.DEFAULT, delete)
|
||||
delete(g.base.toFile)
|
||||
}
|
||||
} tag Tags.Clean
|
||||
private def tryDelete(debug: String => Unit): File => Unit = file => {
|
||||
try {
|
||||
debug(s"clean -- deleting file $file")
|
||||
Files.deleteIfExists(file.toPath)
|
||||
()
|
||||
} catch {
|
||||
case _: DirectoryNotEmptyException =>
|
||||
debug(s"clean -- unable to delete non-empty directory $file")
|
||||
case e: IOException =>
|
||||
debug(s"Caught unexpected exception $e deleting $file")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue