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,
Monad,
}
import xsbti.VirtualFile
import xsbti.{ VirtualFile, VirtualFileRef }
/**
* Implementation of a macro that provides a direct syntax for applicative functors and monads. It
@ -337,7 +337,7 @@ trait Cont:
}.asExprOf[HashWriter[A2]]
else summonHashWriter[A2]
val tagsExpr = '{ List(${ Varargs(tags.map(Expr[CacheLevelTag](_))) }: _*) }
val block = letOutput(outputs)(body)
val block = letOutput(outputs, cacheConfigExpr)(body)
'{
given HashWriter[A2] = $inputHashWriter
given JsonFormat[A1] = $aJsonFormat
@ -353,9 +353,28 @@ trait Cont:
})($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](
outputs: List[Output]
outputs: List[Output],
cacheConfigExpr: Expr[BuildWideCacheConfiguration],
)(body: Expr[A1]): Expr[ActionCache.InternalActionResult[A1]] =
Block(
outputs.map(_.toVarDef),
@ -363,29 +382,38 @@ trait Cont:
ActionCache.InternalActionResult(
value = $body,
outputs = List(${
Varargs[VirtualFile](outputs.map(_.toRef.asExprOf[VirtualFile]))
Varargs[VirtualFile](outputs.map(toVirtualFileExpr(cacheConfigExpr)))
}: _*),
)
}.asTerm
).asExprOf[ActionCache.InternalActionResult[A1]]
val WrapOutputName = "wrapOutput_\u2603\u2603"
val WrapOutputDirectoryName = "wrapOutputDirectory_\u2603\u2603"
// Called when transforming the tree to add an input.
// For `qual` of type F[A], and a `selection` qual.value.
val record = [a] =>
(name: String, tpe: Type[a], qual: Term, oldTree: Term) =>
given t: Type[a] = tpe
convert[a](name, qual) transform { (replacement: Term) =>
if name != WrapOutputName then
// todo cache opt-out attribute
inputBuf += Input(TypeRepr.of[a], qual, replacement, freshName("q"))
oldTree
else
val output = Output(TypeRepr.of[a], qual, freshName("o"), Symbol.spliceOwner)
outputBuf += output
if cacheConfigExprOpt.isDefined then output.toAssign
else oldTree
end if
name match
case WrapOutputName | WrapOutputDirectoryName =>
val output = Output(
tpe = TypeRepr.of[a],
term = qual,
name = freshName("o"),
parent = Symbol.spliceOwner,
outputType = name match
case WrapOutputName => OutputType.File
case WrapOutputDirectoryName => OutputType.Directory,
)
outputBuf += output
if cacheConfigExprOpt.isDefined then output.toAssign
else oldTree
case _ =>
// todo cache opt-out attribute
inputBuf += Input(TypeRepr.of[a], qual, replacement, freshName("q"))
oldTree
}
val exprWithConfig =
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 _ => extractTags0(tree)
enum OutputType:
case File
case Directory
/**
* Represents an output expression via Def.declareOutput
*/
@ -109,9 +113,10 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val valStart: Int):
val term: Term,
val name: String,
val parent: Symbol,
val outputType: OutputType,
):
override def toString: String =
s"Output($tpe, $term, $name)"
s"Output($tpe, $term, $name, $outputType)"
val placeholder: Symbol =
tpe.asType match
case '[a] =>
@ -126,6 +131,7 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val valStart: Int):
ValDef(placeholder, rhs = Some('{ null }.asTerm))
def toAssign: Term = Assign(toRef, term)
def toRef: Ref = Ref(placeholder)
def isFile: Boolean = outputType == OutputType.File
end Output
def applyTuple(tupleTerm: Term, tpe: TypeRepr, idx: Int): Term =

View File

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

View File

@ -74,12 +74,13 @@ class FullConvert[C <: Quotes & scala.Singleton](override val qctx: C, valStart:
override def convert[A: Type](nme: String, in: Term): Converted =
nme match
case InputWrapper.WrapInitTaskName => Converted.success(in)
case InputWrapper.WrapPreviousName => Converted.success(in)
case InputWrapper.WrapInitName => wrapInit[A](in)
case InputWrapper.WrapTaskName => wrapTask[A](in)
case InputWrapper.WrapOutputName => Converted.success(in)
case _ => Converted.NotApplicable()
case InputWrapper.WrapInitTaskName => Converted.success(in)
case InputWrapper.WrapPreviousName => Converted.success(in)
case InputWrapper.WrapInitName => wrapInit[A](in)
case InputWrapper.WrapTaskName => wrapTask[A](in)
case InputWrapper.WrapOutputName => Converted.success(in)
case InputWrapper.WrapOutputDirectoryName => Converted.success(in)
case _ => Converted.NotApplicable()
private def wrapInit[A: Type](tree: Term): Converted =
val expr = tree.asExprOf[Initialize[A]]

View File

@ -22,6 +22,7 @@ object InputWrapper:
private[std] final val WrapTaskName = "wrapTask_\u2603\u2603"
private[std] final val WrapInitName = "wrapInit_\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 WrapInitInputName = "wrapInitInputTask_\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
@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(
"`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
import java.io.File
import java.nio.file.Paths
import sbt.internal.util.{ ActionCacheEvent, CacheEventLog, StringVirtualFile1 }
import sbt.io.IO
import scala.reflect.ClassTag
@ -7,7 +9,7 @@ import scala.annotation.{ meta, StaticAnnotation }
import sjsonnew.{ HashWriter, JsonFormat }
import sjsonnew.support.murmurhash.Hasher
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.file.Path
import scala.quoted.{ Expr, FromExpr, ToExpr, Quotes }
@ -91,6 +93,16 @@ object ActionCache:
else valueFromStr(IO.read(paths.head.toFile()), result.origin)
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.
*/