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

Add more flakes support and getting started guide #972

Merged
merged 8 commits into from Feb 22, 2021

Conversation

hamishmack
Copy link
Collaborator

This PR adds a flake function to haskell.nix projects. It can
be used to transform the outputs of project into a flattened structure
that can be used to make a flake.nix file for your project.

Because the nix code and commands used are different a lot of stuff in
the getting-started.md will not work with a flake.nix. So instead
of trying to add a flake section to that guide this PR adds a new
version for Nix Flake users.

This PR adds a `flake` function to haskell.nix projects.  It can
be used to transform the outputs of project into a flattened structure
that can be used to make a `flake.nix` file for your project.

Because the nix code and commands used are different a lot of stuff in
the getting-started.md will not work with a `flake.nix`.  So instead
of trying to add a flake section to that guide this PR adds a new
version for Nix Flake users.
@blaggacao
Copy link

blaggacao commented Jan 6, 2021

Copy link

@blaggacao blaggacao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to comment.

outputs = { self, nixpkgs, haskellNix, flake-utils }:
flake-utils.lib.eachSystem [ "x86_64-linux" "x86_64-darwin" ] (system:
let
pkgs = haskellNix.legacyPackages.${system};
Copy link

@blaggacao blaggacao Jan 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be acomplished through an overlay, instead.
Like: preOverlays = [ devshell.overlay ] ++ haskell-nix.overlays; (using flake-utils's simpleFlake)


# This is used by `nix develop .` to open a shell for use with
# `cabal`, `hlint` and `haskell-language-server`
devShell = project.shellFor {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside: I would appreciate intgration with the more expressive (& upcoming) numtide/devshell

# let
# pkgs = haskellNix.legacyPackages.${system};
# project = pkgs.haskell-nix.project' {
# src = ./.;
Copy link

@blaggacao blaggacao Jan 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src = self; (to teach convention)

@blaggacao

This comment has been minimized.

@blaggacao
Copy link

blaggacao commented Jan 6, 2021

See also: numtide/flake-utils#17

@hamishmack
Copy link
Collaborator Author

simpleFlake looks cool!

I'm going to be working on #963 then #980 for the next couple of days so feel free to send PRs to this branch if you like. This was my first foray into flakes so feel free to replace/change stuff.

I presume those three files would be flake.nix, overlay.nix and haskell.nix? What would go in devshell.nix (for shell = devshell.nix; in the flake.nix file to work)?

What is nix/buildAndFlash.nix?

@blaggacao
Copy link

I'm still going about the journey of getting this right and will definitly report back (maybe today). I wasn't able to correctly pin nixpkgs, so It built for hours and hours. I'll therefore have a look at #991, too.

@blaggacao
Copy link

blaggacao commented Jan 6, 2021

This starts to work:

# overlay.nix
{ self, name }: final: prev:
{
  "${name}" =
    let
      thisHaskellProject = (
        prev.haskell-nix.project' {
          src = prev.haskell-nix.haskellLib.cleanGit {
            inherit name;
            src = ./.; # parses ./*.cabal
          };
          compiler-nix-name = "ghc865";
          index-state = "2020-12-15T00:00:00Z";
          plan-sha256 = "sha256-nekzAgYkCiZD6rO6Hhk3ZBTScAP54be66KFtxaRdXuU=";
        }
      );
      thisHaskellProjectFlake = thisHaskellProject.flake {};
    in
      {

        ghc = thisHaskellProject.ghcWithHoogle (ps: with ps; [
          arduino-copilot # why do I need to do this??!? Shouldn't this be preset for thisHaskellProject as per cabal file?
        ]);

        # defaultPackage = prev.callPackage ./nix/buildAndFlash.nix {inherit self;};
        checks = thisHaskellProjectFlake.checks;

      }
      // thisHaskellProjectFlake.packages;
}
# flake.nix
{
  description = "CashBoxy is an electronic cashier & vault for bank notes";
  # nixConfig.substituters = [ "https://hydra.iohk.io" ];

  # To update all inputs:
  # $ nix flake update --recreate-lock-file
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/master";
  inputs.flake-utils.url = "github:numtide/flake-utils";
  inputs.devshell.url = "github:numtide/devshell/master";
  # inputs.haskell-nix.url = "github:input-output-hk/haskell.nix/master";
  # inputs.haskell-nix.url = "github:input-output-hk/haskell.nix?ref=hkm/more-flake-support";
  inputs.haskell-nix.url = "github:input-output-hk/haskell.nix?rev=057c5f498d2804930c584a7dffa26a2e504be6c2";

  outputs = { self, nixpkgs, flake-utils, devshell, haskell-nix }:
    let
      name = "CashBoxy";
    in flake-utils.lib.simpleFlake {
      inherit self name nixpkgs;
      preOverlays = [ devshell.overlay haskell-nix.overlay ];
      overlay = (import ./overlay.nix) {inherit self name;};
      shell = (import ./devshell.nix) {inherit self name;};
    };
}
# devshell.nix
{ self, name }: { pkgs }:
with pkgs; mkDevShell {
  inherit name;

  packages = [
    CashBoxy.ghc
  ];

  bash = {
    extra = ''
      git config git-town.code-hosting-driver gitea
      git config git-town.code-hosting-origin-hostname git.p1.rocks
      git config git-town.main-branch-name master
    '';
  };

  commands = [
    {
      help = "Spins up a local Hoogle: a Haskell API search engine";
      name = "docs";
      category = "arduino-copilot";
      command = "${CashBoxy.ghc}/bin/hoogle server --local";
    }
    {
      help = platformio.meta.description;
      name = "pio";
      category = "micro-controllers";
      package = platformio;
    }
    {
      help = "UART hex device monitoring";
      command = "${platformio}/bin/pio device monitor -f hexlify";
      name = "piohexmon";
      category = "micro-controllers";
      package = platformio;
    }
    {
      help = "Transpile Main to C99, build & upload to board (platformio.ini)";
      command = "cd $DEVSHELL_ROOT && ${CashBoxy.ghc}/bin/runghc Main && ${platformio}/bin/pio run -t upload";
      name = "run";
      category = "micro-controllers";
    }
    {
      help = git-town.meta.description;
      name = git-town.pname;
      category = "utilities";
      package = git-town;
    }
  ];
}
Cabal-Version: 2.2

Name:           CashBoxy
Version:        0.1.0.0
Description:    Please see the README at <https://git.p1.rocks/SideProjects/CashBoxy>
Author:         Andres Luque
Maintainer:     carlosluq1@hotmail.com
Copyright:      2020 Andres Luque
License:        MIT
License-file:   LICENSE
Extra-Source-Files:
    README.md
    ChangeLog.md

Source-Repository head
  type: git
  location: git@git.p1.rocks:SideProjects/CashBoxy.git

Executable Main
  GHC-Options: -Wall -fno-warn-tabs
  Default-Language: Haskell2010
  Main-Is: Main.hs
  Build-Depends:
      arduino-copilot -- it's already here ?!
    , base >=4.6 && <5

@blaggacao
Copy link

blaggacao commented Jan 7, 2021

Aside: why devshell is cool...

$ nix develop
🔨 Welcome to CashBoxy

[arduino-copilot]

  docs      - Spins up a local Hoogle: a Haskell API search engine

[general commands]

  menu      - prints this menu

[micro-controllers]

  pio       - An open source ecosystem for IoT development
  piohexmon - UART hex device monitoring
  run       - Transpile Main to C99, build & upload to board (platformio.ini)

[utilities]

  git-town  - Generic, high-level git support for git-flow workflows

@hamishmack
Copy link
Collaborator Author

I tried out simpleFlake, but I had some problems:

  • It does not seem to support apps yet (perhaps they could be added like checks)
  • It wants checks to be in with packages. Would id make sense for simpleFlake to support packages, checks and apps all to be on the same level (as they are in the flake output)? I'm not sure if I like assuming everything is a package. If it is a mater of backwards compatibility it could just add a check for packages like the one for checks and apps.

I have updated the example to use an overlay in a similar way to simpleFlake, we can change it to use simpleFlake in another PR (or perhaps in a more detailed guide).

The devshell stuff should be in another PR and it would be nice if it could be used in shellFor in a way that would make it work for flake and non flake haskell.nix projects.

I have also added nixpkgs inputs to this PR as I found it was very handy to be able to use the follows trick for getting the right nixpkgs while testing.

@hamishmack
Copy link
Collaborator Author

bors try

iohk-bors bot added a commit that referenced this pull request Feb 22, 2021
@iohk-bors
Copy link
Contributor

iohk-bors bot commented Feb 22, 2021

@hamishmack hamishmack merged commit 172d402 into master Feb 22, 2021
@iohk-bors iohk-bors bot deleted the hkm/more-flake-support branch February 22, 2021 07:42
@dhess
Copy link
Contributor

dhess commented Feb 22, 2021

This looks fantastic. Thanks!

@hhefesto
Copy link

Hello!

I'm trying to try this out but I've come to an error that I'm not sure how to fix.

I have a working default.nix that uses haskell.nix:

{ # Fetch the latest haskell.nix and import its default.nix
  haskellNix ? import (builtins.fetchTarball "https://github.com/input-output-hk/haskell.nix/archive/ef6ca0f431fe3830c25cb2d185367245c1cce894.tar.gz") {}
  # haskellNix ? import (builtins.fetchTarball "https://github.com/input-output-hk/haskell.nix/archive/master.tar.gz") {}

  # # For LLVM
  # , enableLLVMAssertions ? true # TODO: Fix

, compiler ? "ghc884"
}:
let
  # haskell.nix provides access to the nixpkgs pins which are used by our CI,
  # hence you will be more likely to get cache hits when using these.
  # But you can also just use your own, e.g. '<nixpkgs>'.
  nixpkgsSrc = haskellNix.sources.nixpkgs-2003;

  # haskell.nix provides some arguments to be passed to nixpkgs, including some
  # patches and also the haskell.nix functionality itself as an overlay.
  nixpkgsArgs = haskellNix.nixpkgsArgs;

  telomare_jumper = pkgs.stdenv.mkDerivation {
    name = "telomareJumper";
    src = ./cbits;
    buildInputs = [ pkgs.boehmgc ];
  };

  telomareOverlays = [ (self: super: {
    jumper = telomare_jumper;
    gc = self.boehmgc;
    llvm-config = self.llvm_9;
    alex = self.haskellPackages.alex;
  }) ];

  # import nixpkgs with overlays
  pkgs = (import nixpkgsSrc (nixpkgsArgs // { overlays = nixpkgsArgs.overlays ++ telomareOverlays;}));
in
pkgs.haskell-nix.cabalProject {
  src = pkgs.haskell-nix.cleanSourceHaskell {
    src = ./.;
    name = "telomare";
  };
  compiler-nix-name = compiler;
  pkg-def-extras = with pkgs.haskell.lib; [
     (hackage: {
       llvm-hs = hackage.llvm-hs."9.0.1".revisions.default;
       llvm-hs-pure = hackage.llvm-hs-pure."9.0.0".revisions.default;
     })
   ];
  modules = [
    { reinstallableLibGhc = true; }
  ];
}

And now I would really like to turn it into a flake. My unsuccessful attempt is:

{
  description = "Nix flake for Telomare";
  inputs.haskellNix.url = "github:input-output-hk/haskell.nix";
  inputs.nixpkgs.follows = "haskellNix/nixpkgs-unstable";
  inputs.flake-utils.url = "github:numtide/flake-utils";
  outputs = { self, nixpkgs, flake-utils, haskellNix }:
    flake-utils.lib.eachSystem [ "x86_64-linux" "x86_64-darwin" ] (system:
    let
      overlays = [ haskellNix.overlay
        (final: prev: {
          # This overlay adds our project to pkgs
          # jumper = telomare_jumper;
          jumper = final.stdenv.mkDerivation {
            name = "telomareJumper";
            src = ./cbits;
            buildInputs = [ final.boehmgc ];
          };
          gc = final.boehmgc;
          llvm-config = final.llvm_9;
          alex = final.haskellPackages.alex;

          telomare = final.haskell-nix.cabalProject {
            src = final.haskell-nix.cleanSourceHaskell {
              src = ./.;
              name = "telomare";
            };
            compiler-nix-name = "ghc884";
            pkg-def-extras = with final.haskell.lib; [
               (hackage: {
                 llvm-hs = hackage.llvm-hs."9.0.1".revisions.default;
                 llvm-hs-pure = hackage.llvm-hs-pure."9.0.0".revisions.default;
               })
             ];
            modules = [
              { reinstallableLibGhc = true; }
            ];
          };
        })
      ];
      pkgs = import nixpkgs { inherit system overlays; };
      flake = pkgs.telomare.flake {};
    in flake // {
      # Built by `nix build .`
      defaultPackage = flake.packages."telomare:exe:telomare-exe";

      # This is used by `nix develop .` to open a shell for use with
      # `cabal`, `hlint` and `haskell-language-server`
      devShell = pkgs.telomare.shellFor {
        tools = {
          cabal = "latest";
          hlint = "latest";
          haskell-language-server = "latest";
        };
      };
    });
}

Which errors with RestrictedPathError because call-cabal-project-to-nix.nix is trying to use readIfExists

This is the full error with trace turned on:

╭─hhefesto@Olimpo ~/src/telomare ‹nix-flake*› 
╰─$ nix build --show-trace               
warning: Git tree '/home/hhefesto/src/telomare' is dirty
error: --- RestrictedPathError ------------------------------------------------------------------------------------------------------------------------------------------------------- nix
access to path '/nix/store/r4bms7fx16cavkzb8y1kk0zlv0fp6p9k-telomare' is forbidden in restricted mode
--------------------------------------------------------------------------------------- show-trace ---------------------------------------------------------------------------------------
trace: while evaluating 'readIfExists'
at: (2:25) in file: /nix/store/di7w1g4s2fyh4b4814sawg7n1pdvjaq9-source/lib/call-cabal-project-to-nix.nix

     1| { dotCabal, pkgs, runCommand, evalPackages, symlinkJoin, cacert, index-state-hashes, haskellLib, materialize }@defaults:
     2| let readIfExists = src: fileName:
      |                         ^
     3|       let origSrcDir = src.origSrcSubDir or src;

trace: from call site
at: (18:26) in file: /nix/store/di7w1g4s2fyh4b4814sawg7n1pdvjaq9-source/lib/call-cabal-project-to-nix.nix

    17| , cabalProjectFileName ? "cabal.project"
    18| , cabalProject         ? readIfExists src cabalProjectFileName
      |                          ^
    19| , cabalProjectLocal    ? readIfExists src "${cabalProjectFileName}.local"

trace: while evaluating anonymous lambda
at: (9:1) in file: /nix/store/di7w1g4s2fyh4b4814sawg7n1pdvjaq9-source/lib/call-cabal-project-to-nix.nix

     8| in
     9| { name          ? src.name or null # optional name for better error messages
      | ^
    10| , src

trace: from call site
at: (502:36) in file: /nix/store/di7w1g4s2fyh4b4814sawg7n1pdvjaq9-source/overlays/haskell.nix

   501|               args = { caller = "cabalProject'"; } // args';
   502|               callProjectResults = callCabalProjectToNix args;
      |                                    ^
   503|             in let pkg-set = mkCabalProjectPkgSet

trace: while evaluating anonymous lambda
at: (5:1) in file: /nix/store/di7w1g4s2fyh4b4814sawg7n1pdvjaq9-source/lib/import-and-filter-project.nix

     4| { pkgs, haskellLib }:
     5| { projectNix, sourceRepos, src }:
      | ^
     6| let

trace: from call site
at: (505:31) in file: /nix/store/di7w1g4s2fyh4b4814sawg7n1pdvjaq9-source/overlays/haskell.nix

   504|                 { inherit compiler-nix-name;
   505|                   plan-pkgs = importAndFilterProject {
      |                               ^
   506|                     inherit (callProjectResults) projectNix sourceRepos src;

trace: while evaluating the attribute 'buildPackages.haskell-nix.compiler."${(((plan-pkgs).pkgs  hackage)).compiler.nix-name}".version'
at: (362:7) in file: /nix/store/ydv1dhahymi31sfyy8bpacz3a9xzs46k-source/lib/attrsets.nix

   361|       inherit name;
   362|       value = f name (catAttrs name sets);
      |       ^
   363|     }) names);

trace: while evaluating 'mkCabalProjectPkgSet'
at: (152:13) in file: /nix/store/di7w1g4s2fyh4b4814sawg7n1pdvjaq9-source/overlays/haskell.nix

   151|         mkCabalProjectPkgSet =
   152|             { plan-pkgs  # Path to the output of plan-to-nix
      |             ^
   153|             , pkg-def-extras ? []

trace: from call site
at: (503:30) in file: /nix/store/di7w1g4s2fyh4b4814sawg7n1pdvjaq9-source/overlays/haskell.nix

   502|               callProjectResults = callCabalProjectToNix args;
   503|             in let pkg-set = mkCabalProjectPkgSet
      |                              ^
   504|                 { inherit compiler-nix-name;

trace: while evaluating the attribute 'hsPkgs'
at: (518:43) in file: /nix/store/di7w1g4s2fyh4b4814sawg7n1pdvjaq9-source/overlays/haskell.nix

   517|                 project = addProjectAndPackageAttrs rec {
   518|                   inherit (pkg-set.config) hsPkgs;
      |                                           ^
   519|                   inherit pkg-set;

trace: while evaluating the attribute 'hsPkgs'
at: (538:15) in file: /nix/store/di7w1g4s2fyh4b4814sawg7n1pdvjaq9-source/overlays/haskell.nix

   537|             in rawProject // rec {
   538|               hsPkgs = final.lib.mapAttrs (n: package':
      |               ^
   539|                 if package' == null

trace: while evaluating 'cabalProject'
at: (628:13) in file: /nix/store/di7w1g4s2fyh4b4814sawg7n1pdvjaq9-source/overlays/haskell.nix

   627|         cabalProject =
   628|             { src, compiler-nix-name, ... }@args':
      |             ^
   629|             let

trace: from call site
at: (28:22) in file: /nix/store/w0hlikdyaq24gmh17ssznyvhhfqzi9as-source/flake.nix

    27| 
    28|           telomare = final.haskell-nix.cabalProject {
      |                      ^
    29|             src = final.haskell-nix.cleanSourceHaskell {

trace: while evaluating the attribute 'telomare.flake'
at: (28:11) in file: /nix/store/w0hlikdyaq24gmh17ssznyvhhfqzi9as-source/flake.nix

    27| 
    28|           telomare = final.haskell-nix.cabalProject {
      |           ^
    29|             src = final.haskell-nix.cleanSourceHaskell {

trace: while evaluating anonymous lambda
at: (7:68) in file: /nix/store/w0hlikdyaq24gmh17ssznyvhhfqzi9as-source/flake.nix

     6|   outputs = { self, nixpkgs, flake-utils, haskellNix }:
     7|     flake-utils.lib.eachSystem [ "x86_64-linux" "x86_64-darwin" ] (system:
      |                                                                    ^
     8|     let

trace: from call site
at: (69:17) in file: /nix/store/m3qzm0c5r708l5h8gn7dwrlciib37x0d-source/default.nix

    68|         let
    69|           ret = f system;
      |                 ^
    70|           op = attrs: key:

trace: while evaluating 'op'
at: (67:19) in file: /nix/store/m3qzm0c5r708l5h8gn7dwrlciib37x0d-source/default.nix

    66|     let
    67|       op = attrs: system:
      |                   ^
    68|         let

trace: from call site
at: (79:5) in file: /nix/store/m3qzm0c5r708l5h8gn7dwrlciib37x0d-source/default.nix

    78|     in
    79|     builtins.foldl' op { } systems
      |     ^
    80|   ;

trace: while evaluating 'eachSystem'
at: (65:25) in file: /nix/store/m3qzm0c5r708l5h8gn7dwrlciib37x0d-source/default.nix

    64|   #
    65|   eachSystem = systems: f:
      |                         ^
    66|     let

trace: from call site
at: (7:5) in file: /nix/store/w0hlikdyaq24gmh17ssznyvhhfqzi9as-source/flake.nix

     6|   outputs = { self, nixpkgs, flake-utils, haskellNix }:
     7|     flake-utils.lib.eachSystem [ "x86_64-linux" "x86_64-darwin" ] (system:
      |     ^
     8|     let

trace: while evaluating 'outputs'
at: (6:13) in file: /nix/store/w0hlikdyaq24gmh17ssznyvhhfqzi9as-source/flake.nix

     5|   inputs.flake-utils.url = "github:numtide/flake-utils";
     6|   outputs = { self, nixpkgs, flake-utils, haskellNix }:
      |             ^
     7|     flake-utils.lib.eachSystem [ "x86_64-linux" "x86_64-darwin" ] (system:

trace: from call site
at: (45:21) from string

    44| 
    45|           outputs = flake.outputs (inputs // { self = result; });
      |                     ^
    46| 

trace: while evaluating anonymous lambda
at: (10:13) from string

     9|     builtins.mapAttrs
    10|       (key: node:
      |             ^
    11|         let

trace: from call site
trace: while evaluating the attribute 'root'
trace: while evaluating anonymous lambda
at: (2:23) from string

     1| 
     2| lockFileStr: rootSrc: rootSubdir:
      |                       ^
     3| 

trace: from call site

Any guidance would be very much appreciated.

@hhefesto
Copy link

Explicitly adding cabalProjectFreeze cabalProject cabalProjectLocal parameters as null to the cabalProject function seems to have solved the problem (still building).

For completeness, this is my flake updated:

{
  description = "Nix flake for Telomare";
  inputs.haskellNix.url = "github:input-output-hk/haskell.nix";
  inputs.nixpkgs.follows = "haskellNix/nixpkgs-unstable";
  inputs.flake-utils.url = "github:numtide/flake-utils";
  outputs = { self, nixpkgs, flake-utils, haskellNix }:
    flake-utils.lib.eachSystem [ "x86_64-linux" "x86_64-darwin" ] (system:
    let
      overlays = [ haskellNix.overlay
        (final: prev: {
          # This overlay adds our project to pkgs
          jumper = final.stdenv.mkDerivation {
            name = "telomareJumper";
            src = ./cbits;
            buildInputs = [ final.boehmgc ];
          };
          gc = final.boehmgc;
          llvm-config = final.llvm_9;
          alex = final.haskellPackages.alex;

          telomare = final.haskell-nix.cabalProject {
            # If these null parameters are absent, you get a RestrictedPathError error
            # from trying to do readIfExists on cabal.project file
            cabalProjectFreeze = null;
            cabalProject = null;
            cabalProjectLocal = null;

            src = final.haskell-nix.cleanSourceHaskell {
              src = ./.;
              name = "telomare";
            };
            compiler-nix-name = "ghc884";
            pkg-def-extras = with final.haskell.lib; [
               (hackage: {
                 llvm-hs = hackage.llvm-hs."9.0.1".revisions.default;
                 llvm-hs-pure = hackage.llvm-hs-pure."9.0.0".revisions.default;
               })
             ];
            modules = [
              { reinstallableLibGhc = true; }
            ];
          };
        })
      ];
      pkgs = import nixpkgs { inherit system overlays; };
      flake = pkgs.telomare.flake {};
    in flake // {
      # Built by `nix build .`
      defaultPackage = flake.packages."telomare:exe:telomare-exe";

      # This is used by `nix develop .` to open a shell for use with
      # `cabal`, `hlint` and `haskell-language-server`
      devShell = pkgs.telomare.shellFor {
        tools = {
          cabal = "latest";
          hlint = "latest";
          haskell-language-server = "latest";
        };
      };
    });
}

booniepepper pushed a commit to booniepepper/haskell.nix that referenced this pull request Feb 4, 2022
This PR adds a `flake` function to haskell.nix projects.  It can
be used to transform the outputs of project into a flattened structure
that can be used to make a `flake.nix` file for your project.

Because the nix code and commands used are different a lot of stuff in
the getting-started.md will not work with a `flake.nix`.  So instead
of trying to add a flake section to that guide this PR adds a new
version for Nix Flake users.
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

Successfully merging this pull request may close these issues.

None yet

4 participants