guix crash course

img
Guix reveals as a practical means of handling dependencies. However, the amount of information available to start using it may appear as a bit overwhelming for a beginner, letting the feeling of a tool reserved to a reduced community or experts. Far from that. Here you’ll find everything you need to get started with guix, with a light touch on using it for #modernhw.
We will concentrate in the use of #guix as an external package manager on top of a #linux distribution based on #systemd. We’ll let aside using and referring to #guixsystem as a full operating system by itself (which I never used anyway). This way, in the context of #modernhw, we may keep on using our favorite tools, environment and workflow. As an addition, we have everything that guix provides at our disposal, without affecting our local packages configuration: guix acts as an extra layer on top of our current OS, without any interference with it. You’ll have the possibility to install any guix software, remove it afterward or make use of the fancy features guix has to offer, without your host OS ever noticing what’s going on.
All what follows is roughly based on the guix reference manual and the guix cookbook, so refer to them for more on depth explanations. This article is strongly influenced by my personal experience as a daily driver, so next topics are necessarily biased towards my own needs.
There is much more to say about guix, but this is just an introductory crash course, right ?

install

First things first. You need to be root to proceed to a binary guix installation. Just download the installer, and follow the instructions.
After that, you’ll be using guix as a regular user, and all what follows must be run without any special rights beyond accessing to your home directory. Behind the curtains, guix computes what’s necessary through the running guix daemon, handled by your host’s systemd.

packages

Packages are built definitions. At no surprise, they are installed (or removed) with

guix search synthesis
guix install yosys
guix remove python

Definitions, in turn, are guile descriptions on how and where to obtain code source, a precise and unambiguous reference to it, how to process and how to install it, along with the necessary input dependencies, its kind, and how to use them. Definitions may be seen as customized default build templates, which avoids complicated package definitions, simplifying its design. Thus, a default build template called python-build-system exist, for example, for producing python packages. A package definition customizes the way this template is used, modifying its default package, source, etc. fields.
Definitions are built on isolated, minimalistic environments. Once built, packages are deposit in the guix store under /gnu/store. Each package is given a unique hash: changing the definition, or any of its inputs, produces a different package and hash. This is what usually is referred to as functional package management of #dependencies.
A package may have multiple outputs (out, by default, but also doc, etc.). Packages are built locally following the package definition. To avoid long run times and wasting cpu cycles, guix introduces substitutes or pre-built packages available on remote (substitute) servers. When available, substitutes are downloaded, which avoids having to built packages locally. Otherwise, your local computing resources will be put to contribution, which is far from ideal, so better configure your substitute servers before anything else (check your systemd guix-daemon file). It is possible to verify substitute availability with

guix weather ghdl-clang
  https://guix.bordeaux.inria.fr ☀
--> 100.0 % des substituts sont disponibles (1 sur 1)  <--
    6,2 Mio de fichiers nar (compressés)
    39,6 Mio sur le disque (décompressé)
    0,777 secondes par requête (0,8 secondes en tout)
    1,3 requêtes par seconde

It is crucial to understand that a given package built will be identical to any other build of this same package, regardless of the host computer, which is what holds the validity of the very idea of substitutes, and guarantees #reproducibility. This holds for any guix construction, including shell containers (see below).
Keep that in mind.

profiles and generations

After first install, guix will create on your behalf a default profile under ~/.guix-profile. All operations (install, remove) will affect this profile, unless you decide to point somewhere else (with the modifier -p $GUIX_PROFILE).

guix package -p $GUIX_PROFILE --list-installed
coreutils       9.1     out     /gnu/store/fk39d3y3zyr6ajyzy8d6ghd0sj524cs5-coreutils-9.1
git             2.46.0  out     /gnu/store/wyhw9f49kvc7qvbsbfgm09lj0cpz1wlb-git-2.46.0
fw-open-logic   3.0.1   out     /gnu/store/hrgdvswmvqcyai4pqmr7df0kpyyak94j-fw-open-logic-3.0.1
osvvm-scripts   2024.09 out     /gnu/store/xhxr3y1k8838my6mfk992kn392pwszjm-osvvm-scripts-2024.09
osvvm-uart      2024.09 out     /gnu/store/x3pjf95h8p3mbcx4zxb6948xfq3y3vg8-osvvm-uart-2024.09
fd              9.0.0   out     /gnu/store/nx0hz1y3g7iyi4snyza7rl5600z73xyn-fd-9.0.0
make            4.4.1   out     /gnu/store/963iman5zw7zdf128mqhklihvjh6habm-make-4.4.1
tcllib          1.19    out     /gnu/store/443vgrmwac1mvipyhin5jblsml9lplxf-tcllib-1.19
tcl             8.6.12  out     /gnu/store/w2icygvc0h294bzak0dyfafq649sdqvn-tcl-8.6.12
ghdl-clang      4.1.0   out     /gnu/store/sy0ryysxwbkzj6gpfka20fs27knmgmkd-ghdl-clang-4.1.0

Each profile generation will consist on a set of symbolic links pointing to /gnu/store. A new generation is produced when you install or remove something. This will only redefine your profile’s links, and so the status of the profile (and the packages you have access to). Generations are roughly the equivalent of #git commits, if this helps. They are nothing but collections of links pointing to the store, where packages are installed. Each collection defines a generation and so the current status of a guix profile.
You may roll back to previous generations, or move forward, but only linear generation histories are allowed. In you go back n generations, and then create a new one, your previous history is lost.

guix package -p $GUIX_PROFILE --list-generations

In addition to creating links, a profile redefines the environment variables (following the profle contents), appending, prepending or replacing the current ones. This way, the user enters an augmented context, having access to the packages in the profile.
Note that, inside a profile, the user still have access to the external system: for example, the PATH env variable is augmented with the profile bin directory, but former binaries are still there. To get a higher degree of isolation, we need shell containers (see below).

clean

From time to time, don’t forget to clean the store, removing stuff no profile and generation is pointing to, with

guix gc

Before that, remove old generations from your profiles, unless you plan to make use of them at some point.

upgrade

Upgrade guix current profile with

guix pull && guix upgrade

This will create a new generation in your default profile, including updates to all your packages in current profile. Pulling syncs local guix with remote guix repository, fetching updates locally. Upgrade will deploy these updates to your profile.
Remember also to

sudo -i guix pull
sudo systemctl daemon-reload
sudo systemctl restart guix-daemon

to upgrade the system daemon, so that it is never too delayed with respect to your guix in use.

manifest, channels

If not already clear from the previous, remember that it is possible to replicate environments (contexts, profiles, dependencies) using a couple of #plaintext files.
First, the #manifest.scm, which includes the list of packages in the environment. As an example, export your current profile with

guix package -p $GUIX_PROFILE --export-manifest > manifest.scm

Put it somewhere under version control, and replicate your environment somewhere else with

guix package -p $GUIX_PROFILE -m manifest.scm

That’s all it takes to get exactly the same development context in another host, for example.
But you’re right, I see you follow. This is not enough. You also need to freeze which guix version you’re using (guix, as any other package manager, not always installs the same version of some package). You need also a #channels.scm file. It may be produced with

guix describe --format=channels -p $GUIX_PROFILE > channels.scm

and includes the list of channels in use, along with a hash to identify which version of the channels to use, among the whole history of channel revisions (the #git commit of the channel repository). Then, import it somewhere else with

guix pull -C channels.scm

examples

Fixing your channels, its revision and the list of packages is all you need to eliminate any ambiguity, achieving #reproducibility and #determinism. Remember that this is the best advantage of guix, after all. Say you publish something (report, article, paper, blog ticket). If you provide a git repository with these two files, anyone else will be
able, hopefully, to replicate your asserts.
As a typical example, when you have a complex #vhdl design, including a large set of dependencies, you need a means to handle them. Here, we assume the #dependencies may be vhdl compilers, tcl shells, python interpreters, unit testing libraries, verification frameworks. etc. ... but also other vhdl modules, each in its own git repository.
At this point, you’ll need, first, to fix your channels.scm to “freeze” the repositories status. Here we are using, for example, the electronics, guix-science and guix channels, each in a fix release given by a commit hash.

(list (channel
       (name 'electronics)
       (url "https://git.sr.ht/~csantosb/guix.channel-electronics")
       (branch "main")
       (commit
        "2cad57b4bb35cc9250a7391d879345b75af4ee0a")
       (introduction
        (make-channel-introduction
         "ba1a85b31202a711d3e3ed2f4adca6743e0ecce2"
         (openpgp-fingerprint
          "DA15 A1FC 975E 5AA4 0B07  EF76 F1B4 CAD1 F94E E99A"))))
      (channel
       (name 'guix-science)
       (url "https://codeberg.org/guix-science/guix-science.git")
       (branch "master")
       (commit
        "1ced1b3b913b181e274ca7ed2239d6661c5154c9")
       (introduction
        (make-channel-introduction
         "b1fe5aaff3ab48e798a4cce02f0212bc91f423dc"
         (openpgp-fingerprint
          "CA4F 8CF4 37D7 478F DA05  5FD4 4213 7701 1A37 8446"))))
      (channel
       (name 'guix)
       (url "https://git.savannah.gnu.org/git/guix.git")
       (branch "master")
       (commit
        "3e2442de5268782213b04048463fcbc5d76accd7")
       (introduction
        (make-channel-introduction
         "9edb3f66fd807b096b48283debdcddccfea34bad"
         (openpgp-fingerprint
          "BBB0 2DDF 2CEA F6A8 0D1D  E643 A2A0 6DF2 A33A 54FA")))))

Then, you need the list of dependencies necessary to your design. These are provided in a manifest.scm file, as for example in

(specifications->manifest
 (list "ghdl-clang"
       "tcl"
       "tcllib"
       "make"
       "python-vunit"
       "osvvm-uart"
       "osvvm-scripts"
       "fw-open-logic"
       "git"
       "which"
       "findutils"
       "coreutils"))

One may include these two files in the design, in a different testing #git branch, for example. Then, all it takes to run your design in a reproducible way is cloning the design git repository, checking out the testing branch, and running #guix time machine (see next) to replicate a local profile containing all the design’s dependencies. Remember that here we include also all third party firmware modules instantiated in our design.

time machine

How guix guarantees that it is possible to reproduce a profile in the future ? The trick consist on asking current guix version to call a previous guix version (the one we define), to deploy the profile with the packages we need.
For example: let's ask guix-5 to make use of guix-4 to install emacs-30 package, which is only available in the guix-4 repositories, whereas guix-5 only provides emacs-32.
This mechanism is called time-machine. It is used as, for example:

guix time-machine --channels=channels.scm -- package -p $GUIX_PROFILE -m manifest.scm

Here, up-to-date guix uses time machine to roll back to the former guix version defined in channels.scm. Then, former guix calls the package command to install under $GUIX_PROFILE the list of packages defined in the manifest.scm file.
What’s important to understand here is that this will produce exactly the same output regardless of the host and the point in time when we run this command. The profile we produce is always the same, by design. And this is what is relevant for #modernhw.

shell containers

Guix includes a command to create independent environments from the rest of our host system. This provides an increased degree of isolation when compared to profiles, as the later lie on top of, and only augment, our current shell. Shell containers create a new, almost empty by default, minimalistic context for us to install packages.

guix shell --container --link-profile --emulate-fhs coreutils which python-vunit osvvm-uart
guix shell --container --link-profile --emulate-fhs -m manifest.scm

or, if one needs determinism

guix time-machine --channels=channels.scm -- shell --container --link-profile --emulate-fhs -m manifest.scm

The --link-profile flag will link the contents of $GUIX_PROFILE under /gnu/store to ~/.guix-profile.
The --emulate-fs will, well, reproduce the standard file system under /, as some packages expect this layout and fail otherwise.
coreutils and which packages will be helpful, otherwise, not even ls command is present within the container. Minimalistic, I said. I should have use isolated instead.

packs

Great. But. What if guix is not around ? How do I use it in a cluster, or in another host where guix is not yet available ? How do I distribute my dependencies, environments, etc. to a non-yet-guix-user ? No problem, guix pack is intended to be used as a simple way of “packaging” guix contexts (understood as a set of packages), deploying them afterward in a target host. This is next step after profiles and shell containers.
Guix pack comes equipped with several different backends, producing contexts in the most habitual and useful formats. For example, the following command will pack #emacs, #ghdl and #yosys for you to use where you need it.

guix pack -f docker emacs ghdl-clang yosys

In the context of #modernhw, #docker images may be used for #ci tests, uploading the image to a remote #gitforge registry; #apptainer containers can be sent and run in a #hpc cluster; .tar.gz compresses files are a clean may of installing non-existing software in a remote machine. Furthermore, one has the possibility of packaging all the project #dependencies in a manifest.scm file, and distribute it along with the source code to anyone willing to use it. No instructions about the proper environment to run the project, no complicated installation of dependencies. Stop asking third party users in your README to handle your dependencies for you.
A simple docker pull pointing to a #forge image repository is enough when guix is not locally available. Long run times ? Use a #forge #ci custom runner in your own hardware with your #singularity image. Remote work to an #ssh server with obsolete software ? Pack, send and untar your favorite development tools and create a custom profile, no admin rights needed. The possibilities are endless.
And most important: the advantage of this approach over a classical docker or singularity files for producing the images is #reproducibility: every single time you build the image, you’ll get the exact same binary product. Use the --save-provenance flag to store in the image itself the manifest you used to create it.
Good luck trying to achieve the same with a docker file.

importing

Now, guix is not a universal tool for installing anything around. What about this obscure #python package no one uses but you ? You’d absolutely need this #emacs package you just found on #codeberg which guix doesn’t provide. Rust, go ... There are plenty of pieces of code around not being yet packaged along with guix. No problem. Guix incorporates a simple and elegant way of extending the amount of packages you’ll be able to install. Just import them.

guix import pypi itsdangerous
guix import crate becareful
guix import gem wow

Previous commands will issue a new package definition corresponding to a package already handled by the language own package manager. #Dependencies, you say ? Use the --recursive flag. Once you have the definition, you’ll be able to build, install and use the corresponding package.
Check in the documentation the surprising amount of backends available, you’ll be gratefully surprised.

software heritage

Last, but not least.
You have guix, its package definitions and all the fancy tools which come along. But. What if you don’t have access to the source code ? In this case, all the previous becomes meaningless: remember that guix is a fully bootstrapped distribution, being built from the very bottom up. Building a package from its source means having access to the source, which most of the time is hosted in a #gitforge. But #forges disappear, especially proprietary ones, repositories relocate or are just obsoleted and get replaced.
In this case, guix gets you covered by falling back to Software Heritage (#SH). This initiative, with support of the UNESCO, collects, preserves, and shares the source code of all software that is publicly available, including its full development history. The collection is trigger automatically by a crawler, manually with a browser plugin or by guix itself when developers lint package definitions.
If, in ten years, you try to replicate one of your papers, and you plan to recreate your environment to run your code and reproduce your plots, you won’t be bothered by nowadays python 3.10 having being obsoleted, abandon and buried in history of computers by #guile. SH keeps a copy for you to sleep better at night.

channels

Channels, as a feature to extend guix repository of definitions, deserve its own chapter.