mirror of
https://codeberg.org/mhwombat/nix-book.git
synced 2026-01-06 05:55:09 +08:00
276 lines
8.1 KiB
Text
276 lines
8.1 KiB
Text
= Haskell
|
|
|
|
Start with an empty directory and create a git repository.
|
|
|
|
....
|
|
$ mkdir hello-haskell
|
|
$ cd hello-haskell
|
|
$ git init
|
|
....
|
|
|
|
== A simple Haskell program
|
|
|
|
Next, we'll create a simple Haskell program.
|
|
|
|
////
|
|
$ cp ../Main.hs .
|
|
////
|
|
|
|
[source,haskell,linenums]
|
|
.Main.hs
|
|
....
|
|
$# cat Main.hs
|
|
....
|
|
|
|
== Running the program manually (optional)
|
|
|
|
[NOTE]
|
|
====
|
|
In this section you will learn how to do some development tasks manually, the "hard" way.
|
|
This can help you understand the distinction between Nix's role and
|
|
the Haskell build system you've chosen.
|
|
Also, if you have a build problem but you're not sure if the fault is in
|
|
your flake definition or some other configuration file,
|
|
these commands can help you narrow it down.
|
|
But you may wish to skip to the <<_the_cabal_file,next section>>
|
|
and come back here later.
|
|
====
|
|
|
|
Before we package the program, let's verify that it runs. We're going to
|
|
need Haskell. By now you've probably figured out that we can write a
|
|
`flake.nix` and define a development shell that includes Haskell. We'll
|
|
do that shortly, but first I want to show you a handy shortcut. We can
|
|
launch a _temporary_ shell with any Nix packages we want. This is
|
|
convenient when you just want to try out some new software and you're
|
|
not sure if you'll use it again. It's also convenient when you're not
|
|
ready to write `flake.nix` (perhaps you're not sure what tools and
|
|
packages you need), and you want to experiment a bit first.
|
|
|
|
The command to enter a temporary shell is
|
|
|
|
`nix shell -p __installables__`
|
|
|
|
Where __installables__ are flakes and other types of packages that you need.
|
|
(You can learn more about these in the
|
|
https://nix.dev/manual/nix/stable/command-ref/new-cli/nix#installables[Nix manual].)
|
|
|
|
=== Some unsuitable shells
|
|
|
|
[NOTE]
|
|
====
|
|
In this section, we will try commands that fail in subtle ways.
|
|
Examining these failures will give you a much better understanding of Haskell development with Nix,
|
|
and help you avoid (or at least diagnose) similar problems in future.
|
|
If you're impatient, you can skip to the next section to see the right way to do it.
|
|
You can come back to this section later to learn more.
|
|
====
|
|
|
|
Let's enter a shell with the Glasgow Haskell Compiler ("ghc") and try to run the program.
|
|
|
|
....
|
|
$# echo '$ nix shell nixpkgs#ghc'
|
|
$# nix shell nixpkgs#ghc --command sh
|
|
$ runghc Main.hs # Fails
|
|
....
|
|
|
|
The error message tells us that we need the module `Network.HostName`.
|
|
That module is provided by the Haskell package called `hostname`.
|
|
Let's exit that shell and try again, this time adding the `hostname` package.
|
|
|
|
....
|
|
$# echo '$ exit'
|
|
$# echo '$ nix shell nixpkgs#ghc nixpkgs#hostname'
|
|
$# nix shell nixpkgs#ghc nixpkgs#hostname --command sh
|
|
$ runghc Main.hs # Fails
|
|
....
|
|
|
|
That reason that failed is that we asked for the wrong package.
|
|
The Nix package `hostname` isn't the Haskell package we wanted,
|
|
it's a different package entirely (an alias for `hostname-net-tools`.)
|
|
The package we want is in the _package set_ called `haskellPackages`, so we can refer to it as `haskellPackages.hostname`.
|
|
|
|
Let's try that again, with the correct package.
|
|
|
|
....
|
|
$# echo '$ exit'
|
|
$# echo '$ nix shell nixpkgs#ghc nixpkgs#haskellPackages.hostname'
|
|
$# nix shell nixpkgs#ghc nixpkgs#haskellPackages.hostname --command sh
|
|
$ runghc Main.hs # Fails
|
|
....
|
|
|
|
Now what's wrong?
|
|
The syntax we used in the `nix shell` command above is fine,
|
|
but it doesn't make the package _available to GHC_!
|
|
|
|
=== A suitable shell for a quick test
|
|
|
|
Now we will create a shell that can run the program.
|
|
When you need support for both a language and some of
|
|
its packages, it's best to use one of the Nix functions that are
|
|
specific to the programming language and build system.
|
|
For Haskell, we can use the `ghcWithPackages` function.
|
|
The command below is rather complex,
|
|
and a complete explanation would be rather lengthy.
|
|
|
|
[#haskell-nix-shell]
|
|
....
|
|
$# echo '$ nix shell --impure --expr '"'"'with import <nixpkgs> {}; haskellPackages.ghcWithPackages (p: [ p.hostname ])'"'"
|
|
$# nix shell --impure --expr 'with import <nixpkgs> {}; haskellPackages.ghcWithPackages (p: [ p.hostname ])' --command sh
|
|
$ runghc Main.hs
|
|
....
|
|
|
|
Success! Now we know the program works.
|
|
|
|
== The cabal file
|
|
|
|
It's time to write a Cabal file for this program.
|
|
This is just an ordinary Cabal file; we don't need to do anything special for Nix.
|
|
|
|
////
|
|
$ cp ../hello-flake-haskell.cabal .
|
|
////
|
|
|
|
[source,cabal,linenums]
|
|
.hello-flake-haskell.cabal
|
|
....
|
|
$# cat hello-flake-haskell.cabal
|
|
....
|
|
|
|
== Building the program manually (optional)
|
|
|
|
[NOTE]
|
|
====
|
|
In this section you will learn how to do some development tasks manually, the "hard" way.
|
|
This can help you understand the distinction between Nix's role and
|
|
the Haskell build system you've chosen.
|
|
Also, if you have a build problem but you're not sure if the fault is in
|
|
your flake definition or some other configuration file,
|
|
these commands can help you narrow it down.
|
|
But you may wish to skip to the <<haskell-flake,next section>>
|
|
and come back here later.
|
|
====
|
|
|
|
We won't write `flake.nix` just yet.
|
|
First we'll try building the package manually.
|
|
(If you didn't run the `nix shell` command from <<haskell-nix-shell,earlier>>, do so now.)
|
|
....
|
|
$ cabal build
|
|
....
|
|
|
|
The `cabal` command is provided by the `cabal-install` package.
|
|
The error happens because we don't have `cabal-install` available
|
|
in the temporary shell.
|
|
We can correct that.
|
|
|
|
....
|
|
$# echo '$ nix shell --impure --expr '"'"'with import <nixpkgs> {}; haskellPackages.ghcWithPackages (p: [ p.hostname p.cabal-install ])'"'"
|
|
$# nix shell --impure --expr 'with import <nixpkgs> {}; haskellPackages.ghcWithPackages (p: [ p.hostname p.cabal-install ])' --command sh
|
|
....
|
|
|
|
Note that we're now inside a temporary shell inside the previous
|
|
temporary shell! To get back to the original shell, we have to `exit`
|
|
twice.
|
|
Alternatively, we could have done `exit` followed by the
|
|
second `nix-shell` command.
|
|
|
|
....
|
|
$# echo '$ cabal build'
|
|
$# echo ". . ."
|
|
$# cabal build |& tail -n 4
|
|
....
|
|
|
|
After a lot of output messages, the build succeeds.
|
|
|
|
[#haskell-flake]
|
|
== The Nix flake
|
|
|
|
Now we should write `flake.nix`.
|
|
We already know how to write most of the flake from the examples we did earlier.
|
|
The two parts that will be different are the development shell and the package builder.
|
|
|
|
However, there's a _much_ simpler way, using `haskell-flake`.
|
|
The following command will create `flake.nix` based on their template.
|
|
|
|
....
|
|
$ nix flake init -t github:srid/haskell-flake
|
|
....
|
|
|
|
Examining the flake,
|
|
you'll notice that it is well-commented.
|
|
The only thing we need to change for now is the name in `packages.default`;
|
|
I've highlighted the change below.
|
|
|
|
[source,nix,linenums,highlight=55]
|
|
.flake.nix
|
|
....
|
|
$# sed -i 's/example/hello-flake-haskell/' flake.nix
|
|
$# cat flake.nix
|
|
....
|
|
|
|
We also need a `LICENSE` file.
|
|
For now, this can be empty.
|
|
|
|
....
|
|
$ touch LICENSE
|
|
....
|
|
|
|
== Building the program
|
|
|
|
Let's try out the new flake.
|
|
Nix flakes only "`see`" files that are
|
|
part of the repository. We need to add all of the important files to the
|
|
repo before building or running the flake.
|
|
|
|
....
|
|
$ git add flake.nix hello-flake-haskell.cabal LICENSE Main.hs
|
|
$ nix build
|
|
....
|
|
|
|
We'll deal with those warnings later.
|
|
The important thing for now is that the build succeeded.
|
|
|
|
== Running the program
|
|
|
|
Now we can run the program.
|
|
|
|
....
|
|
$ nix run
|
|
....
|
|
|
|
By the way, we didn't need to do `nix build` earlier.
|
|
The `nix run` command will first build the program for us if needed.
|
|
|
|
We'd like to share this package with others, but first we should do some
|
|
cleanup.
|
|
It's time to deal with those warnings.
|
|
When the package was built, Nix created a `flake.lock` file.
|
|
We need to add this to the repo, and commit all important files.
|
|
|
|
....
|
|
$ git add flake.lock
|
|
$ git commit -a -m 'initial commit'
|
|
....
|
|
|
|
You can test that your package is properly configured by going to
|
|
another directory and running it from there.
|
|
|
|
....
|
|
$ cd ..
|
|
$ nix run ./hello-haskell
|
|
....
|
|
|
|
If you move the project to a public repo, anyone can run it. Recall from
|
|
the beginning of the tutorial that you were able to run `hello-flake`
|
|
directly from my repo with the following command.
|
|
|
|
....
|
|
nix run "git+https://codeberg.org/mhwombat/hello-flake"
|
|
....
|
|
|
|
Modify the URL accordingly and invite someone else to run your new
|
|
Haskell flake.
|
|
|
|
////
|
|
Good adoc0 scripts clean up after themselves.
|
|
$ rm -rf hello-haskell # clean up
|
|
////
|