Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

From Stack to Nix and beyond #45

Open
rsoeldner opened this issue Apr 20, 2018 · 13 comments
Open

From Stack to Nix and beyond #45

rsoeldner opened this issue Apr 20, 2018 · 13 comments

Comments

@rsoeldner
Copy link

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 😄

@Gabriella439
Copy link
Owner

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

@rsoeldner
Copy link
Author

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

@Gabriella439
Copy link
Owner

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.

@rsoeldner
Copy link
Author

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.

@Gabriella439
Copy link
Owner

@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
Copy link
Owner

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

@rsoeldner
Copy link
Author

Thank you very much for the time! I would be happy to read more! 😁 🎉

@rsoeldner
Copy link
Author

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 👍

@Gabriella439
Copy link
Owner

What does your shell.nix file look like?

@rsoeldner
Copy link
Author

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

@Gabriella439
Copy link
Owner

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)

@rsoeldner
Copy link
Author

rsoeldner commented Apr 21, 2018

nix-shell
error: value is a function while a set was expected, at /home/rsoeldner/tmp/shell.nix:1:1
😢

Thank you really much, I will figure out now, hopefully 😄 👍

@Gabriella439
Copy link
Owner

Oh, you need to change it to:

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants