Add extension methods for input and output files

It is tedious to write (foo / allInputFiles).value so I added simple
extension method macros that expand `foo.inputFiles` to
(foo / allInputFiles).value and `foo.outputFiles` to
`(foo / allOutputFiles).value`.
This commit is contained in:
Ethan Atkins 2019-07-24 19:20:03 -07:00
parent f126206231
commit 8e9efbeaac
9 changed files with 43 additions and 23 deletions

View File

@ -34,6 +34,14 @@ object FileChangesMacro {
"`changedOutputFiles` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task." "`changedOutputFiles` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task."
) )
def changedOutputFiles: Option[ChangedFiles] = macro changedOutputFilesImpl[T] def changedOutputFiles: Option[ChangedFiles] = macro changedOutputFilesImpl[T]
@compileTimeOnly(
"`inputFiles` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task."
)
def inputFiles: Seq[NioPath] = macro inputFilesImpl[T]
@compileTimeOnly(
"`outputFiles` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task."
)
def outputFiles: Seq[NioPath] = macro outputFilesImpl[T]
} }
def changedInputFilesImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[Option[ChangedFiles]] = { def changedInputFilesImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[Option[ChangedFiles]] = {
impl[T](c)(c.universe.reify(changedInputFiles), c.universe.reify(inputFileStamps)) impl[T](c)(c.universe.reify(changedInputFiles), c.universe.reify(inputFileStamps))
@ -50,16 +58,28 @@ object FileChangesMacro {
mapKey: c.Expr[TaskKey[Seq[(NioPath, FileStamp)]]] mapKey: c.Expr[TaskKey[Seq[(NioPath, FileStamp)]]]
): c.Expr[Option[ChangedFiles]] = { ): c.Expr[Option[ChangedFiles]] = {
import c.universe._ import c.universe._
val taskTpe = c.weakTypeOf[TaskKey[T]] val taskKey = getTaskKey(c)
lazy val err = "Couldn't expand file change macro."
val taskKey = c.Expr[TaskKey[T]](c.macroApplication match {
case Select(Apply(_, k :: Nil), _) if k.tpe <:< taskTpe => k
case _ => c.abort(c.enclosingPosition, err)
})
reify { reify {
val changes = (changeKey.splice in taskKey.splice).value val changes = (changeKey.splice in taskKey.splice).value
import sbt.nio.FileStamp.Formats._ import sbt.nio.FileStamp.Formats._
Previous.runtimeInEnclosingTask(mapKey.splice in taskKey.splice).value.flatMap(changes) Previous.runtimeInEnclosingTask(mapKey.splice in taskKey.splice).value.flatMap(changes)
} }
} }
def inputFilesImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[Seq[NioPath]] = {
val taskKey = getTaskKey(c)
c.universe.reify((allInputFiles in taskKey.splice).value)
}
def outputFilesImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[Seq[NioPath]] = {
val taskKey = getTaskKey(c)
c.universe.reify((allOutputFiles in taskKey.splice).value)
}
private def getTaskKey[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[TaskKey[T]] = {
import c.universe._
val taskTpe = c.weakTypeOf[TaskKey[T]]
lazy val err = "Couldn't expand file change macro."
c.Expr[TaskKey[T]](c.macroApplication match {
case Select(Apply(_, k :: Nil), _) if k.tpe <:< taskTpe => k
case _ => c.abort(c.enclosingPosition, err)
})
}
} }

View File

@ -15,7 +15,7 @@ copyFile := Def.task {
prev match { prev match {
case Some(v: Int) if changes.isEmpty => v case Some(v: Int) if changes.isEmpty => v
case _ => case _ =>
changes.getOrElse((copyFile / allInputFiles).value).foreach { p => changes.getOrElse(copyFile.inputFiles).foreach { p =>
val outDir = baseDirectory.value / "out" val outDir = baseDirectory.value / "out"
IO.createDirectory(outDir) IO.createDirectory(outDir)
IO.copyFile(p.toFile, outDir / p.getFileName.toString) IO.copyFile(p.toFile, outDir / p.getFileName.toString)

View File

@ -5,7 +5,7 @@ foo / fileInputs += baseDirectory.value.toGlob / "base" / "*.txt"
foo / target := baseDirectory.value / "out" foo / target := baseDirectory.value / "out"
foo := { foo := {
val out = baseDirectory.value / "out" val out = baseDirectory.value / "out"
((foo / allInputFiles).value: Seq[Path]).map { p => foo.inputFiles.map { p =>
val f = p.toFile val f = p.toFile
val target = out / f.getName val target = out / f.getName
IO.copyFile (f, target) IO.copyFile (f, target)

View File

@ -11,7 +11,7 @@ checkModified := {
val changes = foo.changedInputFiles val changes = foo.changedInputFiles
val modified = changes.map(_.updated).getOrElse(Nil) val modified = changes.map(_.updated).getOrElse(Nil)
println(modified) println(modified)
val allFiles = (foo / allInputFiles).value val allFiles = foo.inputFiles
if (modified.isEmpty) assert(true) if (modified.isEmpty) assert(true)
else { else {
assert(modified != allFiles) assert(modified != allFiles)
@ -21,7 +21,7 @@ checkModified := {
val checkRemoved = taskKey[Unit]("check that removed files are returned") val checkRemoved = taskKey[Unit]("check that removed files are returned")
checkRemoved := Def.taskDyn { checkRemoved := Def.taskDyn {
val files = (foo / allInputFiles).value val files = foo.inputFiles
val removed = foo.changedInputFiles.map(_.deleted).getOrElse(Nil) val removed = foo.changedInputFiles.map(_.deleted).getOrElse(Nil)
if (removed.isEmpty) Def.task(assert(true)) if (removed.isEmpty) Def.task(assert(true))
else Def.task { else Def.task {
@ -32,7 +32,7 @@ checkRemoved := Def.taskDyn {
val checkAdded = taskKey[Unit]("check that modified files are returned") val checkAdded = taskKey[Unit]("check that modified files are returned")
checkAdded := Def.taskDyn { checkAdded := Def.taskDyn {
val files = (foo / allInputFiles).value val files = foo.inputFiles
val added = foo.changedInputFiles.map(_.created).getOrElse(Nil) val added = foo.changedInputFiles.map(_.created).getOrElse(Nil)
if (added.isEmpty || (files.toSet == added.toSet)) Def.task(assert(true)) if (added.isEmpty || (files.toSet == added.toSet)) Def.task(assert(true))
else Def.task { else Def.task {

View File

@ -5,7 +5,7 @@ val foo = taskKey[Seq[File]]("Retrieve Foo.txt")
foo / fileInputs += baseDirectory.value.toGlob / ** / "*.txt" foo / fileInputs += baseDirectory.value.toGlob / ** / "*.txt"
foo := (foo / allInputFiles).value.map(_.toFile) foo := foo.inputFiles.map(_.toFile)
val checkFoo = taskKey[Unit]("Check that the Foo.txt file is retrieved") val checkFoo = taskKey[Unit]("Check that the Foo.txt file is retrieved")
@ -16,7 +16,7 @@ val bar = taskKey[Seq[File]]("Retrieve Bar.md")
bar / fileInputs += baseDirectory.value.toGlob / "base" / "subdir" / "nested-subdir" / "*.md" bar / fileInputs += baseDirectory.value.toGlob / "base" / "subdir" / "nested-subdir" / "*.md"
bar := (bar / allInputFiles).value.map(_.toFile) bar := bar.inputFiles.map(_.toFile)
val checkBar = taskKey[Unit]("Check that the Bar.md file is retrieved") val checkBar = taskKey[Unit]("Check that the Bar.md file is retrieved")
@ -32,7 +32,7 @@ val checkAll = taskKey[Unit]("Check that the Bar.md file is retrieved")
checkAll := { checkAll := {
import sbt.dsl.LinterLevel.Ignore import sbt.dsl.LinterLevel.Ignore
val expected = Set("Foo.txt", "Bar.md").map(baseDirectory.value / "base" / "subdir" / "nested-subdir" / _) val expected = Set("Foo.txt", "Bar.md").map(baseDirectory.value / "base" / "subdir" / "nested-subdir" / _)
val actual = (all / allInputFiles).value.map(_.toFile).toSet val actual = all.inputFiles.map(_.toFile).toSet
assert(actual == expected) assert(actual == expected)
} }
@ -55,6 +55,6 @@ depth / fileInputs ++= {
checkDepth := { checkDepth := {
val expected = Seq("Bar.md").map(baseDirectory.value / "base/subdir/nested-subdir" / _) val expected = Seq("Bar.md").map(baseDirectory.value / "base/subdir/nested-subdir" / _)
val actual = (depth / allInputFiles).value.map(_.toFile) val actual = depth.inputFiles.map(_.toFile)
assert(actual == expected) assert(actual == expected)
} }

View File

@ -16,7 +16,7 @@ compileLib := {
val name = path.getFileName.toString val name = path.getFileName.toString
objectDir.resolve(name.substring(0, name.lastIndexOf('.')) + ".o") objectDir.resolve(name.substring(0, name.lastIndexOf('.')) + ".o")
} }
val allFiles: Seq[Path] = (compileLib / allInputFiles).value val allFiles: Seq[Path] = compileLib.inputFiles
val changedFiles: Option[Seq[Path]] = compileLib.changedInputFiles match { val changedFiles: Option[Seq[Path]] = compileLib.changedInputFiles match {
case Some(ChangedFiles(c, d, u)) => case Some(ChangedFiles(c, d, u)) =>
d.foreach(p => Files.deleteIfExists(objectPath(p))) d.foreach(p => Files.deleteIfExists(objectPath(p)))
@ -52,7 +52,7 @@ linkLib / target := baseDirectory.value / "out" / "lib"
linkLib := { linkLib := {
val changedObjects = compileLib.changedOutputFiles val changedObjects = compileLib.changedOutputFiles
val outPath = (linkLib / target).value.toPath val outPath = (linkLib / target).value.toPath
val allObjects = (compileLib / allOutputFiles).value.map(_.toString) val allObjects = compileLib.outputFiles.map(_.toString)
val logger = streams.value.log val logger = streams.value.log
linkLib.previous match { linkLib.previous match {
case Some(p: Path) if changedObjects.isEmpty => case Some(p: Path) if changedObjects.isEmpty =>
@ -90,7 +90,7 @@ compileMain := {
logger.info(s"Not building $outPath: no dependencies have changed") logger.info(s"Not building $outPath: no dependencies have changed")
p p
case _ => case _ =>
(compileMain / allInputFiles).value match { compileMain.inputFiles match {
case Seq(main) => case Seq(main) =>
Files.createDirectories(outDir) Files.createDirectories(outDir)
logger.info(s"Building executable $outPath") logger.info(s"Building executable $outPath")
@ -112,7 +112,7 @@ compileMain := {
val executeMain = inputKey[Unit]("run the main method") val executeMain = inputKey[Unit]("run the main method")
executeMain := { executeMain := {
val args = Def.spaceDelimited("<arguments>").parsed val args = Def.spaceDelimited("<arguments>").parsed
val binary: Seq[Path] = (compileMain / allOutputFiles).value val binary: Seq[Path] = compileMain.outputFiles
val logger = streams.value.log val logger = streams.value.log
binary match { binary match {
case Seq(b) => case Seq(b) =>
@ -131,7 +131,7 @@ executeMain := {
val checkOutput = inputKey[Unit]("check the output value") val checkOutput = inputKey[Unit]("check the output value")
checkOutput := { checkOutput := {
val args @ Seq(arg, res) = Def.spaceDelimited("").parsed val args @ Seq(arg, res) = Def.spaceDelimited("").parsed
val binary: Path = (compileMain / allOutputFiles).value.head val binary: Path = compileMain.outputFiles.head
val output = RunBinary(binary, args, linkLib.value) val output = RunBinary(binary, args, linkLib.value)
assert(output.contains(s"f($arg) = $res")) assert(output.contains(s"f($arg) = $res"))
() ()

View File

@ -5,7 +5,7 @@ import scala.collection.JavaConverters._
val foo = taskKey[Unit]("foo") val foo = taskKey[Unit]("foo")
foo := { foo := {
val fooTxt = baseDirectory.value / "foo.txt" val fooTxt = baseDirectory.value / "foo.txt"
val _ = println(s"foo inputs: ${(foo / allInputFiles).value}") val _ = println(s"foo inputs: ${foo.inputFiles}")
IO.write(fooTxt, "foo") IO.write(fooTxt, "foo")
println(s"foo wrote to $foo") println(s"foo wrote to $foo")
} }

View File

@ -24,7 +24,7 @@ object Build {
lazy val root = (project in file(".")).settings( lazy val root = (project in file(".")).settings(
reloadFile := baseDirectory.value / "reload", reloadFile := baseDirectory.value / "reload",
foo / fileInputs += baseDirectory.value.toGlob / "foo.txt", foo / fileInputs += baseDirectory.value.toGlob / "foo.txt",
foo := (foo / allInputFiles).value, foo := foo.inputFiles,
setStringValue := Def.taskDyn { setStringValue := Def.taskDyn {
// This hides foo / fileInputs from the input graph // This hides foo / fileInputs from the input graph
Def.taskDyn { Def.taskDyn {

View File

@ -8,7 +8,7 @@ foo / watchForceTriggerOnAnyChange := true
foo / fileInputs := baseDirectory.value.toGlob / "files" / "foo.txt" :: Nil foo / fileInputs := baseDirectory.value.toGlob / "files" / "foo.txt" :: Nil
foo / watchTriggers := baseDirectory.value.toGlob / ** / "foo.txt" :: Nil foo / watchTriggers := baseDirectory.value.toGlob / ** / "foo.txt" :: Nil
foo := { foo := {
(foo / allInputFiles).value.foreach { p => foo.inputFiles.foreach { p =>
Files.setLastModifiedTime(p, FileTime.fromMillis(Files.getLastModifiedTime(p).toMillis + 3000)) Files.setLastModifiedTime(p, FileTime.fromMillis(Files.getLastModifiedTime(p).toMillis + 3000))
} }
sbt.nio.Stamps.check(foo).value sbt.nio.Stamps.check(foo).value