Intro: HDF5 on hackage
I’ve been working on a little Haskell tool1 that needs to read images from HDF5 files, which are pervasive in some research areas.
As usual, searching hackage for “hdf5” gives a few answers:
hdf5-lite has its last commit in 2018 with the commit message “asdfasd” and the comment:
Experimental, partly tested and incomplete, not meant for production use.
as well as
The HDF5 library headers must be correctly installed at /usr/local/hdf5/include.
which were a few reason for me to rule that one out, since I’d definitely have to adapt the package.
bindings-hdf5 is marked broken in nixpkgs. I haven’t figured out yet why that is.
hdf5 looks ok, but is broken in nixpkgs (doesn’t compile) and depends on
hdf5-serial, which I don’t have. I’m in active contact with the maintainer, who has been nothing but helpful. But I wanted a solution now, so…
…writing my own bindings
With Haskell, it’s pretty easy to write bindings to a C library (which HDF5 is). I only need a few functions, so I wrote my own little wrapper, which was fun! In the end, I had a file Hdf5Raw.hsc (note the ending: .hsc is like .hs, but with C extensions) and simply told cabal to search for hdf5 via pkg-config:
executable simplon-stub
main-is: Main.hs
other-modules: Simplon.Hdf5Raw
hs-source-dirs: app
build-depends:
base >=4.7 && <5
pkgconfig-depends: hdf5
I’m using Nix with Flakes, and cabal2nix, which converts a .cabal project description into a Nix derivation.
My flake.nix looked a little bit like this:
{
description = "simplon-stub-hs";
inputs = {
nixpkgs.url = "nixpkgs/nixos-24.05";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem
(system:
let
pkgs = import nixpkgs {inherit system;};
packageName = "simplon-stub-hs";
in
{
packages.${packageName} =
# 1. here we call cabal2nix, resolving the dependencies
pkgs.haskellPackages.callCabal2nix packageName self {};
devShells.default =
pkgs.mkShell {
buildInputs = with pkgs; [
cabal-install
# 2. what we need to compile hdf5 bindings
pkg-config
];
# 3. this will "import" the dependencies found by cabal2nix into the
# current devshell
inputsFrom = [ self.packages.${system}.${packageName}.env ];
};
});
}Going through the source code:
- We call the function
callCabal2nix, which reads the.cabalfile and spits out a Nix derivation for it, taking care to include all necessary dependencies. - I included
pkg-configin my Nix shell build inputs, so cabal can find that incabal build. - The
inputsFromtakes the dependencies (the inputs) from the cabal2nix-generated derivation and uses them for the development shell.
However, this doesn’t work. In fact, with this flake.nix, nix develop will try to build haskellPackages.hdf5 (the one I described above, which is broken in nixpkgs). It took me a while and an issue on the cabal2nix GitHub repo to understand why.
Solution
My first solution to this strange problem was a hack: if cabal2nix is somehow inferring haskellPackages.hdf5 as a dependency, and that one is broken — why not just override (“redirect”) this package to a non-broken one? Like this:
packages.${packageName} =
haskellPackages.callCabal2nix packageName self {hdf5 = haskellPackages.bytestring;};bytestring is a package I’m using anyways, and it’s stable. This solution works, but feels weird, and it doesn’t help explaining what happened. To understand that, let’s call cabal2nix . (the command-line tool of the same name) manually and see the derivation it generates:
{ mkDerivation, aeson, base, bytestring, hdf5 }:
mkDerivation {
pname = "simplon-stub-hs";
version = "1.0.0";
src = ./.;
isLibrary = false;
isExecutable = true;
executableHaskellDepends = [ aeson base bytestring ];
executablePkgconfigDepends = [ hdf5 ];
mainProgram = "simplon-stub";
}And here’s the — very subtle — problem: the derivation has a set of packages as its argument:
{ mkDerivation, aeson, base, bytestring, hdf5 }: ...These derivations can be both packages from pkgs.haskellPackages (like bytestring) as well as packages from pkgs. But what about hdf5? This one is both. It’s in pkgs.hdf5 as well as pkgs.haskellPackage.hdf5. cabal2nix, as far as I understand it, cannot resolve this ambiguity, and gives precedence to haskellPackages.hdf5, which breaks the build.
The maintainer’s solution to that is similar to mine, but way less dubious:
haskellPackages.callCabal2nix packageName self {
inherit (pkgs) hdf5;
};So that’s it. It’s admittedly a corner case scenario, but if you encounter it now, maybe you’re not as surprised.
And of course, if I misunderstood, please write me!
GitHub link, but it’s not finished or has documentation yet; the post is not about the tool itself.↩︎