This commit is contained in:
parent
fbdbf67ce3
commit
ee90351c17
116
content/blog/crystal_nix.md
Normal file
116
content/blog/crystal_nix.md
Normal file
|
@ -0,0 +1,116 @@
|
|||
---
|
||||
title: Building a Basic Crystal Project with Nix
|
||||
date: 2020-02-16T14:31:42-08:00
|
||||
tags: ["Crystal", "Nix"]
|
||||
---
|
||||
I really like the idea of Nix: you can have reproducible builds, written more or less
|
||||
declaratively. I also really like the programming language [Crystal](https://crystal-lang.org/),
|
||||
which is a compiled Ruby derivative. Recently, I decided to try learn NixOS as a package author,
|
||||
and decided to make a Crystal project of mine, [pegasus](https://github.com/DanilaFe/pegasus),
|
||||
my guinea pig. In this post, I will document my experience setting up Nix with Crystal.
|
||||
|
||||
### Getting Started
|
||||
Pegasus is a rather simple package in terms of the build process - it has no dependencies, and
|
||||
can be built with nothing but a Crystal compiler. Thus, I didn't have to worry about
|
||||
dependencies. However, the `nixpkgs` repository does have a way to specify build dependencies
|
||||
for a Nix project: [`crystal2nix`](https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/compilers/crystal/crystal2nix.nix).
|
||||
|
||||
`crystal2nix` is another Nix package, which consists of a single Crystal binary program of
|
||||
the same name. It translates a `shards.lock` file, generated by Crystal's `shards` package
|
||||
manager, into a `shards.nix` file, which allows Nix to properly build the dependencies
|
||||
of a Crystal package. If you have a project with a `shards.lock` file, you can use `shards2nix`
|
||||
inside a `nix-shell` as follows:
|
||||
|
||||
```Bash
|
||||
nix-shell -p crystal2nix --run crystal2nix
|
||||
```
|
||||
|
||||
The above command says, create an environment with the `crystal2nix` package, and run the
|
||||
program. Note that you should run this
|
||||
[inside the project's root](https://github.com/NixOS/nixpkgs/blob/21bfc57dd9eb5c7c58b6ab0bfa707cbc7cf04e98/pkgs/development/compilers/crystal/build-package.nix#L2). Also note that if you
|
||||
don't depend on other Crystal packages, you will not have a `shards.lock`, and running
|
||||
`crystal2nix` is unnecessary.
|
||||
|
||||
The Crystal folder in the `nixpkgs` repository contains one more handy utility:
|
||||
`buildCrystalPackage`. This is a function exported by the `crystal` Nix package, which
|
||||
significantly simplifies the process of building a Crystal binary package. We can
|
||||
look to `crystal2nix.nix` (linked above) for a concrete example. We can observe the following
|
||||
attributes:
|
||||
|
||||
* `pname` - the name of the package.
|
||||
* `version` - the
|
||||
{{< sidenote "right" "version-note" "version" >}}
|
||||
In my example code, I set the Nix package version to the commit hash. Doing this alone
|
||||
is probably not the best idea, since it will prevent version numbers from being ordered.
|
||||
However, version <code>0.1.0</code> didn't make sense either, since the project technically
|
||||
doesn't have a release yet. You should set this to an actual package version if you have
|
||||
one.
|
||||
{{< /sidenote >}} of the package, as usual.
|
||||
* `crystalBinaries.<xxx>.src` - the source Crystal file for binary `xxx`.
|
||||
|
||||
Using these attributes, I concocted the following expression for pegasus and all
|
||||
of its included programs:
|
||||
|
||||
```nix
|
||||
{ stdenv, crystal, fetchFromGitHub }:
|
||||
|
||||
let
|
||||
version = "0489d47b191ecf8501787355b948801506e7c70f";
|
||||
src = fetchFromGitHub {
|
||||
owner = "DanilaFe";
|
||||
repo = "pegasus";
|
||||
rev = version;
|
||||
sha256 = "097m7l16byis07xlg97wn5hdsz9k6c3h1ybzd2i7xhkj24kx230s";
|
||||
};
|
||||
in
|
||||
crystal.buildCrystalPackage {
|
||||
pname = "pegasus";
|
||||
inherit version;
|
||||
inherit src;
|
||||
|
||||
crystalBinaries.pegasus.src = "src/pegasus.cr";
|
||||
crystalBinaries.pegasus-dot.src = "src/tools/dot/pegasus_dot.cr";
|
||||
crystalBinaries.pegasus-sim.src = "src/tools/sim/pegasus_sim.cr";
|
||||
crystalBinaries.pegasus-c.src = "src/generators/c/pegasus_c.cr";
|
||||
crystalBinaries.pegasus-csem.src = "src/generators/csem/pegasus_csem.cr";
|
||||
crystalBinaries.pegasus-crystal.src = "src/generators/crystal/pegasus_crystal.cr";
|
||||
crystalBinaries.pegasus-crystalsem.src = "src/generators/crystalsem/pegasus_crystalsem.cr";
|
||||
}
|
||||
```
|
||||
|
||||
Here, I used Nix's `fetchFromGitHub` helper function. It clones a Git repository
|
||||
from `https://github.com/<owner>/<repo>`, checks out the `rev` commit or branch,
|
||||
and makes sure that it matches the `sha256` hash. The hash check is required so
|
||||
that Nix can maintain the reproducibility of the build: if the commit is changed,
|
||||
the code to compile may not be the same, and thus, the package would be different. The
|
||||
hash helps detect such changes. To generate the hash, I used `nix-prefetch-git`,
|
||||
which tries to clone the repository and compute its hash.
|
||||
|
||||
In the case that your project has a `shards.nix` file generated as above, you will also
|
||||
need to add the following line inside your `buildCrystalPackage` call:
|
||||
|
||||
```
|
||||
shardsFile = ./shards.nix;
|
||||
```
|
||||
|
||||
The `shards.nix` file will contain all the dependency Git repositories, and the
|
||||
`shardsFile` attribute will forward this list to `buildCrystalPackage`, which
|
||||
will handle their inclusion in the package build.
|
||||
|
||||
That's pretty much it! The `buildCrystalPackage` Nix function does most of the heavy
|
||||
lifting for Crystal binary packages. Please also check out
|
||||
[this web page](https://edef.eu/~qyliss/nixlib/file/nixpkgs/doc/languages-frameworks/crystal.section.md.html):
|
||||
I found out from it that `pname` had to be used instead of `name`, and it also has some information
|
||||
regarding additional compiler options and build inputs.
|
||||
|
||||
### Appendix: A Small Caveat
|
||||
I was running the `crystal2nix` (and doing all of my Nix-related work) in a NixOS virtual
|
||||
machine. However, my version of NixOS was somewhat out of date (`19.04`), and I could
|
||||
not retrieve `crystal2nix`. I had to switch channels to `nixos-19.09`, which is the current
|
||||
stable version of NixOS.
|
||||
|
||||
There was one more difficulty involved in
|
||||
[switching channels](https://nixos.wiki/wiki/Nix_channels): I had to do it as root.
|
||||
It so happens that if you add a channel as non-root user, your system will still use
|
||||
the channel specified by root, and thus, you will experience the update. You can spot
|
||||
this issue in the output of `nix-env -u`; it will complain of duplicate packages.
|
Loading…
Reference in New Issue
Block a user