When it is inappropriate to include data as a part of source code into a Haskell program (e.g. data is binary, data is too large, etc.), Template Haskell can be used to safely embed them. The file-embed package is a simple and clean implementation of this idea.
The concept can be expanded into importing data and doing more processing at the compile-time before embedding it. The external data is accessible from a Q
action with runIO
and Data.ByteString.readFile
. However:
$ stack build
...
$ touch data/my3dmodel.bin
$ stack build
$ # nothing happened
Since the external file being imported is not part of the Haskell source code, making changes to it won’t trigger a re-build. This must be a huge trouble, because each time developers making changes to binary assets will have to somehow manually force the tool chain to build again. Is file-embed susceptible to this?
$ touch data/my3dmodel.bin
$ stack build
mypackage-0.1.0.0: unregistering (local file changes: data/my3dmodel.bin)
mypackage-0.1.0.0: build
...
No. Surprisingly, a file embedded with file-embed seems to have been marked as relevant and will trigger a re-build when changed. This is because the source code of file-embed includes:
embedFile :: FilePath -> Q Exp
embedFile fp =
#if MIN_VERSION_template_haskell(2,7,0)
qAddDependentFile fp >>
#endif
(runIO $ B.readFile fp) >>= bsToExp
The qAddDependentFile
from Language.Haskell.TH.Syntax
of the template-haskell package has the type signature of
qAddDependentFile :: FilePath -> m ()
and does the job of “marking as relevant.” As the code excerpt from file-embed suggests, the function has been added in 2.7 of the template-haskell package.
Make sure you call qAddDependentFile
when you runIO $ B.readFile path
in a Q
action.