Nota: Este é um dos jeitos de resolver um problema meu, tenho certeza que não é o único.

Nix é um ótimo gerenciador de pacotes. Ponto. Eu uso ele para quase tudo ao invés do pacman e do docker.

O único problema até agora é que enquanto o docker mantém todas as versões, o nix não.

O maior exemplo é para as versões do Ruby. Eu pensei que isso serial um problema apenas para versões não mais suportadas pelo core team (por exemplo: 1.9.3), mas recentemente eu me deparei com um problema que removeu a versão 2.7.1 (a versão 2.7.2 tinha acabado de ser lançada). Claro que todo projeto usando 2.7.2 é compatível com a 2.7.3, mas é chato ter que mudar o Gemfile e voltar.

Eu tive que correr para compilar a versão do Ruby eu mesmo. Enquanto que isso não é um problema grande eu tive que investir algum tempo compilando o meu ruby para corrigir meu ambiente. Não exatamente uma build reproducível se você tiver que correr para corrigir.

Depois de 3 ou 4 tentativas de compilar usando o nix ao invés de eu mesmo (ou uma ferramenta como rbenv/rvm/chruby) eu finalmente achei um jeito.

Primeiramente: como é o nix para um ambiente de desenvolvimento?

Eu não instalo todas as ferramentas que preciso na minha máquina como generation (i. e., instalar e deixar disponível para todas as minhas sessões do meu shell). Ao invés disso eu tenho um arquivo shell.nix como este (versão simplificada):

with import <nixpkgs> {};

mkShell {
  buildInputs = [
    ruby_2_7
    readline
  ];

  shellHook = ''
    # Opcional
    export GEM_HOME=/some/path/ruby27
    mkdir -p $GEM_HOME
    export GEM_PATH=$GEM_HOME
    export PATH=$GEM_HOME/bin:$PATH

    gem list -i ^bundler$ -v 2.2.22 || gem install bundler --version=2.2.22 --no-document
    export SOME_ENV=xxx
  '';
}

E para ativar eu apenas preciso chamar esse comando:

$ nix-shell
true

[nix-shell:~/Dev/rubynz/membership-register]$

Ou, se você como eu usa o zsh:

$ nix-shell --command zsh
true
[nix-shell]$

Mas o que acontece quando você, tal qual eu, precisa de uma versão anterior da branch 2.7.x?

Compile o Ruby usando nix

O jeito mais fácil é basicamente mudar o shell.nix para algo como:

with import <nixpkgs> {};

mkShell {
  buildInputs = [
    (pkgs.callPackage ./ruby272.nix {}) # esta linha que é diferente
    readline
  ];

  shellHook = ''
    # Opcional
    export GEM_HOME=/some/path/ruby272
    mkdir -p $GEM_HOME
    export GEM_PATH=$GEM_HOME
    export PATH=$GEM_HOME/bin:$PATH

    gem list -i ^bundler$ -v 2.2.22 || gem install bundler --version=2.2.22 --no-document
    export SOME_ENV=xxx
  '';
}

E o arquivo ruby272.nix se parece com:

{ pkgs ? import <nixpkgs> {} }:
with import <nixpkgs> {};

pkgs.stdenv.mkDerivation {
  name = "ruby272";
  version = "2.7.2";

  src = pkgs.fetchurl {
    url = "https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.2.tar.gz";
    sha256 = "6e5706d0d4ee4e1e2f883db9d768586b4d06567debea353c796ec45e8321c3d4";
  };

  phases = [
    "unpackPhase"
    "configurePhase"
    "buildPhase"
    "installPhase"
  ];

  buildInputs = [
    openssl
    zlib
    readline
    gdbm
  ];

  unpackPhase = ''
    tar xfz $src -C /build
  '';

  configurePhase = ''
    cd ruby-$version
    ./configure --prefix=$out --disable-install-doc --disable-install-rdoc
  '';

  buildPhase = ''
    make
  '';

  installPhase = ''
    make install
  '';
}

Algumas notas:

  • Você pode dar o nome que quiser (name = "ruby272";). Mas eu prefiro evitar _ para que eu não confunda esse com um pacote dos repositórios oficiais
  • Até onde testei o version é opcional, mas torna a vida mais fácil depois no mesmo script
  • Você pode fazer uma pré-compilação usando nix-build ruby272.nix, mas ele já está no shell.nix de qualquer forma
  • Você não precisa usar o configurePhase, existe o configureFlags (eu não testei, mas eu prefiro o configurePhase de qualquer forma)
  • O download do tgz é feito e cacheado pelo próprio nix 😉
  • Claro que você notou, mas você pode customizar qualquer etapa, por exemplo para instalar gems por padrão apenas mude o installPhase
  • A compilação é cacheada entre projetos (i. e., você pode ter tantos ruby272.nix quanto quiser que o nix só vai compilar uma vez)