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 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:

That’s all. Further food of thoughts:

References

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


Loved this post? Stay update!

Comments