mirror of https://github.com/sbt/sbt.git
Support custom clean tasks
This rewroks the cleanTask so that it only removes a subset of the files in the target directory. To do this, I add a new task, outputs, that returns the glob representation of the possible output files for the task. It must be a task because some outputs will depend on streams. For each project, the default outputs are all of the files in baseDirectory / target. Long term, we could enhance the clean task to be automatically generated in any scope (as an input task). We could then add the option for the task scoped clean to delete all of the transitive outputs of the class. That is beyond the scope of this commit, however. I copied the scripted tests from #3678 and added an additional test to make sure that the manage source directory was explicitly cleaned.
This commit is contained in:
parent
c77a26e832
commit
172c8e9a0b
|
|
@ -405,6 +405,7 @@ object Defaults extends BuildCommon {
|
|||
managedSourceDirectories := Seq(sourceManaged.value),
|
||||
managedSources := generate(sourceGenerators).value,
|
||||
sourceGenerators :== Nil,
|
||||
sourceGenerators / outputs := Seq(managedDirectory.value ** AllPassFilter),
|
||||
sourceDirectories := Classpaths
|
||||
.concatSettings(unmanagedSourceDirectories, managedSourceDirectories)
|
||||
.value,
|
||||
|
|
@ -568,9 +569,14 @@ object Defaults extends BuildCommon {
|
|||
globalDefaults(enableBinaryCompileAnalysis := true)
|
||||
|
||||
lazy val configTasks: Seq[Setting[_]] = docTaskSettings(doc) ++ inTask(compile)(
|
||||
compileInputsSettings
|
||||
compileInputsSettings :+ (clean := cleanTaskIn(ThisScope).value)
|
||||
) ++ configGlobal ++ defaultCompileSettings ++ compileAnalysisSettings ++ Seq(
|
||||
outputs := Seq(
|
||||
compileAnalysisFileTask.value.toGlob,
|
||||
classDirectory.value ** "*.class"
|
||||
) ++ (sourceGenerators / outputs).value,
|
||||
compile := compileTask.value,
|
||||
clean := cleanTaskIn(ThisScope).value,
|
||||
manipulateBytecode := compileIncremental.value,
|
||||
compileIncremental := (compileIncrementalTask tag (Tags.Compile, Tags.CPU)).value,
|
||||
printWarnings := printWarningsTask.value,
|
||||
|
|
@ -581,7 +587,7 @@ object Defaults extends BuildCommon {
|
|||
val extra =
|
||||
if (crossPaths.value) s"_$binVersion"
|
||||
else ""
|
||||
s"inc_compile${extra}.zip"
|
||||
s"inc_compile$extra.zip"
|
||||
},
|
||||
compileIncSetup := compileIncSetupTask.value,
|
||||
console := consoleTask.value,
|
||||
|
|
@ -616,7 +622,7 @@ object Defaults extends BuildCommon {
|
|||
cleanFiles := cleanFilesTask.value,
|
||||
cleanKeepFiles := Vector.empty,
|
||||
cleanKeepGlobs := historyPath.value.map(_.toGlob).toSeq,
|
||||
clean := (cleanTask tag Tags.Clean).value,
|
||||
clean := cleanTaskIn(ThisScope).value,
|
||||
consoleProject := consoleProjectTask.value,
|
||||
watchTransitiveSources := watchTransitiveSourcesTask.value,
|
||||
watchProjectTransitiveSources := watchTransitiveSourcesTaskImpl(watchProjectSources).value,
|
||||
|
|
@ -657,6 +663,7 @@ object Defaults extends BuildCommon {
|
|||
},
|
||||
watchStartMessage := Watched.projectOnWatchMessage(thisProjectRef.value.project),
|
||||
watch := watchSetting.value,
|
||||
outputs += target.value ** AllPassFilter,
|
||||
)
|
||||
|
||||
def generate(generators: SettingKey[Seq[Task[Seq[File]]]]): Initialize[Task[Seq[File]]] =
|
||||
|
|
@ -1304,30 +1311,45 @@ object Defaults extends BuildCommon {
|
|||
}
|
||||
|
||||
/** Implements `cleanFiles` task. */
|
||||
def cleanFilesTask: Initialize[Task[Vector[File]]] = Def.task { Vector.empty[File] }
|
||||
private[this] def cleanTask: Initialize[Task[Unit]] = Def.task {
|
||||
val defaults = Seq(managedDirectory.value ** AllPassFilter, target.value ** AllPassFilter)
|
||||
val excludes = cleanKeepFiles.value.map {
|
||||
// This mimics the legacy behavior of cleanFilesTask
|
||||
case f if f.isDirectory => f * AllPassFilter
|
||||
case f => f.toGlob
|
||||
} ++ cleanKeepGlobs.value
|
||||
val excludeFilter: File => Boolean = excludes.toFileFilter.accept
|
||||
val globDeletions = defaults.unique.filterNot(excludeFilter)
|
||||
val toDelete = cleanFiles.value.filterNot(excludeFilter) match {
|
||||
case f @ Seq(_, _*) => (globDeletions ++ f).distinct
|
||||
case _ => globDeletions
|
||||
}
|
||||
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")
|
||||
private[sbt] def cleanFilesTask: Initialize[Task[Vector[File]]] = Def.task { Vector.empty[File] }
|
||||
|
||||
/**
|
||||
* Provides an implementation for the clean task. It delegates to [[cleanTaskIn]] using the
|
||||
* resolvedScoped key to set the scope.
|
||||
* @return the clean task definition.
|
||||
*/
|
||||
def cleanTask: Initialize[Task[Unit]] =
|
||||
Def.taskDyn(cleanTaskIn(resolvedScoped.value.scope)) tag Tags.Clean
|
||||
|
||||
/**
|
||||
* Implements the clean task in a given scope. It uses the outputs task value in the provided
|
||||
* scope to determine which files to delete.
|
||||
* @param scope the scope in which the clean task is implemented
|
||||
* @return the clean task definition.
|
||||
*/
|
||||
def cleanTaskIn(scope: Scope): Initialize[Task[Unit]] =
|
||||
Def.task {
|
||||
val excludes = cleanKeepFiles.value.map {
|
||||
// This mimics the legacy behavior of cleanFilesTask
|
||||
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 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")
|
||||
}
|
||||
}
|
||||
} tag Tags.Clean
|
||||
|
||||
def bgRunMainTask(
|
||||
products: Initialize[Task[Classpath]],
|
||||
|
|
@ -1636,6 +1658,8 @@ object Defaults extends BuildCommon {
|
|||
incCompiler.compile(i, s.log)
|
||||
} finally x.close() // workaround for #937
|
||||
}
|
||||
private def compileAnalysisFileTask: Def.Initialize[Task[File]] =
|
||||
Def.task(streams.value.cacheDirectory / compileAnalysisFilename.value)
|
||||
def compileIncSetupTask = Def.task {
|
||||
val lookup = new PerClasspathEntryLookup {
|
||||
private val cachedAnalysisMap = analysisMap(dependencyClasspath.value)
|
||||
|
|
@ -1650,7 +1674,7 @@ object Defaults extends BuildCommon {
|
|||
lookup,
|
||||
(skip in compile).value,
|
||||
// TODO - this is kind of a bad way to grab the cache directory for streams...
|
||||
streams.value.cacheDirectory / compileAnalysisFilename.value,
|
||||
compileAnalysisFileTask.value,
|
||||
compilerCache.value,
|
||||
incOptions.value,
|
||||
(compilerReporter in compile).value,
|
||||
|
|
@ -2049,6 +2073,7 @@ object Classpaths {
|
|||
transitiveClassifiers :== Seq(SourceClassifier, DocClassifier),
|
||||
sourceArtifactTypes :== Artifact.DefaultSourceTypes.toVector,
|
||||
docArtifactTypes :== Artifact.DefaultDocTypes.toVector,
|
||||
outputs :== Nil,
|
||||
sbtDependency := {
|
||||
val app = appConfiguration.value
|
||||
val id = app.provider.id
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ object Keys {
|
|||
val cleanKeepGlobs = settingKey[Seq[Glob]]("Globs to keep during a clean. Must be direct children of target.").withRank(CSetting)
|
||||
val crossPaths = settingKey[Boolean]("If true, enables cross paths, which distinguish input and output directories for cross-building.").withRank(ASetting)
|
||||
val taskTemporaryDirectory = settingKey[File]("Directory used for temporary files for tasks that is deleted after each task execution.").withRank(DSetting)
|
||||
val outputs = taskKey[Seq[Glob]]("Describes the output files of a task")
|
||||
|
||||
// Generators
|
||||
val sourceGenerators = settingKey[Seq[Task[Seq[File]]]]("List of tasks that generate sources.").withRank(CSetting)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
Compile / sourceGenerators += Def.task {
|
||||
val files = Seq(sourceManaged.value / "foo.txt", sourceManaged.value / "bar.txt")
|
||||
files.foreach(IO.touch(_))
|
||||
files
|
||||
}
|
||||
|
||||
cleanKeepGlobs += (sourceManaged.value / "bar.txt").toGlob
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
> compile
|
||||
$ exists target/scala-2.12/src_managed/foo.txt target/scala-2.12/src_managed/bar.txt
|
||||
|
||||
> clean
|
||||
$ absent target/scala-2.12/src_managed/foo.txt
|
||||
$ exists target/scala-2.12/src_managed/bar.txt
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
cleanKeepGlobs in Compile +=
|
||||
((classDirectory in Compile in compile).value / "X.class").toGlob
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
class A {
|
||||
val x: Int = 1
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
class X {
|
||||
val y: Int = 0
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
class B {
|
||||
val x: Int = 2
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
$ touch target/cant-touch-this
|
||||
|
||||
> Test/compile
|
||||
$ exists target/scala-2.12/classes/A.class
|
||||
$ exists target/scala-2.12/test-classes/B.class
|
||||
|
||||
> Test/clean
|
||||
$ exists target/cant-touch-this
|
||||
# it should clean only compile classes
|
||||
$ exists target/scala-2.12/classes/A.class
|
||||
$ exists target/scala-2.12/classes/X.class
|
||||
$ absent target/scala-2.12/test-classes/B.class
|
||||
|
||||
# compiling everything again, but now cleaning only compile classes
|
||||
> Test/compile
|
||||
> Compile/clean
|
||||
$ exists target/cant-touch-this
|
||||
# it should clean only compile classes
|
||||
$ absent target/scala-2.12/classes/A.class
|
||||
$ exists target/scala-2.12/test-classes/B.class
|
||||
# and X has to be kept, because of the cleanKeepFiles override
|
||||
$ exists target/scala-2.12/classes/X.class
|
||||
Loading…
Reference in New Issue