In my SBT project, I have an input file src/main/greeting/Greeting.txt
with the following content:
Hello, world!
This is my build.sbt
that generates Scala source from the Greeting.txt
file:
sourceGenerators in Compile += Def.task{
println("GENERATING FILES")
val inputFile = file("src/main/greeting/Greeting.txt")
val generatedFile =
(sourceManaged in Compile).value / "scala" / "Main.scala"
val greeting = IO.read(inputFile).trim
IO.write(
generatedFile,
s"""object Main extends App { println("${greeting}") }"""
)
Seq(generatedFile)
}.taskValue
This build.sbt
works fine, except that it runs my tasks to generate the Scala sources every time I compile/run my project. I would like it to only run these tasks when the Greeting.txt
-file has changed. How can I achieve this?
MCVE
Bash-script that generates the project:
#!/bin/bash
mkdir sourceGeneratorsExample
cd sourceGeneratorsExample
mkdir -p src/main/scala
mkdir -p src/main/greeting
echo "Hello, world!" >> src/main/greeting/Greeting.txt
cat <<HEREDOC > build.sbt
sourceGenerators in Compile += Def.task{
println("GENERATING FILES")
val inputFile = file("src/main/greeting/Greeting.txt")
val generatedFile =
(sourceManaged in Compile).value / "scala" / "Main.scala"
val greeting = IO.read(inputFile).trim
IO.write(
generatedFile,
"object Main extends App { println(\"" + greeting + "\") }"
)
Seq(generatedFile)
}.taskValue
HEREDOC
Duplicates / Documentation
- This is an answer from 2012, a lot has changed since then.
- The current reference manual advises to use "
sbt.Tracked.{ inputChanged, outputChanged }
etc", but does not expand on that, and the Tracked
object is not mentioned anywhere else in the manual.
You can use FileFunction.cached
, which is a:
Generic change-detection helper used to help build / artifact generation / etc. steps detect whether or not they need to run.
It uses a cache folder, where SBT automatically keeps a record of the file changes. With FileFunction.cached
, your build.sbt
might look like this:
sourceGenerators in Compile += Def.task{
// * Create a cached function which generates the output files
// only if the input files have changed.
// * The first parameter is a file instance of the path to
// the cache folder
// * The second parameter is the function to process the input
// files and return the output files
val cachedFun = FileFunction.cached(
streams.value.cacheDirectory / "greeting"
) { (in: Set[File]) =>
println("GENERATING FILES")
val generatedFile =
(sourceManaged in Compile).value / "scala" / "Main.scala"
val greeting = IO.read(in.head).trim
IO.write(
generatedFile,
"object Main extends App { println(\"" + greeting + "\") }"
)
Set(generatedFile)
}
// get the input file
val inputFile = file("src/main/greeting/Greeting.txt")
// put the input file into a `Set` (as required by `cachedFun`),
// pass it to the `cachedFun`,
// convert the result to `Seq` (as required by `Def.task`)
cachedFun(Set(inputFile)).toSeq
}.taskValue
The first parameter for FileFunction.cached
is a directory that will be used to store cache information (e.g. hashes of the input files). Here, we passed streams.value.cacheDirectory / "greeting"
, which will create a cache subdirectory somewhere inside of the target
-directory. The advantage is that his directory will be automatically cleaned when the task clean
is run.
The first argument list of the cached
method takes two additional optional inStyle
and outStyle
arguments, which determine how changes are detected (e.g. by modification date, or by comparing hashes). Note that in older versions of SBT, these two arguments are mandatory, so that your cachedFun
would look somewhat like this:
val cachedFun = FileFunction.cached(
cacheBaseDirectory = streams.value.cacheDirectory / "greeting",
inStyle = FilesInfo.lastModified,
outStyle = FilesInfo.exists
)(cachedFunBodyImpl)
The second argument list of the FileFunction.cached
-method takes a function that maps a Set
of input files to the Set
of output files. It is invoked only if the input files have changed.
You can find more info for an older version of SBT here (SBT 0.13.5), which expands on cached
and file tracking styles. Quoting:
There are two additional arguments for the first parameter list that allow the file tracking style to be explicitly specified. By default, the input tracking style is FilesInfo.lastModified, based on a file's last modified time, and the output tracking style is FilesInfo.exists, based only on whether the file exists. The other available style is FilesInfo.hash, which tracks a file based on a hash of its contents.
The first code snippet has been tested using SBT 1.2.8. The second code snippet should also work with earlier 0.13.x versions.