diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index d2f8954e2..d2506a32a 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -372,9 +372,9 @@ object Defaults extends BuildCommon { )) lazy val projectTasks: Seq[Setting[_]] = Seq( - cleanFiles := Seq(managedDirectory.value, target.value), - cleanKeepFiles := historyPath.value.toList, - clean := doClean(cleanFiles.value, cleanKeepFiles.value), + cleanFiles := cleanFilesTask.value, + cleanKeepFiles := historyPath.value.toVector, + clean := IO.delete(cleanFiles.value), consoleProject := consoleProjectTask.value, watchTransitiveSources := watchTransitiveSourcesTask.value, watch := watchSetting.value @@ -816,14 +816,21 @@ object Defaults extends BuildCommon { pickMainClass(classes) } - def doClean(clean: Seq[File], preserve: Seq[File]): Unit = - IO.withTemporaryDirectory { temp => - val (dirs, files) = preserve.filter(_.exists).flatMap(_.allPaths.get).partition(_.isDirectory) - val mappings = files.zipWithIndex map { case (f, i) => (f, new File(temp, i.toHexString)) } - IO.move(mappings) - IO.delete(clean) - IO.createDirectories(dirs) // recreate empty directories - IO.move(mappings.map(_.swap)) + /** Implements `cleanFiles` task. */ + def cleanFilesTask: Initialize[Task[Vector[File]]] = + Def.task { + val filesAndDirs = Vector(managedDirectory.value, target.value) + val preserve = cleanKeepFiles.value + val (dirs, fs) = filesAndDirs.filter(_.exists).partition(_.isDirectory) + val preserveSet = preserve.filter(_.exists).toSet + // performance reasons, only the direct items under `filesAndDirs` are allowed to be preserved. + val dirItems = dirs flatMap { _.glob("*").get } + (preserveSet diff dirItems.toSet) match { + case xs if xs.isEmpty => () + case xs => sys.error(s"cleanKeepFiles contains directory/file that are not directly under cleanFiles: $xs") + } + val toClean = (dirItems filterNot { preserveSet(_) }) ++ fs + toClean } def bgRunMainTask(products: Initialize[Task[Classpath]], classpath: Initialize[Task[Classpath]], diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 975f576d3..235da2c74 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -162,8 +162,8 @@ object Keys { // Output paths val classDirectory = SettingKey[File]("class-directory", "Directory for compiled classes and copied resources.", AMinusSetting) - val cleanFiles = SettingKey[Seq[File]]("clean-files", "The files to recursively delete during a clean.", BSetting) - val cleanKeepFiles = SettingKey[Seq[File]]("clean-keep-files", "Files to keep during a clean.", CSetting) + val cleanFiles = TaskKey[Seq[File]]("clean-files", "The files to recursively delete during a clean.", BSetting) + val cleanKeepFiles = SettingKey[Seq[File]]("clean-keep-files", "Files or directories to keep during a clean. Must be direct children of target.", CSetting) val crossPaths = SettingKey[Boolean]("cross-paths", "If true, enables cross paths, which distinguish input and output directories for cross-building.", ASetting) val taskTemporaryDirectory = SettingKey[File]("task-temporary-directory", "Directory used for temporary files for tasks that is deleted after each task execution.", DSetting)