haskell-nix icon indicating copy to clipboard operation
haskell-nix copied to clipboard

From Stack to Nix and beyond

Open rsoeldner opened this issue 6 years ago • 13 comments

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

rsoeldner avatar Apr 20 '18 13:04 rsoeldner

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

Gabriella439 avatar Apr 20 '18 15:04 Gabriella439

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

rsoeldner avatar Apr 20 '18 16:04 rsoeldner

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.

Gabriella439 avatar Apr 20 '18 16:04 Gabriella439

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 avatar Apr 20 '18 17:04 rsoeldner

@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
  • 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.

Gabriella439 avatar Apr 20 '18 18:04 Gabriella439

Also, I'll leave this ticket open to remind myself to document this like you requested

Gabriella439 avatar Apr 20 '18 18:04 Gabriella439

Thank you very much for the time! I would be happy to read more! :grin: :tada:

rsoeldner avatar Apr 20 '18 19:04 rsoeldner

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:

rsoeldner avatar Apr 21 '18 06:04 rsoeldner

What does your shell.nix file look like?

Gabriella439 avatar Apr 21 '18 13:04 Gabriella439

I just created it the way cabal2nix --shell . > shell.nix for testApp and testApp-dep, nothing more.

rsoeldner avatar Apr 21 '18 14:04 rsoeldner

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)

Gabriella439 avatar Apr 21 '18 14:04 Gabriella439

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:

rsoeldner avatar Apr 21 '18 15:04 rsoeldner

Oh, you need to change it to:

(import ./release.nix {}).testApp.env

Gabriella439 avatar Apr 21 '18 15:04 Gabriella439