Using Nix to manage multiple Ruby versions
What is Nix
Nix is many things, fundamentally it's a build tool and a language: as a build tool it focuses on purity and predictability; as a language it focuses on being declarative; more generally it is a package manager and an operating system. Today I'm going to be talking about using it as a package manager.
The ruby version problem
Having multiple projects that run multiple different versions of Ruby can be a problem, and isn't unique to just Ruby. The reason it's a problem is that each version of the language adds new features or deprecates methods people use. If we have a single version of Ruby installed on a development machine, we might be using a different version of Ruby than our project will use in production. This problem is so common that a number of tools have been created to deal with it such as RBenv
and RVM
. However, Nix provides us with a new alternative solution.
How Nix solves it
Nix gives us a way to create separate isolated development environments. This means I don't have any version of Ruby globally installed on my machine. Instead each and every Ruby project contains a default.nix
file in it's root, which defines all of the dependencies that project needs to run.
Here is one such example:
let
pkgs = import <nixpkgs> {};
stdenv = pkgs.stdenv;
ruby = pkgs.ruby_2_2_3;
rubygems = (pkgs.rubygems.override { ruby = ruby; });
in stdenv.mkDerivation rec {
name = "mortgages";
buildInputs = [
ruby
pkgs.libxml2
pkgs.libxslt
pkgs.zlib
pkgs.bzip2
pkgs.openssl
pkgs.mysql
pkgs.libmysql
pkgs.imagemagickBig
pkgs.pkgconfig
];
shellHook = ''
export PKG_CONFIG_PATH=${pkgs.libxml2}/lib/pkgconfig:${pkgs.libxslt}/lib/pkgconfig:${pkgs.zlib}/lib/pkgconfig:${pkgs.mysql}/lib/pkgconfig:${pkgs.imagemagickBig}/lib/pkgconfig
export C_INCLUDE_PATH=${pkgs.libmysql}/include/mysql
mkdir -p .nix-gems
export GEM_HOME=$PWD/.nix-gems
export GEM_PATH=$GEM_HOME
export PATH=$GEM_HOME/bin:$PATH
'';
}
In this file not only do we specify the Ruby version (v2.2.3) that the project requires to run, but we also specify all of the native dependencies that some of our Ruby Gems require to run.
Inside the let
we are simply defining some aliases and then overriding the rubygems
package to ensure we're using the same ruby version everywhere. Inside mkDerivation
is where we start to build our development environment, name
is the name of the project we're inside, buildInputs
is where we define the system dependencies.
shellHook
is basically a shell script that will run at the very end where we can do whatever we want. We have to do a couple of things using shell because of how isolated Nix makes everything by default. Installing dependencies such as libxml2
doesn't make it available and easy to find by the Gems that require it, so we set PKG_CONFIG_PATH
to make this possible. The other thing we do is create a new directory and set environment variables to change where Gems are installed. We do this to preserve isolation of projects, otherwise Gems might get installed into a shared location which could cause unexpected behaviour.
In Action
Globally I don't have Ruby installed:
[~/code/mortgages] $ ruby --version
The program ‘ruby’ is currently not installed. It is provided by
several packages. You can install it by typing one of the following:
nix-env -iA nixos.ruby
nix-env -iA nixos.ruby_1_9
nix-env -iA nixos.ruby_2_0
nix-env -iA nixos.ruby_2_1
nix-env -iA nixos.ruby_2_2
However once inside a project I can create a subshell with the environment setup correctly by running nix-shell
:
[~/code/mortgages] $ nix-shell default.nix
[~/code/mortgages] $ ruby --version
ruby 2.2.3p172 (2015-08-18) [x86_64-linux]
If you're interested in trying Nix out for yourself you can install it on any operating system, Linux is clearly where that will be easiest but I've been told it will still work on OSX and even Windows with Cygwin, but you'll probably have more issues. You can install Nix along side any other package manager and you can easily delete it if it's not for you. You can also go all in with the entire operating system NixOS.