Giter Site home page Giter Site logo

utdemir / hs-nix-template Goto Github PK

View Code? Open in Web Editor NEW
102.0 6.0 12.0 103 KB

A Haskell project template that uses Nix and comes with cabal-install, ghcid, ormolu, haskell-language-server and more.

License: MIT License

Nix 84.47% Haskell 6.44% Shell 2.36% Python 6.73%
haskell nix template-project hacktoberfest

hs-nix-template's Introduction

hs-nix-template

CI Status

A cookiecutter template which creates a Haskell project that

  • Can be built with Nix and cabal-install,
  • Has a library, an executable and a test suite,
  • Comes with a shell.nix which provides an environment with haskell-language-server, ghcid, ormolu and more,
  • ghci integrates with Haddock and Hoogle for all dependencies (:doc, :hoogle),
  • Can be built into a Docker container,
  • Uses a pinned nixpkgs managed by niv.

Usage

No need to install anything, just run:

nix-shell -p cookiecutter git --run 'cookiecutter gh:utdemir/hs-nix-template'

Once that completes, cd into the directory and call:

nix-shell

(includes a recent GHC, cabal, a populated hoogle database, haskell-language-server, ghcid and more)

Or you can directly build the executable for your project with:

nix-build --attr exe

Or deploy to docker image:

nix-build --attr docker

And load the resulting image:

docker load -i result

Cheat Sheet

Use the hpack configuration tool

When running cookiecutter, you'll be presented with the option of using cabal's default configuration format or hpack. When using hpack, you will need to run hpack to generate a cabal file before building, i.e.

hpack .
cabal build

The nix shell will contain hpack for local development and the nix build will automatically handle changes to package.yaml. See the hpack documentation for more information.

Run Hoogle locally

  • hoogle server --local -p 3000 -n

You need the -n to stop hoogle from trying to use https locally. You will need to kill and reload this whenever you add or remove a dependency in your cabal file (if you use lorri your shell will reload for you as well). This is so hoogle will use the newly generated database with your added/modified dependencies.

Add external source with niv

  • From Hackage
    • niv add extra --version 1.6.20 -a name=extra -t 'https://hackage.haskell.org/package/<name>-<version>/<name>-<version>.tar.gz'
  • From GitHub with specific revision
    • niv add ndmitchell/ghcid -a rev=e3a65cd986805948687d9450717efe00ff01e3b5

Add external tool to default.nix

The sources you pull down with niv are accessible under sources.<name> (fyi: <name> is the key in sources.json). However, that is a derivation with the attributes you need to fetch the source, not the source itself. If you want to pull in a source you need to either give it to a function that knows how to fetch the contents like callCabal2nix or if the source has a default.nix you can import it directly, like so: import sources.<name> {}.


Often you will want to explore what you have just imported since you may only want one of its attributes, you can do this by adding the source as an exported attribute in your default.nix:

in
  if pkgs.lib.inNixShell then shell else {
    inherit exe;
    inherit docker;
    ### Added here
    src = import sources.<name> {};
  }

Then call nix repl default.nix and you can tab complete src.<tab> to see what attributes are inside. (Note, this is how I know to call (import sources.niv {}).niv to get the niv derivation).


On the other hand here is an example with callCabal2nix adding the specific version of ghcid we fetched earlier to our development shell:

    buildInputs = with pkgs.haskellPackages; [
      cabal-install
      ### Added modified development tool here
      (pkgs.haskell.lib.justStaticExecutables
          (pkgs.haskellPackages.callCabal2nix "ghcid" (sources.ghcid) {}))
      ormolu
      hlint
      pkgs.niv
      pkgs.nixpkgs-fmt
    ];

If you exit your nix-shell to reload this change you will find it won't build. However, this means you won't have access to niv or other development tools you may need to get the derivation building again. I strongly recommend using lorri to handle re-building your development environment, among other useful features it will load up the last successful build for your development environment alleviating this issue entirely.

To get this version of ghcid building you need to provide a specific version of the extra library:

  extra = pkgs.haskellPackages.callCabal2nix "extra" (sources.extra) {};

  shell = myHaskellPackages.shellFor {
    packages = p: [

Then add it to the end of your callCabal2nix call:

      (pkgs.haskell.lib.justStaticExecutables
          (pkgs.haskellPackages.callCabal2nix "ghcid" (sources.ghcid) {inherit extra;}))

Note: I am building ghcid with haskellPackages not myHaskellPackages. If the tool fails to build you might want to either use a different package set or modify one yourself so the tool has the right dependencies.

Speed up dependency building

For some packages, like extra we don't need its documentation or setup for profiling since its just a dependency of a build tool. You can speed up building dependencies with a modified package set:

  fastHaskellPackages = pkgs.haskellPackages.override {
    overrides = hself: hsuper: rec {
      mkDerivation = args: hsuper.mkDerivation (args // {
        doCheck = false;
        doHaddock = false;
        enableLibraryProfiling = false;
        enableExecutableProfiling = false;
        jailbreak = true;
      });
    };
  };

  ### The external library then is build with the modified package set
  extra = fastHaskellPackages.callCabal2nix "extra" (sources.extra) {};

Add external dependency to default.nix

Lets say you wanted to add extra as dependency of your project and its not in the package set by default:

  myHaskellPackages = pkgs.haskell.packages.${compiler}.override {
    overrides = hself: hsuper: {
      ### Add new dependences here
      extra =
        hself.callCabal2nix
          "extra"
          (sources.extra)
          {};
      ### Local package without a default.nix and don't run tests
      hedgehog = self.haskell.lib.dontCheck (hself.callCabal2nix "hedgehog" /absolute/path/to/project/haskell-hedgehog/hedgehog {});
      "your-project-name" =
        hself.callCabal2nix
          "your-project-name"
          (gitignore ./.)
          {};
    };
  };

This will not only add extra to your project, but also build the documentation for you. However, to get it in your local hoogle database you need to add it to your cabal file and then call nix-shell.

Override dependency in default.nix

If you want to override a dependency you add it like we did above with extra, just make sure the name is identical to what is in the package set. As you would expect, the name in the package set is the same as the name on Hackage. However, there are a few packages with multiple versions, like zip and zip_1_4_1.

If you want to see exactly what is in your modified package run nix repl default.nix and you will get this:


Loading 'default.nix'...
Added 3 variables.

nix-repl>

Then you can tab complete to see what is in myHaskellPackages

nix-repl> myHaskellPackages.ex<tab>

This is also the best way to find out what versions of libraries are in a package set. Instead of having to add them to your cabal file to find out the version you can just view the version attribute. Again in nix repl

nix-repl> myHaskellPackages.extra.version
1.6.20

Importing your library in another project

One of the attributes default.nix exports is "your-project-name". This is so you can easily import your project's library into your other Haskell projects.

If you want to import your project locally you can just directly reference the default.nix file.

  myHaskellPackages = pkgs.haskell.packages.${compiler}.override {
    overrides = hself: hsuper: {
      ### local import
      "your-project-name" =
        (import /absolute/path/to/your-project-name/default.nix {}).your-project-name;

The downside to this approach is that your continuous integration or others won't be able to build your project from scratch. You need to host the code somewhere online and fetch it in the derivation. You can use niv to fetch your code from github or the like and then import it like so:

  myHaskellPackages = pkgs.haskell.packages.${compiler}.override {
    overrides = hself: hsuper: {
      ### local import
      "your-project-name" =
        (import source.your-project-name {}).your-project-name;

Deploy to Docker Image

The third project in haskell-nix goes into detail how this works, but we have already included docker under the docker attribute.

Note: if your project name has a space in it, the executable path will be wrong.

hs-nix-template's People

Contributors

chiroptical avatar dalpd avatar purcell avatar skyfold avatar steshaw avatar utdemir avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

hs-nix-template's Issues

Hedgehog seems to builds from source

I'm stuck on windows for a bit so I'm trying out hs-nix-template from a new VM. It seems that hedgehog has to be built from source for some reason and that takes forever and it seems its dependencies also have to be built from source.

[demo@nixos:~/code/test-nix-template]$ nix-shell -p nix-info --run "nix-info -m"
 - system: `"x86_64-linux"`
 - host os: `Linux 5.4.35, NixOS, 20.03.1577.74a80c5a9ab (Markhor)`
 - multi-user?: `yes`
 - sandbox: `yes`
 - version: `nix-env (Nix) 2.3.4`
 - channels(root): `"nixos-20.03.1577.74a80c5a9ab"`
 - channels(demo): `"home-manager"`
 - nixpkgs: `/nix/var/nix/profiles/per-user/root/channels/nixos`

Adding hpack?

Hey, I recently discovered this template and it is super helpful. I was wondering if you think adding hpack would be beneficial to the template? The nix build automatically picks it up and you don't have to add all of your modules to the cabal file.

I would gladly do the work, but I wanted to see if it aligns with your view of the project :)

Summoner integration and more

Hi, I stumbled upon this repo from reading https://discourse.nixos.org/t/nix-haskell-development-2020/6170 and it was very useful in my journey with nix.

I developed a personal version that includes summoner Haskell project scaffolding and lorri integration! Unfortunately it's not a fork but I'm opening this issue to share what I did since it can fit other persons necessities too.

The repo is here: https://github.com/bolt12/hs-nix-template

Please tell me if you think I stepped a line somewhere.

Optionally add `relude` and common GHC extensions

relude is my go-to custom prelude, and I currently spend time adding the required mixins' section. Same with some GHC extensions.

I'd enable them unconditionally if this project were only for me, but it'd probably make other users happier if we prompt at the beginning.

"Prefer composable approach"

There is an issue in haskell-nix repo suggesting a few good practices, and they all make sense to me. I think we should apply them to our template too.

To quote:

The examples use methods that cause issues when trying to extend the package set further. Using the right tools, which are no harder, we can avoid frustration down the line.

  • Use overlays instead of overrides. They are the way to go, because they are more powerful and, I'd argue, more consistent: overlays for packages sets, overrides for packages.
  • Avoid rec keyword. People get used to it and will use it in overlay definitions, leading to surprises.
  • Use haskellPackages.extend instead of .override { overrides =. It lets you add more layers of overrides which is more flexible. Attempting the same on the current code replaces the single layer of overrides with the new one, which is unexpected. As a bonus, it is syntactically simpler.

I think the first and third point is relevant to us.

Adding some workflow examples

My plan is to add:

  • How to add new development tool or dependency with niv (and link back to niv repo)
  • Note about keeping a nix-shell open while changing default.nix or using lorri
  • link how to setup docker builds

Let me know if I'm putting too much information.

Add license

I'd like to fork this if only to have a public CI to see errors but there isn't a license, can you please add one?

Validate user input to avoid creating malformed files

Currently most of the inputs we allow are raw strings. The user can easily pass incorrect values (project names with disallowed characters, module names that does not fit haskell syntax).

We should have a script which validates them to ensure that the files we generate are correct.

It doesn't have to be a super strict validation, I am happy if a dedicated user can break it; but we should at least protect for simple mistakes.

Flake support

The upcoming flakes feature of Nix simplifies the setup on that project, so I think we need to migrate to it once it is released.

Actually, using https://github.com/edolstra/flake-compat we could even use the flake-based setup today, however it lacks many niceties coming with flakes, so I still think we should wait for an official release.

An example Haskell project using flakes is here: https://github.com/utdemir/nix-tree

Add another branch with Haskell IDE Engine

I've been getting hie working locally to see how it works in practice. There are a few downsides to getting it setup: You need to either compile it yourself (2 hours plus) or set your nixpkgs to the same checkout as one of the pre-built ones that you can get on cachix (see this repo). I've been running into issues if I use a different checkout of nixpkgs even if I use the correct version of ghc and cabal. There is also a hack I need to write to get hie to find the hoogle database. Either way I would want to put the setup with hie in another branch so users can choose to just use ghcid (which always works).

Note: Setting up cachix is more difficult than I thought since we cannot just upload the shell derivation since it will be different for each user. Instead we would need to build the parts individually.

Add the 'executable' section conditionally

Currently hs-nix-template generates a package with a library, executable and a test-suite.

In my experience, users usually create a library than an executable. So, hs-nix-template should prompt the user if an executable section is necessary, and only conditionally create the section and the related app/ directory.

github actions workflow with multiple lines?

- run: nix-build ci.nix --argstr use_hpack "${{ matrix.use_hpack }}" --argstr add_executable_section "${{ matrix.add_executable_section }}"

While working on #22, we tried using the multiline syntax (from https://yaml-multiline.info). Basically, writing,

 - run: >-
   nix-build ci.nix
     --argstr use_hpack "${{ matrix.use_hpack }}"
     --argstr add_executable_section "${{ matrix.add_executable_section }}" 

However, this caused CI to fail. I tried both >, >- and,

 - run: |
   nix-build ci.nix \
     --argstr use_hpack "${{ matrix.use_hpack }}" \
     --argstr add_executable_section "${{ matrix.add_executable_section }}" 

None of these work. If anyone knows how these things work that would be great :)

Change testing framework to tasty-discover

I'm thinking of changing the test framework to tasty-discover. The reason is that, it also supports more conventional unit tests rather than property tests.

What do you think @Skyfold ?

Edit: Looks like tasty-discover is not maintained. Maybe we can just go to plain tasty. Do you know any alternatives which supports both property tests and unit tests?

Leverage Recursive Nix to have more comprehensive tests

Nix 2.4 is supposed to be released soon (famous last words), which should support invoking Nix inside Nix derivations.

That would allow us writing more tests like

  1. Generate the project
  2. Enter to a nix-shell (this part we can not do yet)
  3. Run cabal build.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.