Fix todos in actions/compile-clean

This commit is contained in:
Adrien Piquerez 2024-04-08 16:27:35 +02:00
parent e1cf43c6bd
commit ab1aa6d001
7 changed files with 135 additions and 146 deletions

View File

@ -12,70 +12,54 @@ import java.io.File
import scala.quoted.* import scala.quoted.*
import scala.reflect.ClassTag import scala.reflect.ClassTag
import sbt.util.OptJsonWriter import sbt.util.{ NoJsonWriter, OptJsonWriter }
import sbt.internal.util.{ AttributeKey, KeyTag }
private[sbt] object KeyMacro: private[sbt] object KeyMacro:
def settingKeyImpl[A1: Type]( def settingKeyImpl[A1: Type](description: Expr[String])(using Quotes): Expr[SettingKey[A1]] =
description: Expr[String] val name = definingValName(errorMsg("settingKey"))
)(using qctx: Quotes): Expr[SettingKey[A1]] = val tag = '{ KeyTag.Setting[A1](${ summonRuntimeClass[A1] }) }
keyImpl2[A1, SettingKey[A1]]("settingKey") { (name, mf, ojw) => val ojw = Expr
val n = Expr(name) .summon[OptJsonWriter[A1]]
'{ .getOrElse(errorAndAbort(s"OptJsonWriter[A] not found for ${Type.show[A1]}"))
SettingKey[A1]($n, $description)($mf, $ojw) '{ SettingKey(AttributeKey($name, $description, Int.MaxValue)(using $tag, $ojw)) }
}
}
def taskKeyImpl[A1: Type](description: Expr[String])(using qctx: Quotes): Expr[TaskKey[A1]] = def taskKeyImpl[A1: Type](description: Expr[String])(using Quotes): Expr[TaskKey[A1]] =
keyImpl[A1, TaskKey[A1]]("taskKey") { (name, mf) => val name = definingValName(errorMsg("taskKey"))
val n = Expr(name) val tag: Expr[KeyTag[Task[A1]]] = Type.of[A1] match
'{ case '[Seq[a]] =>
TaskKey[A1]($n, $description)($mf) '{ 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]] = def inputKeyImpl[A1: Type](description: Expr[String])(using Quotes): Expr[InputKey[A1]] =
keyImpl[A1, InputKey[A1]]("inputKey") { (name, mf) => val name = definingValName(errorMsg("inputTaskKey"))
val n = Expr(name) val tag: Expr[KeyTag[InputTask[A1]]] = '{ KeyTag.InputTask(${ summonRuntimeClass[A1] }) }
'{ '{ InputKey(AttributeKey($name, $description, Int.MaxValue)(using $tag, NoJsonWriter())) }
InputKey[A1]($n, $description)($mf)
}
}
private def keyImpl[A1: Type, A2: Type](methodName: String)( def projectImpl(using Quotes): Expr[Project] =
f: (String, Expr[ClassTag[A1]]) => Expr[A2] val name = definingValName(errorMsg2)
)(using qctx: Quotes): Expr[A2] = '{ Project($name, new File($name)) }
val tpe = summon[Type[A1]]
f(
definingValName(errorMsg(methodName)),
Expr.summon[ClassTag[A1]].getOrElse(sys.error("ClassTag[A] not found for $tpe"))
)
private def keyImpl2[A1: Type, A2: Type](methodName: String)( private def summonRuntimeClass[A: Type](using Quotes): Expr[Class[?]] =
f: (String, Expr[ClassTag[A1]], Expr[OptJsonWriter[A1]]) => Expr[A2] val classTag = Expr
)(using qctx: Quotes): Expr[A2] = .summon[ClassTag[A]]
val tpe = summon[Type[A1]] .getOrElse(errorAndAbort(s"ClassTag[${Type.show[A]}] not found"))
f( '{ $classTag.runtimeClass }
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")),
)
def projectImpl(using qctx: Quotes): Expr[Project] = private def errorAndAbort(msg: String)(using q: Quotes): Nothing =
val name = Expr(definingValName(errorMsg2("project"))) q.reflect.report.errorAndAbort(msg)
'{
Project($name, new File($name))
}
private def errorMsg(methodName: String): String = private def errorMsg(methodName: String): String =
s"""$methodName must be directly assigned to a val, such as `val x = $methodName[Int]("description")`.""" s"""$methodName must be directly assigned to a val, such as `val x = $methodName[Int]("description")`."""
private def errorMsg2(methodName: String): String = private def errorMsg2: String =
s"""$methodName must be directly assigned to a val, such as `val x = ($methodName in file("core"))`.""" """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 val term = enclosingTerm
if term.isValDef then term.name if term.isValDef then Expr(term.name)
else sys.error(errorMsg) else errorAndAbort(errorMsg)
def enclosingTerm(using qctx: Quotes) = def enclosingTerm(using qctx: Quotes) =
import qctx.reflect._ import qctx.reflect._

View File

@ -21,6 +21,7 @@ final case class ScopedKeyData[A](scoped: ScopedKey[A], value: Any) {
def description: String = def description: String =
key.tag match key.tag match
case KeyTag.Task(typeArg) => s"Task: $typeArg" 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.InputTask(typeArg) => s"Input task: $typeArg"
case KeyTag.Setting(typeArg) => s"Setting: $typeArg = $value" case KeyTag.Setting(typeArg) => s"Setting: $typeArg = $value"
} }

View File

@ -24,6 +24,7 @@ import sbt.nio.file.Glob.{ GlobOps }
import sbt.util.Level import sbt.util.Level
import sjsonnew.JsonFormat import sjsonnew.JsonFormat
import scala.annotation.nowarn import scala.annotation.nowarn
import xsbti.{ PathBasedFile, VirtualFileRef }
private[sbt] object Clean { private[sbt] object Clean {
@ -142,8 +143,13 @@ private[sbt] object Clean {
private[sbt] object ToSeqPath: private[sbt] object ToSeqPath:
given identitySeqPath: ToSeqPath[Seq[Path]] = identity[Seq[Path]](_) given identitySeqPath: ToSeqPath[Seq[Path]] = identity[Seq[Path]](_)
given seqFile: ToSeqPath[Seq[File]] = _.map(_.toPath) given seqFile: ToSeqPath[Seq[File]] = _.map(_.toPath)
given virtualFileRefSeq: ToSeqPath[Seq[VirtualFileRef]] =
_.collect { case f: PathBasedFile => f.toPath }
given path: ToSeqPath[Path] = _ :: Nil given path: ToSeqPath[Path] = _ :: Nil
given file: ToSeqPath[File] = _.toPath :: Nil given file: ToSeqPath[File] = _.toPath :: Nil
given virtualFileRef: ToSeqPath[VirtualFileRef] =
case f: PathBasedFile => Seq(f.toPath)
case _ => Nil
end ToSeqPath end ToSeqPath
private[this] implicit class ToSeqPathOps[T](val t: T) extends AnyVal { private[this] implicit class ToSeqPathOps[T](val t: T) extends AnyVal {

View File

@ -16,6 +16,7 @@ import sbt.io.IO
import sbt.nio.file.FileAttributes import sbt.nio.file.FileAttributes
import sjsonnew.{ Builder, JsonFormat, Unbuilder, deserializationError } import sjsonnew.{ Builder, JsonFormat, Unbuilder, deserializationError }
import xsbti.compile.analysis.{ Stamp => XStamp } 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 * 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 private[sbt] final case class Error(exception: IOException) extends FileStamp
object Formats { object Formats {
implicit val seqPathJsonFormatter: JsonFormat[Seq[Path]] = new JsonFormat[Seq[Path]] { implicit val seqPathJsonFormatter: JsonFormat[Seq[Path]] =
override def write[J](obj: Seq[Path], builder: Builder[J]): Unit = { asStringArray(_.toString, Paths.get(_))
builder.beginArray() implicit val seqFileJsonFormatter: JsonFormat[Seq[File]] =
obj.foreach { path => asStringArray(_.toString, new File(_))
builder.writeString(path.toString) 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] = private def fromSeqJsonFormat[T](using seqJsonFormat: JsonFormat[Seq[T]]): JsonFormat[T] =
jsOpt match { new JsonFormat[T] {
case Some(js) => override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): T =
val size = unbuilder.beginArray(js) seqJsonFormat.read(jsOpt, unbuilder).head
val res = (1 to size) map { _ =>
Paths.get(unbuilder.readString(unbuilder.nextElement))
}
unbuilder.endArray()
res
case None =>
deserializationError("Expected JsArray but found None")
}
}
implicit val seqFileJsonFormatter: JsonFormat[Seq[File]] = new JsonFormat[Seq[File]] { override def write[J](obj: T, builder: Builder[J]): Unit =
override def write[J](obj: Seq[File], builder: Builder[J]): Unit = { seqJsonFormat.write(obj :: Nil, builder)
builder.beginArray()
obj.foreach { file =>
builder.writeString(file.toString)
}
builder.endArray()
} }
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)]] = implicit val seqPathFileStampJsonFormatter: JsonFormat[Seq[(Path, FileStamp)]] =
new JsonFormat[Seq[(Path, FileStamp)]] { new JsonFormat[Seq[(Path, FileStamp)]] {
override def write[J](obj: Seq[(Path, FileStamp)], builder: Builder[J]): Unit = { override def write[J](obj: Seq[(Path, FileStamp)], builder: Builder[J]): Unit = {

View File

@ -25,6 +25,8 @@ import sjsonnew.JsonFormat
import scala.annotation.nowarn import scala.annotation.nowarn
import scala.collection.immutable.VectorBuilder import scala.collection.immutable.VectorBuilder
import java.io.File
import xsbti.VirtualFileRef
private[sbt] object Settings { private[sbt] object Settings {
private[sbt] def inject(transformed: Seq[Def.Setting[_]]): Seq[Def.Setting[_]] = { private[sbt] def inject(transformed: Seq[Def.Setting[_]]): Seq[Def.Setting[_]] = {
@ -68,45 +70,55 @@ private[sbt] object Settings {
setting: Def.Setting[_], setting: Def.Setting[_],
fileOutputScopes: Set[Scope] fileOutputScopes: Set[Scope]
): List[Def.Setting[_]] = { ): List[Def.Setting[_]] = {
val attributeKey = setting.key.key setting.key.key.tag match {
attributeKey.tag match {
case tag: KeyTag.Task[?] => case tag: KeyTag.Task[?] =>
def default: List[Def.Setting[_]] = { if pathClass.isAssignableFrom(tag.typeArg) then addOutputAndStampTasks[Path](setting)
val scope = setting.key.scope.copy(task = Select(attributeKey)) else if fileClass.isAssignableFrom(tag.typeArg) then addOutputAndStampTasks[File](setting)
if (fileOutputScopes.contains(scope)) { else if virtualFileRefClass.isAssignableFrom(tag.typeArg) then
val sk = setting.asInstanceOf[Def.Setting[Task[Any]]].key addOutputAndStampTasks[VirtualFileRef](setting)
val scopedKey = Keys.dynamicFileOutputs in (sk.scope in sk.key) else addDefaultTasks(setting, fileOutputScopes)
addTaskDefinition { case tag: KeyTag.SeqTask[?] =>
val init: Def.Initialize[Task[Seq[Path]]] = sk(_.map(_ => Nil)) if pathClass.isAssignableFrom(tag.typeArg) then addOutputAndStampTasks[Seq[Path]](setting)
Def.setting[Task[Seq[Path]]](scopedKey, init, setting.pos) else if fileClass.isAssignableFrom(tag.typeArg) then
} :: allOutputPathsImpl(scope) :: outputFileStampsImpl(scope) :: cleanImpl(scope) :: Nil addOutputAndStampTasks[Seq[File]](setting)
} else Nil else if virtualFileRefClass.isAssignableFrom(tag.typeArg) then
} addOutputAndStampTasks[Seq[VirtualFileRef]](setting)
def mkSetting[T: JsonFormat: ToSeqPath]: List[Def.Setting[_]] = { else addDefaultTasks(setting, fileOutputScopes)
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
case _ => Nil 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 => private[sbt] val inject: Def.ScopedKey[_] => Seq[Def.Setting[_]] = scopedKey =>
scopedKey.key match { scopedKey.key match {
case transitiveDynamicInputs.key => case transitiveDynamicInputs.key =>
@ -161,7 +173,9 @@ private[sbt] object Settings {
} }
private[this] val seqClass = classOf[Seq[_]] 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 * Returns all of the paths for the regular files described by a glob. Directories and hidden

View File

@ -6,17 +6,14 @@ $ exists target/out/jvm/scala-2.12.17/compile-clean/test-classes/B.class
> Test/clean > Test/clean
$ exists target/cant-touch-this $ 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/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/X.class
$ absent target/out/jvm/scala-2.12.17/compile-clean/test-classes/B.class $ absent target/out/jvm/scala-2.12.17/compile-clean/test-classes/B.class
# compiling everything again, but now cleaning only compile classes # compiling everything again, but now cleaning only compile classes
> Test/products > Test/products
> Compile/clean > Compile/clean
$ exists target/cant-touch-this $ 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 $ 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 $ 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/classes/X.class

View File

@ -15,11 +15,13 @@ import sjsonnew.*
enum KeyTag[A]: enum KeyTag[A]:
case Setting[A](typeArg: Class[?]) extends KeyTag[A] case Setting[A](typeArg: Class[?]) extends KeyTag[A]
case Task[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] case InputTask[A](typeArg: Class[?]) extends KeyTag[A]
override def toString: String = this match override def toString: String = this match
case Setting(typeArg) => typeArg.toString case Setting(typeArg) => typeArg.toString
case Task(typeArg) => s"Task[$typeArg]" case Task(typeArg) => s"Task[$typeArg]"
case SeqTask(typeArg) => s"Task[Seq[$typeArg]]"
case InputTask(typeArg) => s"InputTask[$typeArg]" case InputTask(typeArg) => s"InputTask[$typeArg]"
def typeArg: Class[?] def typeArg: Class[?]