Copy Your Project Resources at Configure Time

Posted in: fp, haskell.

This is mainly a note to myself, but I think it’s content it’s worthwhile to deserve a blog entry, and might save some headache to some other Haskeller as well.

The problem

While rewriting my freelance project Expiweb in Haskell, I stumbled upon the following problem: my newly-created project uses persistent , which relies on TH. In one of my modules I have a snippet like this:

Where getSchema is defined as such:

This boils down to have the generated code to search, at compile time, for a file called schema.txt inside a resources directory. The accurate reader may notice I’m using getDataDir , to leverage the full power of Cabal. The problem with this code is that we have a mismatch between the nature of our code:

The TH snippet generates and check code at compile time

getDataDir is meant to be used at run time

The proof is that the aforementioned function is generated inside a file called Paths_yourprojectname.hs , where yourprojectname is the name of your Cabal project. So, in my case, Cabal creates, at compile time, but during the build step, a module called Paths_expiweb.hs . So far so good, but now if we try to compile our project, we end up with an error message like this:

This, unsurprisingly, means that we need our schema.txt to be there at compile time.

A first attempt: Using data-files and/or extra-source-files

After googling a bit, the first thing I’ve tried to do was to use data-files to instruct Cabal about the fact I have files I want to be copied over when I install the package. The problem is that data-files works at run time! In other terms, our package will still be broken. This way is a dead end.

A second attempt: Using Paths_expiweb inside Setup.hs

The second idea I had was to exploit the generated module Paths_expiweb inside a Setup.hs file, writing a pre-build hook to copy all the files I need using the aforementioned getDataDir function. The code might look like this:

This code seems to work, until you try to install the project inside a prestine environment (e.g. your production server): the problem is a classical circular dependency one: Paths_expiweb is created at build time, but we need to use it in our Setup.hs before it was even created (the proof is that, well, we need to import it, and Setup.hs needs to typecheck and compile before we can do anything)! So what?

My final solution

The final solution is something similar, which I created shamelessly copying a script I’ve found in the Haskell Cafè. Disclaimer: This will work only if you are using hsenv, but it’s simple to adapt to other scenarios:

The trick is simple: We know that, under hsenv , cabal will install package resources under /cabal/share/packagename-packageversion , so all we need is:

Get the absolute path of our hsenv environment, looking up its variable in the environment

Get the current package version, to allow the script to work across different versions. This is achieved using the pkgVersion' function, which parses the project manifest.

That’s all. Further food of thoughts:

Make the function work with everything, not just hsenv

Remove the hardcoded name of the package, making it fully generic

Make the function to work against hsenv “named environments” (e.g. .hsenv_myenv )

References

This is a collection of useful posts which helped me during my woes:

http://neilmitchell.blogspot.it/2008/02/adding-data-files-using-cabal.html

http://comments.gmane.org/gmane.comp.lang.haskell.cafe/106000

Comments

Please enable JavaScript to view the comments powered by Disqus.

Loved this post? Stay update