Hi all! Though I am happily running NixOS for all my general computing stuff, I’m just getting started with nix-shell for local isolated development environments. Somehow it feels like I am already using it wrong, so here we go:
Say I want to have an isolated shell environment for a Ruby project. From what I got in the documentation, I would start out with a simple shell.nix like
which happens to give me (at the point of this writing) a Ruby 2.7.5p203.
But how would I go about and really pin the specific Ruby version I want, e.g. 2.7.4 or even an older 2.6.10? I found Hydra - nixos:release-21.11:nixpkgs.ruby_2_7.x86_64-linux - which seems to be job that builds the packages, but how would that translate to my shell.nix?
I found an old forum post – which goes into that direction, but it looks odd, since it looks like they build Ruby from source again
It’s not so much that I need a specific old version, but I want to have a consistent version for everyone on the project.
By just specifying ruby_2_7 it could mean 2.7.3, which is already a year old or 2.7.6 which was released last week. So if I forget to run a nix-channel --update before entering nix-shell I could end up with a different patch version than a fellow team member. To reproduce failures, especially with third-party libraries I would say the patch version differences are meaningful.
With the current setup I could not really rely on two team members having the exact same version, which feels odd.
You could probably checkout old nixpkgs release if you are desperate for that specific version.
I want to have a consistent version for everyone on the project. (…) To reproduce failures, especially with third-party libraries I would say the patch version differences are meaningful.
There are a couple of alternatives. With ruby_2_7_4 = import (fetchTarball ..., while the ruby version is now fixed, different people on the project might still have different versions of, say, zlib. You could even get into situations where ruby was built with a different version of zlib than your project - which might lead to problems in some cases.
One solution is to avoid import <nixpkgs> {} entirely and take everything from that import (fetchTarball .... That locks things down further, and requires you to update the nixpkgs commit hash in the shell.nix each time you want to update the environment. This could be an advantage, but is also rather rigid (and leads to merge conflicts if you do it a lot - which also might or might not be an advantage depending on your viewpoint ).
Yet another approach, one that I like, is:
use ruby_2_7
document that your project is intended to build with the nixos-unstable channel (or whichever channel you choose)
log the specific commit of the channel when building
That way, your project ‘naturally’ follows the channel progression. Different people on the project might have slightly different versions based on how up-to-date their channel is, but if you need to reproduce problems you can always ask ‘what commit of nixpkgs did you build with’ and get exactly the same environment. If it’s a problem caused by a change in nixpkgs, you can even ‘git bisect’ nixpkgs to find out when/where it was introduced!
@raboof thank you very much for the detailed answer!
I think for now I will try to remove the import <nixpkgs> {} completely and just pin down what I can. That way the shell.nix feels a lot more like a “lock” file for all the dependencies needed and it should work well for smaller teams. Maybe we would then just pull from github.com/NixOS/nixpkgs into our own repository and push our own tags, e.g. 2022-04-25, so it’s easier to see how far you’re behind and you might end up with a conflict, but at least you don’t have to diff checksums.
But your second approach – basically documenting the requirements and letting people roll with whatever comes downstream – feels more natural to NixOS. I’ll have to think about it. The list of channels is global though, so they would need to add it to their system via nix-channel --add before entering the shell, no?