From 7acb8764f507b29930565cf963172c2207f5aaa9 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 12 Apr 2017 02:00:14 -0400 Subject: [PATCH 1/2] Reimplement clean and cleanFiles tasks cleanFiles is now a task that lists exactly what will be deleted recursively. --- main/src/main/scala/sbt/Defaults.scala | 29 ++++++++++++++++---------- main/src/main/scala/sbt/Keys.scala | 4 ++-- 2 files changed, 20 insertions(+), 13 deletions(-) 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) From acf6c1191e18ab9acedf2c7bbdb7b7c71044decb Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 12 Apr 2017 02:06:31 -0400 Subject: [PATCH 2/2] Make clean task run exclusively --- main/src/main/scala/sbt/Defaults.scala | 15 ++++++++++----- main/src/main/scala/sbt/Tags.scala | 3 ++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index d2506a32a..d262342ca 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -374,7 +374,7 @@ object Defaults extends BuildCommon { lazy val projectTasks: Seq[Setting[_]] = Seq( cleanFiles := cleanFilesTask.value, cleanKeepFiles := historyPath.value.toVector, - clean := IO.delete(cleanFiles.value), + clean := (Def.task { IO.delete(cleanFiles.value) } tag (Tags.Clean)).value, consoleProject := consoleProjectTask.value, watchTransitiveSources := watchTransitiveSourcesTask.value, watch := watchSetting.value @@ -692,10 +692,15 @@ object Defaults extends BuildCommon { Def.task { Tests.discover(loadedTestFrameworks.value.values.toList, compile.value, streams.value.log)._1 } - def defaultRestrictions: Initialize[Seq[Tags.Rule]] = parallelExecution { par => - val max = EvaluateTask.SystemProcessors - Tags.limitAll(if (par) max else 1) :: Tags.limit(Tags.ForkedTestGroup, 1) :: Nil - } + def defaultRestrictions: Initialize[Seq[Tags.Rule]] = + Def.setting { + val par = parallelExecution.value + val max = EvaluateTask.SystemProcessors + Tags.limitAll(if (par) max else 1) :: + Tags.limit(Tags.ForkedTestGroup, 1) :: + Tags.exclusiveGroup(Tags.Clean) :: + Nil + } lazy val packageBase: Seq[Setting[_]] = Seq( artifact := Artifact(moduleName.value) diff --git a/main/src/main/scala/sbt/Tags.scala b/main/src/main/scala/sbt/Tags.scala index 4ab6b5fe0..28ce79f66 100644 --- a/main/src/main/scala/sbt/Tags.scala +++ b/main/src/main/scala/sbt/Tags.scala @@ -13,6 +13,7 @@ object Tags { val Test = Tag("test") val Update = Tag("update") val Publish = Tag("publish") + val Clean = Tag("clean") val CPU = Tag("cpu") val Network = Tag("network") @@ -24,7 +25,7 @@ object Tags { * Describes a restriction on concurrently executing tasks. * A Rule is constructed using one of the Tags.limit* methods. */ - sealed trait Rule { // TODO: make this an abstract class for 0.14 + abstract class Rule { def apply(m: TagMap): Boolean def ||(r: Rule): Rule = new Or(this, r) def &&(r: Rule): Rule = new And(this, r)