![JAR search and dependency download from the Maven repository](/logo.png)
sbt.internal.Clean.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of main_2.12 Show documentation
Show all versions of main_2.12 Show documentation
sbt is an interactive build tool
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
package internal
import java.io.IOException
import java.nio.file.{ DirectoryNotEmptyException, Files, Path }
import sbt.Def._
import sbt.Keys._
import sbt.Project.richInitializeTask
import sbt.SlashSyntax0._
import sbt.io.syntax._
import sbt.nio.Keys._
import sbt.nio.file._
import sbt.nio.file.syntax._
import sbt.util.Level
import sjsonnew.JsonFormat
import scala.annotation.nowarn
private[sbt] object Clean {
private[sbt] def deleteContents(file: File, exclude: File => Boolean): Unit =
deleteContents(
file.toPath,
path => exclude(path.toFile),
FileTreeView.default,
tryDelete((_: String) => {})
)
private[this] def deleteContents(
path: Path,
exclude: Path => Boolean,
view: FileTreeView.Nio[FileAttributes],
delete: Path => Unit
): Unit = {
def deleteRecursive(path: Path): Unit = {
view
.list(Glob(path, AnyPath))
.filterNot { case (p, _) => exclude(p) }
.foreach {
case (dir, attrs) if attrs.isDirectory && !attrs.isSymbolicLink =>
deleteRecursive(dir)
delete(dir)
case (file, _) => delete(file)
}
}
deleteRecursive(path)
}
private[this] def cleanFilter(scope: Scope): Def.Initialize[Task[Path => Boolean]] = Def.task {
val excludes = (scope / cleanKeepFiles).value.map {
// This mimics the legacy behavior of cleanFilesTask
case f if f.isDirectory => Glob(f, AnyPath)
case f => f.toGlob
} ++ (scope / cleanKeepGlobs).value
p: Path => excludes.exists(_.matches(p))
}
private[this] def cleanDelete(scope: Scope): Def.Initialize[Task[Path => Unit]] = Def.task {
// Don't use a regular logger because the logger actually writes to the target directory.
val debug = (scope / logLevel).?.value.orElse(state.value.get(logLevel.key)) match {
case Some(Level.Debug) =>
(string: String) => println(s"[debug] $string")
case _ =>
(_: String) => {}
}
tryDelete(debug)
}
/**
* 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.
*/
private[sbt] def task(
scope: Scope,
full: Boolean
): Def.Initialize[Task[Unit]] =
Def.taskDyn {
val state = Keys.state.value
val extracted = Project.extract(state)
val view = (scope / fileTreeView).value
val manager = streamsManager.value
Def.task {
val excludeFilter = cleanFilter(scope).value
val delete = cleanDelete(scope).value
val targetDir = (scope / target).?.value.map(_.toPath)
targetDir.withFilter(_ => full).foreach(deleteContents(_, excludeFilter, view, delete))
(scope / cleanFiles).?.value.getOrElse(Nil).foreach { x =>
if (x.isDirectory) deleteContents(x.toPath, excludeFilter, view, delete)
else delete(x.toPath)
}
// This is the special portion of the task where we clear out the relevant streams
// and file outputs of a task.
val streamsKey = scope.task.toOption.map(k => ScopedKey(scope.copy(task = Zero), k))
val stampsKey =
extracted.structure.data.getDirect(scope, inputFileStamps.key) match {
case Some(_) => ScopedKey(scope, inputFileStamps.key) :: Nil
case _ => Nil
}
val streamsGlobs =
(streamsKey.toSeq ++ stampsKey).map(k => manager(k).cacheDirectory.toGlob / **)
((scope / fileOutputs).value.filter(g => targetDir.fold(true)(g.base.startsWith)) ++ streamsGlobs)
.foreach { g =>
val filter: Path => Boolean = { path =>
!g.matches(path) || excludeFilter(path)
}
deleteContents(g.base, filter, FileTreeView.default, delete)
delete(g.base)
}
}
} tag Tags.Clean
private[sbt] trait ToSeqPath[T] {
def apply(t: T): Seq[Path]
}
private[sbt] object ToSeqPath {
implicit val identitySeqPath: ToSeqPath[Seq[Path]] = identity _
implicit val seqFile: ToSeqPath[Seq[File]] = _.map(_.toPath)
implicit val path: ToSeqPath[Path] = _ :: Nil
implicit val file: ToSeqPath[File] = _.toPath :: Nil
}
private[this] implicit class ToSeqPathOps[T](val t: T) extends AnyVal {
def toSeqPath(implicit toSeqPath: ToSeqPath[T]): Seq[Path] = toSeqPath(t)
}
@nowarn
private[sbt] def cleanFileOutputTask[T: JsonFormat: ToSeqPath](
taskKey: TaskKey[T]
): Def.Initialize[Task[Unit]] =
Def.taskDyn {
val scope = taskKey.scope in taskKey.key
Def.task {
val targetDir = (scope / target).value.toPath
val filter = cleanFilter(scope).value
// We do not want to inadvertently delete files that are not in the target directory.
val excludeFilter: Path => Boolean = path => !path.startsWith(targetDir) || filter(path)
val delete = cleanDelete(scope).value
val st = (scope / streams).value
taskKey.previous.foreach(_.toSeqPath.foreach(p => if (!excludeFilter(p)) delete(p)))
delete(st.cacheDirectory.toPath / Previous.DependencyDirectory)
}
} tag Tags.Clean
private[this] def tryDelete(debug: String => Unit): Path => Unit = path => {
try {
debug(s"clean -- deleting file $path")
Files.deleteIfExists(path)
()
} catch {
case _: DirectoryNotEmptyException =>
debug(s"clean -- unable to delete non-empty directory $path")
case e: IOException =>
debug(s"Caught unexpected exception $e deleting $path")
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy