Add a mini-haskell project with Cabal and flakes
This commit is contained in:
parent
73fbd6c176
commit
cc55974109
24
05-haskell/README.md
Normal file
24
05-haskell/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# 05-haskell
|
||||
|
||||
This example shows a small Haskell project packaged with Nix flakes.
|
||||
|
||||
It includes:
|
||||
|
||||
- a library module under `src/`,
|
||||
- an executable under `app/`,
|
||||
- a test suite under `test/`, and
|
||||
- a dev shell with GHC, `cabal-install`, and Haskell Language Server.
|
||||
|
||||
Useful commands:
|
||||
|
||||
```bash
|
||||
nix develop
|
||||
cabal run
|
||||
cabal test
|
||||
|
||||
nix build
|
||||
./result/bin/mini-haskell flakes
|
||||
|
||||
nix run . -- flakes
|
||||
nix flake check
|
||||
```
|
||||
@ -1,10 +1,8 @@
|
||||
module Main where
|
||||
|
||||
import MiniHaskell.Greeting (greeting)
|
||||
import System.Environment (getArgs)
|
||||
|
||||
greeting :: String -> String
|
||||
greeting name = "hello, " ++ name ++ ", from Haskell and Nix"
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
args <- getArgs
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
inherit (pkgs) haskellPackages;
|
||||
project = haskellPackages.callCabal2nix "mini-haskell" ./. { };
|
||||
checkedProject = pkgs.haskell.lib.doCheck project;
|
||||
in
|
||||
{
|
||||
packages.${system}.default = project;
|
||||
@ -32,15 +33,7 @@
|
||||
];
|
||||
};
|
||||
|
||||
checks.${system}.greeting = pkgs.runCommand "mini-haskell-greeting" { } ''
|
||||
output="$(${self.packages.${system}.default}/bin/mini-haskell flakes)"
|
||||
|
||||
if [ "$output" = "hello, flakes, from Haskell and Nix" ]; then
|
||||
echo ok > "$out"
|
||||
else
|
||||
echo "unexpected output: $output" >&2
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
# `doCheck` turns on the Cabal test suite when this derivation is built.
|
||||
checks.${system}.test-suite = checkedProject;
|
||||
};
|
||||
}
|
||||
|
||||
@ -3,8 +3,25 @@ name: mini-haskell
|
||||
version: 0.1.0.0
|
||||
build-type: Simple
|
||||
|
||||
library
|
||||
exposed-modules: MiniHaskell.Greeting
|
||||
hs-source-dirs: src
|
||||
build-depends: base >=4.14 && <5
|
||||
default-language: Haskell2010
|
||||
|
||||
executable mini-haskell
|
||||
main-is: Main.hs
|
||||
hs-source-dirs: app
|
||||
build-depends: base >=4.14 && <5
|
||||
build-depends:
|
||||
base >=4.14 && <5,
|
||||
mini-haskell
|
||||
default-language: Haskell2010
|
||||
|
||||
test-suite mini-haskell-test
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Main.hs
|
||||
hs-source-dirs: test
|
||||
build-depends:
|
||||
base >=4.14 && <5,
|
||||
mini-haskell
|
||||
default-language: Haskell2010
|
||||
|
||||
4
05-haskell/src/MiniHaskell/Greeting.hs
Normal file
4
05-haskell/src/MiniHaskell/Greeting.hs
Normal file
@ -0,0 +1,4 @@
|
||||
module MiniHaskell.Greeting where
|
||||
|
||||
greeting :: String -> String
|
||||
greeting name = "hello, " ++ name ++ ", from Haskell and Nix"
|
||||
11
05-haskell/test/Main.hs
Normal file
11
05-haskell/test/Main.hs
Normal file
@ -0,0 +1,11 @@
|
||||
module Main where
|
||||
|
||||
import MiniHaskell.Greeting (greeting)
|
||||
import System.Exit (die)
|
||||
|
||||
main :: IO ()
|
||||
main =
|
||||
if greeting "flakes" == "hello, flakes, from Haskell and Nix" then
|
||||
putStrLn "test passed"
|
||||
else
|
||||
die "unexpected greeting"
|
||||
6
06-haskell-shellfor/app/Main.hs
Normal file
6
06-haskell-shellfor/app/Main.hs
Normal file
@ -0,0 +1,6 @@
|
||||
module Main where
|
||||
|
||||
import MiniShellFor.Message (message)
|
||||
|
||||
main :: IO ()
|
||||
main = putStrLn message
|
||||
27
06-haskell-shellfor/flake.lock
generated
Normal file
27
06-haskell-shellfor/flake.lock
generated
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1776548001,
|
||||
"narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
43
06-haskell-shellfor/flake.nix
Normal file
43
06-haskell-shellfor/flake.nix
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
# Adds a local Haskell package to the package set, then uses `shellFor`
|
||||
# so the dev shell follows that package's build inputs.
|
||||
description = "A Haskell dev shell built with shellFor";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{ self, nixpkgs, ... }:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
haskellPackages = pkgs.haskellPackages.override {
|
||||
overrides = final: _: {
|
||||
mini-shellfor = final.callCabal2nix "mini-shellfor" ./. { };
|
||||
};
|
||||
};
|
||||
project = haskellPackages.mini-shellfor;
|
||||
checkedProject = pkgs.haskell.lib.doCheck project;
|
||||
in
|
||||
{
|
||||
packages.${system}.default = project;
|
||||
|
||||
apps.${system}.default = {
|
||||
type = "app";
|
||||
program = "${self.packages.${system}.default}/bin/mini-shellfor";
|
||||
meta.description = "Run the shellFor-based Haskell example.";
|
||||
};
|
||||
|
||||
devShells.${system}.default = haskellPackages.shellFor {
|
||||
packages = hp: [ hp.mini-shellfor ];
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkgs.cabal-install
|
||||
pkgs.haskell-language-server
|
||||
];
|
||||
};
|
||||
|
||||
checks.${system}.test-suite = checkedProject;
|
||||
};
|
||||
}
|
||||
27
06-haskell-shellfor/mini-shellfor.cabal
Normal file
27
06-haskell-shellfor/mini-shellfor.cabal
Normal file
@ -0,0 +1,27 @@
|
||||
cabal-version: 2.4
|
||||
name: mini-shellfor
|
||||
version: 0.1.0.0
|
||||
build-type: Simple
|
||||
|
||||
library
|
||||
exposed-modules: MiniShellFor.Message
|
||||
hs-source-dirs: src
|
||||
build-depends: base >=4.14 && <5
|
||||
default-language: Haskell2010
|
||||
|
||||
executable mini-shellfor
|
||||
main-is: Main.hs
|
||||
hs-source-dirs: app
|
||||
build-depends:
|
||||
base >=4.14 && <5,
|
||||
mini-shellfor
|
||||
default-language: Haskell2010
|
||||
|
||||
test-suite mini-shellfor-test
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Main.hs
|
||||
hs-source-dirs: test
|
||||
build-depends:
|
||||
base >=4.14 && <5,
|
||||
mini-shellfor
|
||||
default-language: Haskell2010
|
||||
4
06-haskell-shellfor/src/MiniShellFor/Message.hs
Normal file
4
06-haskell-shellfor/src/MiniShellFor/Message.hs
Normal file
@ -0,0 +1,4 @@
|
||||
module MiniShellFor.Message where
|
||||
|
||||
message :: String
|
||||
message = "shellFor keeps the dev shell aligned with the package"
|
||||
11
06-haskell-shellfor/test/Main.hs
Normal file
11
06-haskell-shellfor/test/Main.hs
Normal file
@ -0,0 +1,11 @@
|
||||
module Main where
|
||||
|
||||
import MiniShellFor.Message (message)
|
||||
import System.Exit (die)
|
||||
|
||||
main :: IO ()
|
||||
main =
|
||||
if message == "shellFor keeps the dev shell aligned with the package" then
|
||||
putStrLn "test passed"
|
||||
else
|
||||
die "unexpected message"
|
||||
@ -1,6 +1,6 @@
|
||||
# Haskell Project
|
||||
|
||||
This note covers `05-haskell/`, which packages a tiny Cabal executable with Nix and provides a dev shell for editing it.
|
||||
This note covers `05-haskell/`, which packages a tiny Cabal library and executable with Nix, runs a Cabal test suite during `nix flake check`, and provides a dev shell for editing it.
|
||||
|
||||
---
|
||||
|
||||
@ -9,16 +9,19 @@ This note covers `05-haskell/`, which packages a tiny Cabal executable with Nix
|
||||
The example combines three pieces that show up in real Haskell projects:
|
||||
|
||||
- a local Cabal package, defined by `mini-haskell.cabal`,
|
||||
- a small library module under `src/`,
|
||||
- an executable under `app/`, and
|
||||
- a test suite under `test/`,
|
||||
- a flake output that builds that package with `callCabal2nix`, and
|
||||
- a dev shell that provides GHC, `cabal-install`, and Haskell Language Server.
|
||||
|
||||
That keeps the example focused on one idea: a flake can describe both how to build a Haskell program and how to work on it interactively.
|
||||
That keeps the example focused on one idea: a flake can describe a small Haskell project end to end, including code, tests, and a development environment.
|
||||
|
||||
---
|
||||
|
||||
## 2. The Package Build
|
||||
|
||||
`pkgs.haskellPackages.callCabal2nix` reads the local Cabal file and produces a Nix derivation for the executable:
|
||||
`pkgs.haskellPackages.callCabal2nix` reads the local Cabal file and produces a Nix derivation for the package:
|
||||
|
||||
```nix
|
||||
project = haskellPackages.callCabal2nix "mini-haskell" ./. { };
|
||||
@ -26,6 +29,12 @@ project = haskellPackages.callCabal2nix "mini-haskell" ./. { };
|
||||
|
||||
The first argument is the package name as it should appear in Nix. The second is the source tree. The third is an attrset of overrides, which this example leaves empty.
|
||||
|
||||
In this example, the Cabal package contains:
|
||||
|
||||
- a library module, `MiniHaskell.Greeting`,
|
||||
- an executable that imports that library, and
|
||||
- a test suite that imports the same library.
|
||||
|
||||
That derivation becomes `packages.<system>.default`, so `nix build` produces the executable, and `nix run` executes it.
|
||||
|
||||
---
|
||||
@ -42,15 +51,20 @@ This keeps the shell small and obvious. For projects with many Haskell dependenc
|
||||
|
||||
---
|
||||
|
||||
## 4. The Check
|
||||
## 4. The Test Suite Check
|
||||
|
||||
`checks.<system>.greeting` runs the built executable with an argument and compares its output to an expected string.
|
||||
The flake defines a second derivation for checking:
|
||||
|
||||
That gives `nix flake check` one concrete behavior to verify:
|
||||
```nix
|
||||
checkedProject = pkgs.haskell.lib.doCheck project;
|
||||
checks.${system}.test-suite = checkedProject;
|
||||
```
|
||||
|
||||
`doCheck` tells the Haskell package build to run the Cabal test suite. That gives `nix flake check` one concrete behavior to verify:
|
||||
|
||||
- the Cabal package evaluates,
|
||||
- the executable builds, and
|
||||
- the program runs and prints the expected result.
|
||||
- the library and executable build, and
|
||||
- the test suite passes.
|
||||
|
||||
---
|
||||
|
||||
@ -62,6 +76,7 @@ cd 05-haskell
|
||||
nix develop
|
||||
cabal run
|
||||
cabal run -- flakes
|
||||
cabal test
|
||||
|
||||
nix build
|
||||
./result/bin/mini-haskell
|
||||
|
||||
66
notes/008-haskell-shellfor.md
Normal file
66
notes/008-haskell-shellfor.md
Normal file
@ -0,0 +1,66 @@
|
||||
# Haskell Shell with shellFor
|
||||
|
||||
This note covers `06-haskell-shellfor/`, which builds a local Haskell package and constructs the dev shell with `shellFor` instead of `mkShell`.
|
||||
|
||||
---
|
||||
|
||||
## 1. What `shellFor` Changes
|
||||
|
||||
`mkShell` is a generic shell constructor. You list tools manually.
|
||||
|
||||
`shellFor` is specific to Haskell package sets. It starts from one or more Haskell packages and builds a development environment around their dependencies.
|
||||
|
||||
That means the shell tracks the package definition more closely. When the package's Haskell dependencies change, the shell changes with it.
|
||||
|
||||
---
|
||||
|
||||
## 2. Why the Package Set Override Exists
|
||||
|
||||
The example adds the local package to the Haskell package set first:
|
||||
|
||||
```nix
|
||||
haskellPackages = pkgs.haskellPackages.override {
|
||||
overrides = final: _: {
|
||||
mini-shellfor = final.callCabal2nix "mini-shellfor" ./. { };
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
`shellFor` expects packages from the package set it is working with. Defining `mini-shellfor` inside that set makes it available both as a normal package output and as a package that `shellFor` can reference.
|
||||
|
||||
---
|
||||
|
||||
## 3. The Dev Shell
|
||||
|
||||
The shell itself is small:
|
||||
|
||||
```nix
|
||||
devShells.${system}.default = haskellPackages.shellFor {
|
||||
packages = hp: [ hp.mini-shellfor ];
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkgs.cabal-install
|
||||
pkgs.haskell-language-server
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
`packages = hp: [ hp.mini-shellfor ];` tells `shellFor` which Haskell package should drive the environment. `nativeBuildInputs` adds the interactive tools you still want on top.
|
||||
|
||||
---
|
||||
|
||||
## 4. Commands to Try
|
||||
|
||||
```bash
|
||||
cd 06-haskell-shellfor
|
||||
|
||||
nix develop
|
||||
cabal run
|
||||
cabal test
|
||||
|
||||
nix build
|
||||
./result/bin/mini-shellfor
|
||||
|
||||
nix run
|
||||
nix flake check
|
||||
```
|
||||
Loading…
x
Reference in New Issue
Block a user