Yes, Cabal custom scripts do have uses

This will be a quick one. I've been working on implementing replication in my Redis server. The first step for this, per the codecrafters challenge that I'm following, is to implement the INFO command, which I've done in this PR and may be the focus of a future blog post. The framework for the INFO command was pretty easy to implement (types ftw). What I found myself spending the most time on was figuring out where the information in the INFO response comes from.

For the challenge, the goal was to implement the Replication section with just the role field, but I decided to also take a stab at implementing the Server section. It was a bit of a tangent. It took a while to hunt down what each field of the section meant and how Redis acquired the field's value. GitHub Copilot was very helpful in navigating and tracing things around in the codebase. I'll spare you all the mundane details. Suffice it to say, I ended up translating the Redis code for the server section into this Haskell module.

I'm pretty proud of it overall, and through it I found a decent use for Cabal's custom script mechanism. I use the script to collect and generate static build/release information like the current git commit hash, current git dirty status, and server build ID for the redis_git_sha1, redis_git_dirty, and redis_build_id fields under the "Server" INFO section. Redis does something similar with its mkreleasehdr.sh script, which generates a header file with release info variables that it then uses to, among other things, seed those "Server" section fields I mentioned in its INFO command implementation.

I used Cabal's custom script mechanism to mimic this since I didn't want to have to bend over backwards to try to collect this information at runtime. The setup script creates a BuildInfo.hs module that is seeded with constants, which I think is more efficient than whatever runtime attempt at fetching the same data I'd have needed to concoct, which would have required shelling out to git or potentially generating a build ID on each INFO request. I also made it such that the BuildInfo.hs module is only regenerated when certain data points change, similar to what the Redis script does. The full setup script can be perused here.

Though Cabal custom scripts are deprecated, it was much easier for me to get set up with them than the newer recommended setup hooks API. There were also more examples on the former to draw inspiration from than the latter. I think this is because of how new the hooks API seems to be. I will add a TODO to migrate it over; who knows, I might even get to it someday. But since it is non-blocking, I plan to just move on for now.

An alternative approach would have been to use Template Haskell. Template Haskell could do compile-time generation too, but would push metaprogramming into the app code for a small amount of static metadata. It seemed a little overkill for this particular scenario.

In any case, that's all for this one folks. If you need to generate a module at compile time, Cabal custom scripts (or Hooks API) got your back. Till next time!