Add Def.declareOutputDirectory function

This commit is contained in:
Eugene Yokota 2024-08-10 17:16:46 -04:00
parent c8ddbaed0e
commit 987ab5f214
6 changed files with 79 additions and 25 deletions

View File

@ -15,7 +15,7 @@ import sbt.util.{
Digest, Digest,
Monad, Monad,
} }
import xsbti.VirtualFile import xsbti.{ VirtualFile, VirtualFileRef }
/** /**
* Implementation of a macro that provides a direct syntax for applicative functors and monads. It * Implementation of a macro that provides a direct syntax for applicative functors and monads. It
@ -337,7 +337,7 @@ trait Cont:
}.asExprOf[HashWriter[A2]] }.asExprOf[HashWriter[A2]]
else summonHashWriter[A2] else summonHashWriter[A2]
val tagsExpr = '{ List(${ Varargs(tags.map(Expr[CacheLevelTag](_))) }: _*) } val tagsExpr = '{ List(${ Varargs(tags.map(Expr[CacheLevelTag](_))) }: _*) }
val block = letOutput(outputs)(body) val block = letOutput(outputs, cacheConfigExpr)(body)
'{ '{
given HashWriter[A2] = $inputHashWriter given HashWriter[A2] = $inputHashWriter
given JsonFormat[A1] = $aJsonFormat given JsonFormat[A1] = $aJsonFormat
@ -353,9 +353,28 @@ trait Cont:
})($cacheConfigExpr) })($cacheConfigExpr)
} }
// wrap body in between output var declarations and var references def toVirtualFileExpr(
cacheConfigExpr: Expr[BuildWideCacheConfiguration]
)(out: Output): Expr[VirtualFile] =
if out.isFile then out.toRef.asExprOf[VirtualFile]
else
'{
ActionCache.packageDirectory(
dir = ${ out.toRef.asExprOf[VirtualFileRef] },
conv = $cacheConfigExpr.fileConverter,
)
}
// This will generate following code for Def.declareOutput(...):
// var $o1: VirtualFile = null
// ActionCache.ActionResult({
// body...
// $o1 = out // Def.declareOutput(out)
// result
// }, List($o1))
def letOutput[A1: Type]( def letOutput[A1: Type](
outputs: List[Output] outputs: List[Output],
cacheConfigExpr: Expr[BuildWideCacheConfiguration],
)(body: Expr[A1]): Expr[ActionCache.InternalActionResult[A1]] = )(body: Expr[A1]): Expr[ActionCache.InternalActionResult[A1]] =
Block( Block(
outputs.map(_.toVarDef), outputs.map(_.toVarDef),
@ -363,29 +382,38 @@ trait Cont:
ActionCache.InternalActionResult( ActionCache.InternalActionResult(
value = $body, value = $body,
outputs = List(${ outputs = List(${
Varargs[VirtualFile](outputs.map(_.toRef.asExprOf[VirtualFile])) Varargs[VirtualFile](outputs.map(toVirtualFileExpr(cacheConfigExpr)))
}: _*), }: _*),
) )
}.asTerm }.asTerm
).asExprOf[ActionCache.InternalActionResult[A1]] ).asExprOf[ActionCache.InternalActionResult[A1]]
val WrapOutputName = "wrapOutput_\u2603\u2603" val WrapOutputName = "wrapOutput_\u2603\u2603"
val WrapOutputDirectoryName = "wrapOutputDirectory_\u2603\u2603"
// Called when transforming the tree to add an input. // Called when transforming the tree to add an input.
// For `qual` of type F[A], and a `selection` qual.value. // For `qual` of type F[A], and a `selection` qual.value.
val record = [a] => val record = [a] =>
(name: String, tpe: Type[a], qual: Term, oldTree: Term) => (name: String, tpe: Type[a], qual: Term, oldTree: Term) =>
given t: Type[a] = tpe given t: Type[a] = tpe
convert[a](name, qual) transform { (replacement: Term) => convert[a](name, qual) transform { (replacement: Term) =>
if name != WrapOutputName then name match
// todo cache opt-out attribute case WrapOutputName | WrapOutputDirectoryName =>
inputBuf += Input(TypeRepr.of[a], qual, replacement, freshName("q")) val output = Output(
oldTree tpe = TypeRepr.of[a],
else term = qual,
val output = Output(TypeRepr.of[a], qual, freshName("o"), Symbol.spliceOwner) name = freshName("o"),
parent = Symbol.spliceOwner,
outputType = name match
case WrapOutputName => OutputType.File
case WrapOutputDirectoryName => OutputType.Directory,
)
outputBuf += output outputBuf += output
if cacheConfigExprOpt.isDefined then output.toAssign if cacheConfigExprOpt.isDefined then output.toAssign
else oldTree else oldTree
end if case _ =>
// todo cache opt-out attribute
inputBuf += Input(TypeRepr.of[a], qual, replacement, freshName("q"))
oldTree
} }
val exprWithConfig = val exprWithConfig =
cacheConfigExprOpt.map(config => '{ $config; $expr }).getOrElse(expr) cacheConfigExprOpt.map(config => '{ $config; $expr }).getOrElse(expr)

View File

@ -101,6 +101,10 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val valStart: Int):
case Apply(_, List(arg)) => extractTags(arg) case Apply(_, List(arg)) => extractTags(arg)
case _ => extractTags0(tree) case _ => extractTags0(tree)
enum OutputType:
case File
case Directory
/** /**
* Represents an output expression via Def.declareOutput * Represents an output expression via Def.declareOutput
*/ */
@ -109,9 +113,10 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val valStart: Int):
val term: Term, val term: Term,
val name: String, val name: String,
val parent: Symbol, val parent: Symbol,
val outputType: OutputType,
): ):
override def toString: String = override def toString: String =
s"Output($tpe, $term, $name)" s"Output($tpe, $term, $name, $outputType)"
val placeholder: Symbol = val placeholder: Symbol =
tpe.asType match tpe.asType match
case '[a] => case '[a] =>
@ -126,6 +131,7 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val valStart: Int):
ValDef(placeholder, rhs = Some('{ null }.asTerm)) ValDef(placeholder, rhs = Some('{ null }.asTerm))
def toAssign: Term = Assign(toRef, term) def toAssign: Term = Assign(toRef, term)
def toRef: Ref = Ref(placeholder) def toRef: Ref = Ref(placeholder)
def isFile: Boolean = outputType == OutputType.File
end Output end Output
def applyTuple(tupleTerm: Term, tpe: TypeRepr, idx: Int): Term = def applyTuple(tupleTerm: Term, tpe: TypeRepr, idx: Int): Term =

View File

@ -9,7 +9,6 @@
package sbt package sbt
import java.net.URI import java.net.URI
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.annotation.targetName import scala.annotation.targetName
import sbt.KeyRanks.{ DTask, Invisible } import sbt.KeyRanks.{ DTask, Invisible }
@ -20,7 +19,7 @@ import sbt.internal.util.{ Terminal => ITerminal, * }
import sbt.util.{ ActionCacheStore, AggregateActionCacheStore, BuildWideCacheConfiguration, cacheLevel , DiskActionCacheStore } import sbt.util.{ ActionCacheStore, AggregateActionCacheStore, BuildWideCacheConfiguration, cacheLevel , DiskActionCacheStore }
import Util._ import Util._
import sbt.util.Show import sbt.util.Show
import xsbti.{ HashedVirtualFileRef, VirtualFile } import xsbti.{ HashedVirtualFileRef, VirtualFile, VirtualFileRef }
import sjsonnew.JsonFormat import sjsonnew.JsonFormat
import scala.reflect.ClassTag import scala.reflect.ClassTag
@ -330,6 +329,8 @@ object Def extends Init[Scope] with TaskMacroExtra with InitializeImplicits:
inline def declareOutput(inline vf: VirtualFile): Unit = inline def declareOutput(inline vf: VirtualFile): Unit =
InputWrapper.`wrapOutput_\u2603\u2603`[VirtualFile](vf) InputWrapper.`wrapOutput_\u2603\u2603`[VirtualFile](vf)
inline def declareOutputDirectory(inline vf: VirtualFileRef): Unit =
InputWrapper.`wrapOutputDirectory_\u2603\u2603`[VirtualFileRef](vf)
// The following conversions enable the types Initialize[T], Initialize[Task[T]], and Task[T] to // The following conversions enable the types Initialize[T], Initialize[Task[T]], and Task[T] to
// be used in task and setting macros as inputs with an ultimate result of type T // be used in task and setting macros as inputs with an ultimate result of type T

View File

@ -79,6 +79,7 @@ class FullConvert[C <: Quotes & scala.Singleton](override val qctx: C, valStart:
case InputWrapper.WrapInitName => wrapInit[A](in) case InputWrapper.WrapInitName => wrapInit[A](in)
case InputWrapper.WrapTaskName => wrapTask[A](in) case InputWrapper.WrapTaskName => wrapTask[A](in)
case InputWrapper.WrapOutputName => Converted.success(in) case InputWrapper.WrapOutputName => Converted.success(in)
case InputWrapper.WrapOutputDirectoryName => Converted.success(in)
case _ => Converted.NotApplicable() case _ => Converted.NotApplicable()
private def wrapInit[A: Type](tree: Term): Converted = private def wrapInit[A: Type](tree: Term): Converted =

View File

@ -22,6 +22,7 @@ object InputWrapper:
private[std] final val WrapTaskName = "wrapTask_\u2603\u2603" private[std] final val WrapTaskName = "wrapTask_\u2603\u2603"
private[std] final val WrapInitName = "wrapInit_\u2603\u2603" private[std] final val WrapInitName = "wrapInit_\u2603\u2603"
private[std] final val WrapOutputName = "wrapOutput_\u2603\u2603" private[std] final val WrapOutputName = "wrapOutput_\u2603\u2603"
private[std] final val WrapOutputDirectoryName = "wrapOutputDirectory_\u2603\u2603"
private[std] final val WrapInitTaskName = "wrapInitTask_\u2603\u2603" private[std] final val WrapInitTaskName = "wrapInitTask_\u2603\u2603"
private[std] final val WrapInitInputName = "wrapInitInputTask_\u2603\u2603" private[std] final val WrapInitInputName = "wrapInitInputTask_\u2603\u2603"
private[std] final val WrapInputName = "wrapInputTask_\u2603\u2603" private[std] final val WrapInputName = "wrapInputTask_\u2603\u2603"
@ -42,6 +43,11 @@ object InputWrapper:
) )
def `wrapOutput_\u2603\u2603`[A](@deprecated("unused", "") in: Any): A = implDetailError def `wrapOutput_\u2603\u2603`[A](@deprecated("unused", "") in: Any): A = implDetailError
@compileTimeOnly(
"`declareOutputDirectory` can only be used within a task macro, such as Def.cachedTask."
)
def `wrapOutputDirectory_\u2603\u2603`[A](@deprecated("unused", "") in: Any): A = implDetailError
@compileTimeOnly( @compileTimeOnly(
"`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task." "`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task."
) )

View File

@ -1,5 +1,7 @@
package sbt.util package sbt.util
import java.io.File
import java.nio.file.Paths
import sbt.internal.util.{ ActionCacheEvent, CacheEventLog, StringVirtualFile1 } import sbt.internal.util.{ ActionCacheEvent, CacheEventLog, StringVirtualFile1 }
import sbt.io.IO import sbt.io.IO
import scala.reflect.ClassTag import scala.reflect.ClassTag
@ -7,7 +9,7 @@ import scala.annotation.{ meta, StaticAnnotation }
import sjsonnew.{ HashWriter, JsonFormat } import sjsonnew.{ HashWriter, JsonFormat }
import sjsonnew.support.murmurhash.Hasher import sjsonnew.support.murmurhash.Hasher
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter, Parser } import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter, Parser }
import xsbti.{ FileConverter, VirtualFile } import xsbti.{ FileConverter, VirtualFile, VirtualFileRef }
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.nio.file.Path import java.nio.file.Path
import scala.quoted.{ Expr, FromExpr, ToExpr, Quotes } import scala.quoted.{ Expr, FromExpr, ToExpr, Quotes }
@ -91,6 +93,16 @@ object ActionCache:
else valueFromStr(IO.read(paths.head.toFile()), result.origin) else valueFromStr(IO.read(paths.head.toFile()), result.origin)
case Left(_) => organicTask case Left(_) => organicTask
def packageDirectory(dir: VirtualFileRef, conv: FileConverter): VirtualFile =
import sbt.io.syntax.*
val dirPath = conv.toPath(dir)
val dirFile = dirPath.toFile()
val zipPath = Paths.get(dirPath.toString + ".dirzip")
val rebase: File => Seq[(File, String)] =
f => if f != dirFile then (f -> dirPath.relativize(f.toPath).toString) :: Nil else Nil
IO.zip(dirFile.allPaths.get().flatMap(rebase), zipPath.toFile(), None)
conv.toVirtualFile(zipPath)
/** /**
* Represents a value and output files, used internally by the macro. * Represents a value and output files, used internally by the macro.
*/ */