cabal2nixWithoutIFD icon indicating copy to clipboard operation
cabal2nixWithoutIFD copied to clipboard

cabal2nixWithoutIFD

This repo contains a proof-of-concept for a callCabal2nix function written in Nix. callCabal2nix uses Import From Derivation (IFD) and a Haskell program cabal2nix to take a Haskell .cabal file (like example-cabal-library.cabal) and translate it into a derivation that can be built with Nix.

One unfortunate part of callCabal2nix is that cabal2nix is written in Haskell, so IFD must be used to build the resulting derivation. IFD can often be slower than just doing things with native Nix, and it is not allowed in Nixpkgs.

This repo provides a callCabal2nixWithoutIFD function that is written in Nix, so it doesn't have either of the above downsides. Cabal files are parsed in Nix directly, so IFD is not needed.

Internally, this callCabal2nixWithoutIFD function is written in PureScript and transpiled to Nix using PureNix. Writing a parser for a complicated format like a Cabal file is much more reasonable in a language like PureScript than Nix itself.

While this repo is just a proof-of-concept, this approach of writing a complicated parser in PureNix could reasonably be extended to write a full parser for .cabal files. Writing a similar parser directly in raw Nix would be considerably more difficult.

Compiling PureScript Code to Nix

Compiling purescript-cabal-parser to Nix can be done with the following steps.

First, get into a Nix devShell:

$ nix develop

The, change to ./purescript-cabal-parser directory and run spago build:

$ cd ./purescript-cabal-parser/
$ spago build

This transpiles the PureScript code to Nix. The transpiled Nix code is placed in ./purescript-cabal-parser/output/.

Running Transpiled PureScript Code

The transpiled PureScript code can be tested by using the test.nix script.

Here's a derivation for a Haskell package that was built using callCabal2nixWithoutIFD:

$ nix-build ./test.nix -A exampleHaskellPackage
...
/nix/store/xb0yxkpzdz3zjqmcxwa9z6r6r98yfizz-example-cabal-library-0.1.0.0

Here's a derivation for a Haskell package that was built using the normal callCabal2nix:

$ nix-build ./test.nix -A exampleHaskellPackageWithIFD
...
/nix/store/xb0yxkpzdz3zjqmcxwa9z6r6r98yfizz-example-cabal-library-0.1.0.0

You can disable IFD in Nix and see that the Haskell derivation built with callCabal2nixWithoutIFD still builds successfully:

$ nix-build --option allow-import-from-derivation false ./test.nix -A exampleHaskellPackage
...
/nix/store/xb0yxkpzdz3zjqmcxwa9z6r6r98yfizz-example-cabal-library-0.1.0.0
$ nix-build --option allow-import-from-derivation false ./test.nix -A exampleHaskellPackageWithIFD
error: cannot build '/nix/store/q5hq65ynrqnnnjs3kl1ggsx2pkwlw702-cabal2nix-example-cabal-library.drv'
during evaluation because the option 'allow-import-from-derivation' is disabled

I recommend reading through the test.nix file to see what other derivations have been defined that you can try building. The file is heavily commented.

Playing around in the Nix REPL

You may be interested in taking a look at the PureScript code that defines the above derivations. Most of the interesting code is in purescript-cabal-parser/src/Main.purs.

You can load the transpiled PureScript code in a Nix REPL to play around with it:

$ cd ./purescript-cabal-parser/
$ nix repl ./purescript-cabal-parser/output/Main

This puts you in a Nix REPL with all the functions and datatypes from the Main module available.

For instance, Main defines a function called cabalParser:

cabalParser :: String -> Either (Array String) CabalFile

This takes the raw text of a Cabal file and parses it into a CabalFile datatype:

type CabalFile =
  { name :: String
  , version :: String
  , license :: License
  , executable :: Executable
  }

data License = LicenseBSD3

type Executable =
  { name :: String
  , buildDepends :: Array String
  }

Let's try calling cabalParser from the Nix REPL:

nix-repl> rawCabalFile = builtins.readFile ./
nix-repl> :p cabalParser rawCabalFile
{ __field0 = { executable = { buildDepends = [ "base" "aeson" ]; name = "example-cabal-library"; }; license = { __tag = "LicenseBSD3"; }; name = "example-cabal-library"; version = "0.1.0.0"; }; __tag = "Right"; }

If you squint, you can roughly see this attrset corresponds to the CabalFile datatype.

There are lots of comments in the Main.purs file, so you may want to skim through it to see what else is available.

Caveats

The cabal parser implemented in ./purescript-cabal-parser/ is completely a proof-of-concept. It only parses the absolute simplest of .cabal files. I wouldn't recommend using it any arbitrary Haskell package.

However, I hope the PureScript code in this repository shows the potential of PureNix for writing things that would be difficult in raw Nix.