haskell-nix
haskell-nix copied to clipboard
From Stack to Nix and beyond
Hey, I'm still moving from Stack to Nix & cabal and this tutorial helped me a lot! But a few general points aren't clear , at least for me.
-
Do you suggest adding each dependency a separated file, for example like:
$ cabal2nix cabal://turtle-1.3.2 > turtle.nix
and add them to your project with thereadDir
trick ? -
Do you work inside the
nix-shell
? Right now I'm moving from Stack and nix pick quite old packages and I'm not able to jump into the shell because of compile errors. -
How do you browse documentation ?
-
How do you manage conflicts and dependencies between several packages.
Thank you very much for your tutorial :smile:
Yes, I recommend keeping each pinned dependency in a separate file, mainly because that's the easiest way to add/update/remove them using cabal2nix
You actually don't have to work inside a nix-shell
. One trick that I haven't documented yet is that you can add nix: True
to your ~/.cabal/config
and then any time you run cabal configure
all subsequent cabal
commands for that project will run automatically inside the nix-shell
For other Haskell projects, I personally browse documentation just by Googling "hackage <thing I'm searching for>" and then browsing the Hackage documentation. For my own project that I'm actively developing, cabal haddock
is what I use.
For conflicts between packages, I try the following things, in this order:
- Use
pkgs.haskell.lib.doJailbreak
if the conflict is due to a package having a too restrictive upper bound - Try
pkgs.haskell.lib.dontCheck
if the conflict is due to the test suite - Use a Stackage resolver to figure out which package versions work together
However, one thing I never do is use two versions of a given package in the dependency set
Thank you very much for answering my question. Could you elaborate or point topics to read more about
Use a Stackage resolver to figure out which package versions work together
The nix: True
approach is neat but I have the feeling I should prefer a solution which is in scm too.
Do you suggest dropping stack
? Right now I used it but now it looks useless if I can find a way to get package combinations like for example, which sha for mtl
and wuss
package
Ignoring Nix for a moment, one lesser known fact about stack
is that you can get the benefits of Stackage even when using cabal
. Every stack resolver is essentially just a giant cabal.config
file specifying the versions of all packages on Stackage. For example, you can find the cabal.config
file for the latest Stackage LTS resolver here:
https://www.stackage.org/lts/cabal.config
... and if you save that to a cabal.config
file in any Cabal-based project it will pin every dependency to the exact same version that stack
would have used for that resolver (assuming that you're not using Nix). That is essentially how stack
works under the hood.
Now, going back to Nix, that cabal.config
file won't be directly helpful in a project using Nix to supply Haskell packages because Nixpkgs won't choose the exact same packages that Stackage will (although they will be pretty similar). However, even if you can't use the cabal.config
file directly you can still refer to it when deciding what packages to use when resolving conflicts. For example, if optparse-applicative
is conflicting with turtle
, I can just consult the cabal.config
for my preferred Stackage resolver and use that to decide which one (or both) to pin to fix the conflict.
Also, I believe the nix: True
solution might be safe to turn on globally instead of configuring it per-project although I haven't tested this. If a project does not have a shell.nix
file then I think cabal
falls back to the old non-Nix behavior.
Wow, this makes so much sense now.
To summarise this, in general you would just specify the package name without version in the cabal file and use the output of cabal2nix cabal://turtle-1.3
for each dependency.
Version lookup is done over the stackage cabal file.
Did I understand this correct? We should definitely document the stuff you pointed out, it really helps new people.
@rsoeldner: Yes. Usually my rule of thumb for whether or not to put dependency bounds in the .cabal
file is:
- If it's a private project: omit the bounds
- i.e. let Stack or Nixpkgs+
cabal2nix
pick the package version
- i.e. let Stack or Nixpkgs+
- If it's a public project: add bounds
- this is for the benefit of people who don't use Stack or Nix
For example, I would classify hobby projects and proprietary internal projects at a company as private projects, so I omit bounds in those cases. That lets me iterate quickly when there is no public contract to worry about breaking.
Once I publish the project as an open source project and encourage others to depend on it then I'm careful to add bounds so that downstream users who use just Cabal to resolve dependencies don't run into build failures.
Also, I'll leave this ticket open to remind myself to document this like you requested
Thank you very much for the time! I would be happy to read more! :grin: :tada:
Hey @Gabriel439, I got it building but I'm not able to jump into a repl session.
release.nix:
{ compiler ? "ghc822" }:
let
config = {
allowUnfree = true;
packageOverrides = pkgs: rec {
haskell = pkgs.haskell // {
packages = pkgs.haskell.packages // {
"${compiler}" = pkgs.haskell.packages."${compiler}".override {
overrides = haskellPackagesNew: haskellPackagesOld:
let
toPackage = file: _: {
name = builtins.replaceStrings [ ".nix" ] [ "" ] file;
value = haskellPackagesNew.callPackage (./. + "/pkgs/${file}") { };
};
packages = pkgs.lib.mapAttrs' toPackage (builtins.readDir ./pkgs);
in
packages // {
testApp = haskellPackagesNew.callPackage ../default.nix { };
testApp-dep = haskellPackagesNew.callPackage ../testApp-dep/default.nix { };
aws = pkgs.haskell.lib.dontCheck (haskellPackagesNew.callPackage ./pkgs/aws.nix { });
};
};
};
};
};
};
pkgs = import <nixpkgs> { inherit config; };
in
{ testApp = pkgs.haskell.packages.${compiler}.testApp;
}
I created a shell.nix
and a default.nix
file using cabal2nix
. When using cabal repl
for the testApp cabal complaining It was run without the testApp-dep
dependency. And when I run a nix-shell --attr testApp
I have the same problem. Running stack
still works.
While mention here
We pass the --attr env flag to specify that nix-shell should compute the development environment from the derivation's env "attribute".
When running nix-shell --attr env nix/release.nix
I receive
error: attribute ‘env’ in selection path ‘env’ not found
Maybe this is a nix-shell
version problem ? I'm running 1.11.16
Thank you in advance, you should create a patreon account :+1:
What does your shell.nix
file look like?
I just created it the way cabal2nix --shell . > shell.nix
for testApp
and testApp-dep
, nothing more.
You want to manually author the shell.nix
derivation to just be this:
(import ./release.nix).testApp.env
Then nix-shell
with no arguments should work correctly (or alternatively cabal configure
if you set nix: True
)
nix-shell error: value is a function while a set was expected, at /home/rsoeldner/tmp/shell.nix:1:1 :cry:
Thank you really much, I will figure out now, hopefully :smile: :+1:
Oh, you need to change it to:
(import ./release.nix {}).testApp.env