mirror of https://github.com/sbt/sbt.git
Fix todos in actions/compile-clean
This commit is contained in:
parent
e1cf43c6bd
commit
ab1aa6d001
|
|
@ -12,70 +12,54 @@ import java.io.File
|
|||
import scala.quoted.*
|
||||
import scala.reflect.ClassTag
|
||||
|
||||
import sbt.util.OptJsonWriter
|
||||
import sbt.util.{ NoJsonWriter, OptJsonWriter }
|
||||
import sbt.internal.util.{ AttributeKey, KeyTag }
|
||||
|
||||
private[sbt] object KeyMacro:
|
||||
def settingKeyImpl[A1: Type](
|
||||
description: Expr[String]
|
||||
)(using qctx: Quotes): Expr[SettingKey[A1]] =
|
||||
keyImpl2[A1, SettingKey[A1]]("settingKey") { (name, mf, ojw) =>
|
||||
val n = Expr(name)
|
||||
'{
|
||||
SettingKey[A1]($n, $description)($mf, $ojw)
|
||||
}
|
||||
}
|
||||
def settingKeyImpl[A1: Type](description: Expr[String])(using Quotes): Expr[SettingKey[A1]] =
|
||||
val name = definingValName(errorMsg("settingKey"))
|
||||
val tag = '{ KeyTag.Setting[A1](${ summonRuntimeClass[A1] }) }
|
||||
val ojw = Expr
|
||||
.summon[OptJsonWriter[A1]]
|
||||
.getOrElse(errorAndAbort(s"OptJsonWriter[A] not found for ${Type.show[A1]}"))
|
||||
'{ SettingKey(AttributeKey($name, $description, Int.MaxValue)(using $tag, $ojw)) }
|
||||
|
||||
def taskKeyImpl[A1: Type](description: Expr[String])(using qctx: Quotes): Expr[TaskKey[A1]] =
|
||||
keyImpl[A1, TaskKey[A1]]("taskKey") { (name, mf) =>
|
||||
val n = Expr(name)
|
||||
'{
|
||||
TaskKey[A1]($n, $description)($mf)
|
||||
}
|
||||
}
|
||||
def taskKeyImpl[A1: Type](description: Expr[String])(using Quotes): Expr[TaskKey[A1]] =
|
||||
val name = definingValName(errorMsg("taskKey"))
|
||||
val tag: Expr[KeyTag[Task[A1]]] = Type.of[A1] match
|
||||
case '[Seq[a]] =>
|
||||
'{ KeyTag.SeqTask(${ summonRuntimeClass[a] }) }
|
||||
case _ => '{ KeyTag.Task(${ summonRuntimeClass[A1] }) }
|
||||
'{ TaskKey(AttributeKey($name, $description, Int.MaxValue)(using $tag, NoJsonWriter())) }
|
||||
|
||||
def inputKeyImpl[A1: Type](description: Expr[String])(using qctx: Quotes): Expr[InputKey[A1]] =
|
||||
keyImpl[A1, InputKey[A1]]("inputKey") { (name, mf) =>
|
||||
val n = Expr(name)
|
||||
'{
|
||||
InputKey[A1]($n, $description)($mf)
|
||||
}
|
||||
}
|
||||
def inputKeyImpl[A1: Type](description: Expr[String])(using Quotes): Expr[InputKey[A1]] =
|
||||
val name = definingValName(errorMsg("inputTaskKey"))
|
||||
val tag: Expr[KeyTag[InputTask[A1]]] = '{ KeyTag.InputTask(${ summonRuntimeClass[A1] }) }
|
||||
'{ InputKey(AttributeKey($name, $description, Int.MaxValue)(using $tag, NoJsonWriter())) }
|
||||
|
||||
private def keyImpl[A1: Type, A2: Type](methodName: String)(
|
||||
f: (String, Expr[ClassTag[A1]]) => Expr[A2]
|
||||
)(using qctx: Quotes): Expr[A2] =
|
||||
val tpe = summon[Type[A1]]
|
||||
f(
|
||||
definingValName(errorMsg(methodName)),
|
||||
Expr.summon[ClassTag[A1]].getOrElse(sys.error("ClassTag[A] not found for $tpe"))
|
||||
)
|
||||
def projectImpl(using Quotes): Expr[Project] =
|
||||
val name = definingValName(errorMsg2)
|
||||
'{ Project($name, new File($name)) }
|
||||
|
||||
private def keyImpl2[A1: Type, A2: Type](methodName: String)(
|
||||
f: (String, Expr[ClassTag[A1]], Expr[OptJsonWriter[A1]]) => Expr[A2]
|
||||
)(using qctx: Quotes): Expr[A2] =
|
||||
val tpe = summon[Type[A1]]
|
||||
f(
|
||||
definingValName(errorMsg(methodName)),
|
||||
Expr.summon[ClassTag[A1]].getOrElse(sys.error("ClassTag[A] not found for $tpe")),
|
||||
Expr.summon[OptJsonWriter[A1]].getOrElse(sys.error("OptJsonWriter[A] not found for $tpe")),
|
||||
)
|
||||
private def summonRuntimeClass[A: Type](using Quotes): Expr[Class[?]] =
|
||||
val classTag = Expr
|
||||
.summon[ClassTag[A]]
|
||||
.getOrElse(errorAndAbort(s"ClassTag[${Type.show[A]}] not found"))
|
||||
'{ $classTag.runtimeClass }
|
||||
|
||||
def projectImpl(using qctx: Quotes): Expr[Project] =
|
||||
val name = Expr(definingValName(errorMsg2("project")))
|
||||
'{
|
||||
Project($name, new File($name))
|
||||
}
|
||||
private def errorAndAbort(msg: String)(using q: Quotes): Nothing =
|
||||
q.reflect.report.errorAndAbort(msg)
|
||||
|
||||
private def errorMsg(methodName: String): String =
|
||||
s"""$methodName must be directly assigned to a val, such as `val x = $methodName[Int]("description")`."""
|
||||
|
||||
private def errorMsg2(methodName: String): String =
|
||||
s"""$methodName must be directly assigned to a val, such as `val x = ($methodName in file("core"))`."""
|
||||
private def errorMsg2: String =
|
||||
"""project must be directly assigned to a val, such as `val x = project.in(file("core"))`."""
|
||||
|
||||
private def definingValName(errorMsg: String)(using qctx: Quotes): String =
|
||||
private def definingValName(errorMsg: String)(using qctx: Quotes): Expr[String] =
|
||||
val term = enclosingTerm
|
||||
if term.isValDef then term.name
|
||||
else sys.error(errorMsg)
|
||||
if term.isValDef then Expr(term.name)
|
||||
else errorAndAbort(errorMsg)
|
||||
|
||||
def enclosingTerm(using qctx: Quotes) =
|
||||
import qctx.reflect._
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ final case class ScopedKeyData[A](scoped: ScopedKey[A], value: Any) {
|
|||
def description: String =
|
||||
key.tag match
|
||||
case KeyTag.Task(typeArg) => s"Task: $typeArg"
|
||||
case KeyTag.SeqTask(typeArg) => s"Task: Seq[$typeArg]"
|
||||
case KeyTag.InputTask(typeArg) => s"Input task: $typeArg"
|
||||
case KeyTag.Setting(typeArg) => s"Setting: $typeArg = $value"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import sbt.nio.file.Glob.{ GlobOps }
|
|||
import sbt.util.Level
|
||||
import sjsonnew.JsonFormat
|
||||
import scala.annotation.nowarn
|
||||
import xsbti.{ PathBasedFile, VirtualFileRef }
|
||||
|
||||
private[sbt] object Clean {
|
||||
|
||||
|
|
@ -142,8 +143,13 @@ private[sbt] object Clean {
|
|||
private[sbt] object ToSeqPath:
|
||||
given identitySeqPath: ToSeqPath[Seq[Path]] = identity[Seq[Path]](_)
|
||||
given seqFile: ToSeqPath[Seq[File]] = _.map(_.toPath)
|
||||
given virtualFileRefSeq: ToSeqPath[Seq[VirtualFileRef]] =
|
||||
_.collect { case f: PathBasedFile => f.toPath }
|
||||
given path: ToSeqPath[Path] = _ :: Nil
|
||||
given file: ToSeqPath[File] = _.toPath :: Nil
|
||||
given virtualFileRef: ToSeqPath[VirtualFileRef] =
|
||||
case f: PathBasedFile => Seq(f.toPath)
|
||||
case _ => Nil
|
||||
end ToSeqPath
|
||||
|
||||
private[this] implicit class ToSeqPathOps[T](val t: T) extends AnyVal {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import sbt.io.IO
|
|||
import sbt.nio.file.FileAttributes
|
||||
import sjsonnew.{ Builder, JsonFormat, Unbuilder, deserializationError }
|
||||
import xsbti.compile.analysis.{ Stamp => XStamp }
|
||||
import xsbti.VirtualFileRef
|
||||
|
||||
/**
|
||||
* A trait that indicates what file stamping implementation should be used to track the state of
|
||||
|
|
@ -102,65 +103,49 @@ object FileStamp {
|
|||
private[sbt] final case class Error(exception: IOException) extends FileStamp
|
||||
|
||||
object Formats {
|
||||
implicit val seqPathJsonFormatter: JsonFormat[Seq[Path]] = new JsonFormat[Seq[Path]] {
|
||||
override def write[J](obj: Seq[Path], builder: Builder[J]): Unit = {
|
||||
builder.beginArray()
|
||||
obj.foreach { path =>
|
||||
builder.writeString(path.toString)
|
||||
implicit val seqPathJsonFormatter: JsonFormat[Seq[Path]] =
|
||||
asStringArray(_.toString, Paths.get(_))
|
||||
implicit val seqFileJsonFormatter: JsonFormat[Seq[File]] =
|
||||
asStringArray(_.toString, new File(_))
|
||||
implicit val seqVirtualFileRefJsonFormatter: JsonFormat[Seq[VirtualFileRef]] =
|
||||
asStringArray(_.id, VirtualFileRef.of)
|
||||
|
||||
implicit val fileJsonFormatter: JsonFormat[File] = fromSeqJsonFormat[File]
|
||||
implicit val pathJsonFormatter: JsonFormat[Path] = fromSeqJsonFormat[Path]
|
||||
implicit val virtualFileRefJsonFormatter: JsonFormat[VirtualFileRef] =
|
||||
fromSeqJsonFormat[VirtualFileRef]
|
||||
|
||||
private def asStringArray[T](toStr: T => String, fromStr: String => T): JsonFormat[Seq[T]] =
|
||||
new JsonFormat[Seq[T]] {
|
||||
override def write[J](obj: Seq[T], builder: Builder[J]): Unit = {
|
||||
builder.beginArray()
|
||||
obj.foreach { x => builder.writeString(toStr(x)) }
|
||||
builder.endArray()
|
||||
}
|
||||
builder.endArray()
|
||||
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Seq[T] =
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
val size = unbuilder.beginArray(js)
|
||||
val res = (1 to size) map { _ =>
|
||||
fromStr(unbuilder.readString(unbuilder.nextElement))
|
||||
}
|
||||
unbuilder.endArray()
|
||||
res
|
||||
case None =>
|
||||
deserializationError("Expected JsArray but found None")
|
||||
}
|
||||
}
|
||||
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Seq[Path] =
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
val size = unbuilder.beginArray(js)
|
||||
val res = (1 to size) map { _ =>
|
||||
Paths.get(unbuilder.readString(unbuilder.nextElement))
|
||||
}
|
||||
unbuilder.endArray()
|
||||
res
|
||||
case None =>
|
||||
deserializationError("Expected JsArray but found None")
|
||||
}
|
||||
}
|
||||
private def fromSeqJsonFormat[T](using seqJsonFormat: JsonFormat[Seq[T]]): JsonFormat[T] =
|
||||
new JsonFormat[T] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): T =
|
||||
seqJsonFormat.read(jsOpt, unbuilder).head
|
||||
|
||||
implicit val seqFileJsonFormatter: JsonFormat[Seq[File]] = new JsonFormat[Seq[File]] {
|
||||
override def write[J](obj: Seq[File], builder: Builder[J]): Unit = {
|
||||
builder.beginArray()
|
||||
obj.foreach { file =>
|
||||
builder.writeString(file.toString)
|
||||
}
|
||||
builder.endArray()
|
||||
override def write[J](obj: T, builder: Builder[J]): Unit =
|
||||
seqJsonFormat.write(obj :: Nil, builder)
|
||||
}
|
||||
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Seq[File] =
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
val size = unbuilder.beginArray(js)
|
||||
val res = (1 to size) map { _ =>
|
||||
new File(unbuilder.readString(unbuilder.nextElement))
|
||||
}
|
||||
unbuilder.endArray()
|
||||
res
|
||||
case None =>
|
||||
deserializationError("Expected JsArray but found None")
|
||||
}
|
||||
}
|
||||
implicit val fileJsonFormatter: JsonFormat[File] = new JsonFormat[File] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): File =
|
||||
seqFileJsonFormatter.read(jsOpt, unbuilder).head
|
||||
|
||||
override def write[J](obj: File, builder: Builder[J]): Unit =
|
||||
seqFileJsonFormatter.write(obj :: Nil, builder)
|
||||
}
|
||||
implicit val pathJsonFormatter: JsonFormat[Path] = new JsonFormat[Path] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Path =
|
||||
seqPathJsonFormatter.read(jsOpt, unbuilder).head
|
||||
|
||||
override def write[J](obj: Path, builder: Builder[J]): Unit =
|
||||
seqPathJsonFormatter.write(obj :: Nil, builder)
|
||||
}
|
||||
implicit val seqPathFileStampJsonFormatter: JsonFormat[Seq[(Path, FileStamp)]] =
|
||||
new JsonFormat[Seq[(Path, FileStamp)]] {
|
||||
override def write[J](obj: Seq[(Path, FileStamp)], builder: Builder[J]): Unit = {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import sjsonnew.JsonFormat
|
|||
|
||||
import scala.annotation.nowarn
|
||||
import scala.collection.immutable.VectorBuilder
|
||||
import java.io.File
|
||||
import xsbti.VirtualFileRef
|
||||
|
||||
private[sbt] object Settings {
|
||||
private[sbt] def inject(transformed: Seq[Def.Setting[_]]): Seq[Def.Setting[_]] = {
|
||||
|
|
@ -68,45 +70,55 @@ private[sbt] object Settings {
|
|||
setting: Def.Setting[_],
|
||||
fileOutputScopes: Set[Scope]
|
||||
): List[Def.Setting[_]] = {
|
||||
val attributeKey = setting.key.key
|
||||
attributeKey.tag match {
|
||||
setting.key.key.tag match {
|
||||
case tag: KeyTag.Task[?] =>
|
||||
def default: List[Def.Setting[_]] = {
|
||||
val scope = setting.key.scope.copy(task = Select(attributeKey))
|
||||
if (fileOutputScopes.contains(scope)) {
|
||||
val sk = setting.asInstanceOf[Def.Setting[Task[Any]]].key
|
||||
val scopedKey = Keys.dynamicFileOutputs in (sk.scope in sk.key)
|
||||
addTaskDefinition {
|
||||
val init: Def.Initialize[Task[Seq[Path]]] = sk(_.map(_ => Nil))
|
||||
Def.setting[Task[Seq[Path]]](scopedKey, init, setting.pos)
|
||||
} :: allOutputPathsImpl(scope) :: outputFileStampsImpl(scope) :: cleanImpl(scope) :: Nil
|
||||
} else Nil
|
||||
}
|
||||
def mkSetting[T: JsonFormat: ToSeqPath]: List[Def.Setting[_]] = {
|
||||
val sk = setting.asInstanceOf[Def.Setting[Task[T]]].key
|
||||
val taskKey = TaskKey(sk.key) in sk.scope
|
||||
// We create a previous reference so that clean automatically works without the
|
||||
// user having to explicitly call previous anywhere.
|
||||
val init = Previous.runtime(taskKey).zip(taskKey) { case (_, t) =>
|
||||
t.map(implicitly[ToSeqPath[T]].apply)
|
||||
}
|
||||
val key = Def.ScopedKey(taskKey.scope in taskKey.key, Keys.dynamicFileOutputs.key)
|
||||
addTaskDefinition(Def.setting[Task[Seq[Path]]](key, init, setting.pos)) ::
|
||||
outputsAndStamps(taskKey)
|
||||
}
|
||||
if seqClass.isAssignableFrom(tag.typeArg) then
|
||||
// TODO fix this: maybe using the taskKey macro to convey the information
|
||||
// t.typeArguments match {
|
||||
// case p :: Nil if pathClass.isAssignableFrom(p.runtimeClass) => mkSetting[Seq[Path]]
|
||||
// case _ => default
|
||||
// }
|
||||
default
|
||||
else if pathClass.isAssignableFrom(tag.typeArg) then mkSetting[Path]
|
||||
else default
|
||||
if pathClass.isAssignableFrom(tag.typeArg) then addOutputAndStampTasks[Path](setting)
|
||||
else if fileClass.isAssignableFrom(tag.typeArg) then addOutputAndStampTasks[File](setting)
|
||||
else if virtualFileRefClass.isAssignableFrom(tag.typeArg) then
|
||||
addOutputAndStampTasks[VirtualFileRef](setting)
|
||||
else addDefaultTasks(setting, fileOutputScopes)
|
||||
case tag: KeyTag.SeqTask[?] =>
|
||||
if pathClass.isAssignableFrom(tag.typeArg) then addOutputAndStampTasks[Seq[Path]](setting)
|
||||
else if fileClass.isAssignableFrom(tag.typeArg) then
|
||||
addOutputAndStampTasks[Seq[File]](setting)
|
||||
else if virtualFileRefClass.isAssignableFrom(tag.typeArg) then
|
||||
addOutputAndStampTasks[Seq[VirtualFileRef]](setting)
|
||||
else addDefaultTasks(setting, fileOutputScopes)
|
||||
case _ => Nil
|
||||
}
|
||||
}
|
||||
|
||||
@nowarn
|
||||
private def addDefaultTasks(
|
||||
setting: Def.Setting[_],
|
||||
fileOutputScopes: Set[Scope]
|
||||
): List[Def.Setting[_]] = {
|
||||
val scope = setting.key.scope.copy(task = Select(setting.key.key))
|
||||
if (fileOutputScopes.contains(scope)) {
|
||||
val sk = setting.asInstanceOf[Def.Setting[Task[Any]]].key
|
||||
val scopedKey = Keys.dynamicFileOutputs in (sk.scope in sk.key)
|
||||
val init: Def.Initialize[Task[Seq[Path]]] = sk(_.map(_ => Nil))
|
||||
addTaskDefinition(Def.setting[Task[Seq[Path]]](scopedKey, init, setting.pos)) ::
|
||||
allOutputPathsImpl(scope) :: outputFileStampsImpl(scope) :: cleanImpl(scope) :: Nil
|
||||
} else Nil
|
||||
}
|
||||
|
||||
@nowarn
|
||||
private def addOutputAndStampTasks[T: JsonFormat: ToSeqPath](
|
||||
setting: Def.Setting[_]
|
||||
): List[Def.Setting[_]] = {
|
||||
val sk = setting.asInstanceOf[Def.Setting[Task[T]]].key
|
||||
val taskKey = TaskKey(sk.key) in sk.scope
|
||||
// We create a previous reference so that clean automatically works without the
|
||||
// user having to explicitly call previous anywhere.
|
||||
val init = Previous.runtime(taskKey).zip(taskKey) { case (_, t) =>
|
||||
t.map(implicitly[ToSeqPath[T]].apply)
|
||||
}
|
||||
val key = Def.ScopedKey(taskKey.scope in taskKey.key, Keys.dynamicFileOutputs.key)
|
||||
addTaskDefinition(Def.setting[Task[Seq[Path]]](key, init, setting.pos)) ::
|
||||
outputsAndStamps(taskKey)
|
||||
}
|
||||
|
||||
private[sbt] val inject: Def.ScopedKey[_] => Seq[Def.Setting[_]] = scopedKey =>
|
||||
scopedKey.key match {
|
||||
case transitiveDynamicInputs.key =>
|
||||
|
|
@ -161,7 +173,9 @@ private[sbt] object Settings {
|
|||
}
|
||||
|
||||
private[this] val seqClass = classOf[Seq[_]]
|
||||
private[this] val pathClass = classOf[java.nio.file.Path]
|
||||
private[this] val pathClass = classOf[Path]
|
||||
private val fileClass = classOf[File]
|
||||
private val virtualFileRefClass = classOf[VirtualFileRef]
|
||||
|
||||
/**
|
||||
* Returns all of the paths for the regular files described by a glob. Directories and hidden
|
||||
|
|
|
|||
|
|
@ -6,17 +6,14 @@ $ exists target/out/jvm/scala-2.12.17/compile-clean/test-classes/B.class
|
|||
|
||||
> Test/clean
|
||||
$ exists target/cant-touch-this
|
||||
# TODO it should clean only test classes
|
||||
# $ exists target/out/jvm/scala-2.12.17/compile-clean/classes/A.class
|
||||
# $ exists target/out/jvm/scala-2.12.17/compile-clean/classes/X.class
|
||||
$ exists target/out/jvm/scala-2.12.17/compile-clean/classes/A.class
|
||||
$ exists target/out/jvm/scala-2.12.17/compile-clean/classes/X.class
|
||||
$ absent target/out/jvm/scala-2.12.17/compile-clean/test-classes/B.class
|
||||
|
||||
# compiling everything again, but now cleaning only compile classes
|
||||
> Test/products
|
||||
> Compile/clean
|
||||
$ exists target/cant-touch-this
|
||||
# TODO it should clean only compile classes
|
||||
$ absent target/out/jvm/scala-2.12.17/compile-clean/classes/A.class
|
||||
# $ exists target/out/jvm/scala-2.12.17/compile-clean/test-classes/B.class
|
||||
# TODO and X has to be kept, because of the cleanKeepFiles override
|
||||
# $ exists target/out/jvm/scala-2.12.17/compile-clean/classes/X.class
|
||||
$ exists target/out/jvm/scala-2.12.17/compile-clean/test-classes/B.class
|
||||
$ exists target/out/jvm/scala-2.12.17/compile-clean/classes/X.class
|
||||
|
|
|
|||
|
|
@ -15,11 +15,13 @@ import sjsonnew.*
|
|||
enum KeyTag[A]:
|
||||
case Setting[A](typeArg: Class[?]) extends KeyTag[A]
|
||||
case Task[A](typeArg: Class[?]) extends KeyTag[A]
|
||||
case SeqTask[A](typeArg: Class[?]) extends KeyTag[A]
|
||||
case InputTask[A](typeArg: Class[?]) extends KeyTag[A]
|
||||
|
||||
override def toString: String = this match
|
||||
case Setting(typeArg) => typeArg.toString
|
||||
case Task(typeArg) => s"Task[$typeArg]"
|
||||
case SeqTask(typeArg) => s"Task[Seq[$typeArg]]"
|
||||
case InputTask(typeArg) => s"InputTask[$typeArg]"
|
||||
|
||||
def typeArg: Class[?]
|
||||
|
|
|
|||
Loading…
Reference in New Issue