Compile Ruby usando Nix
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á noshell.nix
de qualquer forma - Você não precisa usar o
configurePhase
, existe oconfigureFlags
(eu não testei, mas eu prefiro oconfigurePhase
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)