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),
|
managedSourceDirectories := Seq(sourceManaged.value),
|
||||||
managedSources := generate(sourceGenerators).value,
|
managedSources := generate(sourceGenerators).value,
|
||||||
sourceGenerators :== Nil,
|
sourceGenerators :== Nil,
|
||||||
|
sourceGenerators / outputs := Seq(managedDirectory.value ** AllPassFilter),
|
||||||
sourceDirectories := Classpaths
|
sourceDirectories := Classpaths
|
||||||
.concatSettings(unmanagedSourceDirectories, managedSourceDirectories)
|
.concatSettings(unmanagedSourceDirectories, managedSourceDirectories)
|
||||||
.value,
|
.value,
|
||||||
|
|
@ -568,9 +569,14 @@ object Defaults extends BuildCommon {
|
||||||
globalDefaults(enableBinaryCompileAnalysis := true)
|
globalDefaults(enableBinaryCompileAnalysis := true)
|
||||||
|
|
||||||
lazy val configTasks: Seq[Setting[_]] = docTaskSettings(doc) ++ inTask(compile)(
|
lazy val configTasks: Seq[Setting[_]] = docTaskSettings(doc) ++ inTask(compile)(
|
||||||
compileInputsSettings
|
compileInputsSettings :+ (clean := cleanTaskIn(ThisScope).value)
|
||||||
) ++ configGlobal ++ defaultCompileSettings ++ compileAnalysisSettings ++ Seq(
|
) ++ configGlobal ++ defaultCompileSettings ++ compileAnalysisSettings ++ Seq(
|
||||||
|
outputs := Seq(
|
||||||
|
compileAnalysisFileTask.value.toGlob,
|
||||||
|
classDirectory.value ** "*.class"
|
||||||
|
) ++ (sourceGenerators / outputs).value,
|
||||||
compile := compileTask.value,
|
compile := compileTask.value,
|
||||||
|
clean := cleanTaskIn(ThisScope).value,
|
||||||
manipulateBytecode := compileIncremental.value,
|
manipulateBytecode := compileIncremental.value,
|
||||||
compileIncremental := (compileIncrementalTask tag (Tags.Compile, Tags.CPU)).value,
|
compileIncremental := (compileIncrementalTask tag (Tags.Compile, Tags.CPU)).value,
|
||||||
printWarnings := printWarningsTask.value,
|
printWarnings := printWarningsTask.value,
|
||||||
|
|
@ -581,7 +587,7 @@ object Defaults extends BuildCommon {
|
||||||
val extra =
|
val extra =
|
||||||
if (crossPaths.value) s"_$binVersion"
|
if (crossPaths.value) s"_$binVersion"
|
||||||
else ""
|
else ""
|
||||||
s"inc_compile${extra}.zip"
|
s"inc_compile$extra.zip"
|
||||||
},
|
},
|
||||||
compileIncSetup := compileIncSetupTask.value,
|
compileIncSetup := compileIncSetupTask.value,
|
||||||
console := consoleTask.value,
|
console := consoleTask.value,
|
||||||
|
|
@ -616,7 +622,7 @@ object Defaults extends BuildCommon {
|
||||||
cleanFiles := cleanFilesTask.value,
|
cleanFiles := cleanFilesTask.value,
|
||||||
cleanKeepFiles := Vector.empty,
|
cleanKeepFiles := Vector.empty,
|
||||||
cleanKeepGlobs := historyPath.value.map(_.toGlob).toSeq,
|
cleanKeepGlobs := historyPath.value.map(_.toGlob).toSeq,
|
||||||
clean := (cleanTask tag Tags.Clean).value,
|
clean := cleanTaskIn(ThisScope).value,
|
||||||
consoleProject := consoleProjectTask.value,
|
consoleProject := consoleProjectTask.value,
|
||||||
watchTransitiveSources := watchTransitiveSourcesTask.value,
|
watchTransitiveSources := watchTransitiveSourcesTask.value,
|
||||||
watchProjectTransitiveSources := watchTransitiveSourcesTaskImpl(watchProjectSources).value,
|
watchProjectTransitiveSources := watchTransitiveSourcesTaskImpl(watchProjectSources).value,
|
||||||
|
|
@ -657,6 +663,7 @@ object Defaults extends BuildCommon {
|
||||||
},
|
},
|
||||||
watchStartMessage := Watched.projectOnWatchMessage(thisProjectRef.value.project),
|
watchStartMessage := Watched.projectOnWatchMessage(thisProjectRef.value.project),
|
||||||
watch := watchSetting.value,
|
watch := watchSetting.value,
|
||||||
|
outputs += target.value ** AllPassFilter,
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate(generators: SettingKey[Seq[Task[Seq[File]]]]): Initialize[Task[Seq[File]]] =
|
def generate(generators: SettingKey[Seq[Task[Seq[File]]]]): Initialize[Task[Seq[File]]] =
|
||||||
|
|
@ -1304,30 +1311,45 @@ object Defaults extends BuildCommon {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Implements `cleanFiles` task. */
|
/** Implements `cleanFiles` task. */
|
||||||
def cleanFilesTask: Initialize[Task[Vector[File]]] = Def.task { Vector.empty[File] }
|
private[sbt] 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 {
|
* Provides an implementation for the clean task. It delegates to [[cleanTaskIn]] using the
|
||||||
// This mimics the legacy behavior of cleanFilesTask
|
* resolvedScoped key to set the scope.
|
||||||
case f if f.isDirectory => f * AllPassFilter
|
* @return the clean task definition.
|
||||||
case f => f.toGlob
|
*/
|
||||||
} ++ cleanKeepGlobs.value
|
def cleanTask: Initialize[Task[Unit]] =
|
||||||
val excludeFilter: File => Boolean = excludes.toFileFilter.accept
|
Def.taskDyn(cleanTaskIn(resolvedScoped.value.scope)) tag Tags.Clean
|
||||||
val globDeletions = defaults.unique.filterNot(excludeFilter)
|
|
||||||
val toDelete = cleanFiles.value.filterNot(excludeFilter) match {
|
/**
|
||||||
case f @ Seq(_, _*) => (globDeletions ++ f).distinct
|
* Implements the clean task in a given scope. It uses the outputs task value in the provided
|
||||||
case _ => globDeletions
|
* scope to determine which files to delete.
|
||||||
}
|
* @param scope the scope in which the clean task is implemented
|
||||||
val logger = streams.value.log
|
* @return the clean task definition.
|
||||||
toDelete.sorted.reverseIterator.foreach { f =>
|
*/
|
||||||
logger.debug(s"clean -- deleting file $f")
|
def cleanTaskIn(scope: Scope): Initialize[Task[Unit]] =
|
||||||
try Files.deleteIfExists(f.toPath)
|
Def.task {
|
||||||
catch {
|
val excludes = cleanKeepFiles.value.map {
|
||||||
case _: DirectoryNotEmptyException =>
|
// This mimics the legacy behavior of cleanFilesTask
|
||||||
logger.debug(s"clean -- unable to delete non-empty directory $f")
|
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(
|
def bgRunMainTask(
|
||||||
products: Initialize[Task[Classpath]],
|
products: Initialize[Task[Classpath]],
|
||||||
|
|
@ -1636,6 +1658,8 @@ object Defaults extends BuildCommon {
|
||||||
incCompiler.compile(i, s.log)
|
incCompiler.compile(i, s.log)
|
||||||
} finally x.close() // workaround for #937
|
} finally x.close() // workaround for #937
|
||||||
}
|
}
|
||||||
|
private def compileAnalysisFileTask: Def.Initialize[Task[File]] =
|
||||||
|
Def.task(streams.value.cacheDirectory / compileAnalysisFilename.value)
|
||||||
def compileIncSetupTask = Def.task {
|
def compileIncSetupTask = Def.task {
|
||||||
val lookup = new PerClasspathEntryLookup {
|
val lookup = new PerClasspathEntryLookup {
|
||||||
private val cachedAnalysisMap = analysisMap(dependencyClasspath.value)
|
private val cachedAnalysisMap = analysisMap(dependencyClasspath.value)
|
||||||
|
|
@ -1650,7 +1674,7 @@ object Defaults extends BuildCommon {
|
||||||
lookup,
|
lookup,
|
||||||
(skip in compile).value,
|
(skip in compile).value,
|
||||||
// TODO - this is kind of a bad way to grab the cache directory for streams...
|
// TODO - this is kind of a bad way to grab the cache directory for streams...
|
||||||
streams.value.cacheDirectory / compileAnalysisFilename.value,
|
compileAnalysisFileTask.value,
|
||||||
compilerCache.value,
|
compilerCache.value,
|
||||||
incOptions.value,
|
incOptions.value,
|
||||||
(compilerReporter in compile).value,
|
(compilerReporter in compile).value,
|
||||||
|
|
@ -2049,6 +2073,7 @@ object Classpaths {
|
||||||
transitiveClassifiers :== Seq(SourceClassifier, DocClassifier),
|
transitiveClassifiers :== Seq(SourceClassifier, DocClassifier),
|
||||||
sourceArtifactTypes :== Artifact.DefaultSourceTypes.toVector,
|
sourceArtifactTypes :== Artifact.DefaultSourceTypes.toVector,
|
||||||
docArtifactTypes :== Artifact.DefaultDocTypes.toVector,
|
docArtifactTypes :== Artifact.DefaultDocTypes.toVector,
|
||||||
|
outputs :== Nil,
|
||||||
sbtDependency := {
|
sbtDependency := {
|
||||||
val app = appConfiguration.value
|
val app = appConfiguration.value
|
||||||
val id = app.provider.id
|
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 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 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 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
|
// Generators
|
||||||
val sourceGenerators = settingKey[Seq[Task[Seq[File]]]]("List of tasks that generate sources.").withRank(CSetting)
|
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