Guix/Nix Reproducible Environments
Distinct patterns for reproducibility#
Guix and Nix use very different patterns for ensuring your build environments are exactly reproduced. Both systems use the concept of a “channel” that contains the Nix or Scheme code for its packages and services. When you ask the nix/guix package manager to install something it uses its list of channels at the versions it has checked out. So the same code could produce different environments on the same system if they don’t have the same set of channels and the same versions of those channels checked out. Updating your system involves updating your channels and then asking your system to check and build updates. When guix forked from Nix one of the things they addressed was this and so they have a time-machine you can use to create the exact version of guix used to build the original environment, which then will build that original environment. Nix doesn’t have a way of dealing with this unless you use the experimental flakes feature, which instead describes the channel dependencies and package build together. This means that with guix, if you want reproducible builds, you’ll be using at least two scheme files while in Nix it could all be in just one.
The guix time-machine#
In guix you use a channel configuration to run the time machine to create the guix used. Then you use it to do what you were going to do. You can just write out the scheme for the channel configuration or you can use guix describe to get the current set of channels and possibly edit it further. The describe command will output the scheme if you give it the -f channels argument. Then you can output this to a channels.scm (name doesn’t matter, this is convention):
$ guix describe -f channels > channels.scm
Now you can tell the time machine to use that set of channels with -C channels.scm in its command arguments. To run the time machine you use the time-machine command followed by its arguments, followed by --, followed by your original command. So if you wanted to do a build based on the channels in the file you just created you’d do this:
$ guix time-machine -C channels.scm -- build
If you want to conceptualize this as the scheme it produces then that’s something like:
(guix build)
; vs.
((guix time-machine #:channels channels.scm) build)
Nix flakes#
In nix you describe the “channels” as inputs. The “channels” are not channels anymore though, they are flakes–a new, but similar concept. Flakes actually end up in the store themselves and are considered immutable once used in a build. Any edit to the flake repository creates a new flake in the store. These flakes can then be bundled together as “inputs” in other flakes. If there are shared dependencies then you have to map out how those dependencies map–if left alone each dependency will have its own independent dependency to the shared dependency and might not share the same version, which can be problematic. You change that by setting the other dependency’s dependency to “follow”. For example, if you want to use the devshell flake with a nixpkgs that is not whatever is on the system (usually the case) then you have to set up a follow or it won’t work correctly:
{
description = "example flake"
inputs = {
nixpkgs.url = "github.com:NixOS/nixpkgs/nixpkgs-unstable"; # tag most people follow
divshell = {
url = "github.com:numtide/devshell"; # master/main branch default
inputs.nixpkgs.follows = "nixpkgs";
};
};
oututs = ...;
}
Since this is built into the flake command itself you don’t need to do anything extra. Nix doesn’t have a time machine and you don’t need one if you use flakes. To build the current flake you’d do something like:
$ nix build
Which is better#
I don’t know that either is better or worse. However, the separation of concerns in guix does mean you can maintain a set of standard channels for all of your projects and then use them easily. With the Nix version you’re either maintaining an independent set of inputs and then trying to integrate, or you are writing one large, hopefully modularized flake. I just get caught in a choice loop here because both have their pluses and minuses. It’s going to depend on what I’m doing. I tend to be someone that uses fine-grained encapsulation, which would fit the nix pattern better. I can see why the guix version could be just easier and produce a more standard output.