Using bundlerEnv with non-default version of Ruby (v2.5)

I’m trying to setup nix-shell for a simple rails project. I’ve generated a gemset.nix file using bundix and I’m using the following shell.nix specifying a non-default version of Ruby, i.e. v2.5 vs v2.6.

with (import <nixpkgs> {});
let
  env = bundlerEnv {
    name = "site-bundler-env";
    inherit ruby_2_5;
    gemdir = ./.;
  };
in stdenv.mkDerivation {
  name = "site";
  buildInputs = [ env ruby_2_5 ];
}

When I enter the nix-shell, although the correct version of Ruby (v2.5) is made available, it looks as if the gems are installed against the default version of Ruby (i.e. v2.6).

The results of the following commands are as I would expect, i.e. pointing at Ruby v2.5:

$ ruby --version
ruby 2.5.8p224 (2020-03-31) [x86_64-darwin17]

$ which ruby
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/bin/ruby

$ echo $GEM_PATH
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/lib/ruby/gems/2.5.0

$ ruby -e 'puts $LOAD_PATH'
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/lib/ruby/site_ruby
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/lib/ruby/site_ruby/2.5.0
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/lib/ruby/site_ruby
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/lib/ruby/site_ruby/2.5.0
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/lib/ruby/site_ruby/2.5.0
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/lib/ruby/site_ruby/2.5.0/x86_64-darwin17
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/lib/ruby/site_ruby
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/lib/ruby/vendor_ruby/2.5.0
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/lib/ruby/vendor_ruby/2.5.0/x86_64-darwin17
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/lib/ruby/vendor_ruby
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/lib/ruby/2.5.0
/nix/store/2kg3mrk7101ghazdpriacfm8lyqxmq1q-ruby-2.5.8/lib/ruby/2.5.0/x86_64-darwin17

However, the following results surprised me because they are pointing at Ruby v2.6:

$ which bundle
/nix/store/inkl1lx0cp2056yk1ymcg9wr7niliwdp-site-bundler-env/bin/bundle

$ cat `which bundle` | grep GEM_HOME
Gem.paths = { 'GEM_HOME' => "/nix/store/inkl1lx0cp2056yk1ymcg9wr7niliwdp-site-bundler-env/lib/ruby/gems/2.6.0" }

$ cat `which bundle` | grep LOAD_PATH
$LOAD_PATH.unshift "/nix/store/dr1lkwz7q2shjj83nms7jw3g8z1r2vy6-bundler-2.1.4/lib/ruby/gems/2.6.0/gems/bundler-2.1.4/lib"

And when I try to run the tests (using rake) or run the server (using rails server), I see an exception “Your Ruby version is 2.6.6, but your Gemfile specified 2.5.8”:

$ rake
Traceback (most recent call last):
	3: from /nix/store/pda1qki1ff40d4yy1jjkzz71204mr8fq-site-bundler-env/bin/rake:18:in `<main>'
	2: from /nix/store/dr1lkwz7q2shjj83nms7jw3g8z1r2vy6-bundler-2.1.4/lib/ruby/gems/2.6.0/gems/bundler-2.1.4/lib/bundler.rb:143:in `setup'
	1: from /nix/store/dr1lkwz7q2shjj83nms7jw3g8z1r2vy6-bundler-2.1.4/lib/ruby/gems/2.6.0/gems/bundler-2.1.4/lib/bundler/definition.rb:470:in `validate_runtime!'
/nix/store/dr1lkwz7q2shjj83nms7jw3g8z1r2vy6-bundler-2.1.4/lib/ruby/gems/2.6.0/gems/bundler-2.1.4/lib/bundler/definition.rb:495:in `validate_ruby!': Your Ruby version is 2.6.6, but your Gemfile specified 2.5.8 (Bundler::RubyVersionMismatch)

What am I missing? Is there a way to explicitly specify the version of bundler used by bundlerEnv?

Hmm. I’ve just noticed that the --magic option for bundix (which I hadn’t previously used) looks as if it should take the Ruby version into account when running bundler.

It looks as if this might have solved my problem, but now I’m running into another problem. I’ll report back when I know more.

I’m not sure this is relevant, because I already had a Gemfile.lock. I think this is only relevant if you need to generate the lock file, i.e. it determines the version of Ruby used when generating the lock file; not the version of Ruby in which nix-shell installs the gems.

After a lot of fiddling about I came up with the following shell.nix which seems to do what I want:

with (import <nixpkgs> {});
let
  targetRuby = ruby_2_5;
  myBundler = bundler.override {
    ruby = targetRuby;
  };
  env = bundlerEnv {
    name = "site-bundler-env";
    ruby = targetRuby;
    bundler = myBundler;
    gemdir  = ./.;
  };
in stdenv.mkDerivation {
  name = "site";
  buildInputs = [ targetRuby myBundler env ];
}

However, I’d love to know whether this is a sensible approach or whether there is a better way!

I’m not an expert, but I think there is a simpler solution based off the nixpkgs manual here: https://github.com/NixOS/nixpkgs/blob/1f49035aca52303abb2e09976baf36f297eb68a6/doc/languages-frameworks/ruby.section.md

I first created a new directory with the following files:

# ./Gemfile
source "https://rubygems.org" do
  gem 'clockwork'
end

ruby '2.5.8'
# ./shell.nix
with (import <nixpkgs> {});
let
  gems = bundlerEnv {
    name = "example-for-floehopper";

    ruby = ruby_2_5;
    gemdir = ./.;
  };
in mkShell { buildInputs = [ gems gems.wrappedRuby ]; }
# ./bundler-shell.nix
with (import <nixpkgs> {});
let
  myBundler = bundler.override { ruby = ruby_2_5; };
in
mkShell {
  name = "bundler-shell";
  buildInputs = [ myBundler ];
}

Then I ran $ nix-shell --run “bundle lock” ./bundler-shell.nix to create the initial Gemfile.lock. If I try to use my system ruby or bundix -l to create the lockfile bundler raises an exception that my ruby version is wrong. After this step ./bundler-shell.nix isn’t needed though, as the shell provided by shell.nix will provide the correct ruby and bundler as soon as we create a gemset.nix.

Now that I have a Gemfile and Gemfile.lock I created the gemset.nix that bundlerEnv wants by running $ nix-shell --run “bundix” -p bundix. I had to do this before I could enter the final nix-shell because the bundlerEnv needs a gemset.nix file before it can be used.

Now if I enter the nix-shell I get the following

[nix-shell:~/ruby-test]$ which bundle
/nix/store/dra4g2dzacwadq9ymh72z0jlhzyd6y65-example-for-floehopper/bin/bundle

[nix-shell:~/ruby-test]$ cat `which bundle` | grep GEM_HOME
Gem.paths = { 'GEM_HOME' => "/nix/store/dra4g2dzacwadq9ymh72z0jlhzyd6y65-example-for-floehopper/lib/ruby/gems/2.5.0" }

[nix-shell:~/ruby-test]$ cat `which bundle` | grep LOAD_PATH
$LOAD_PATH.unshift "/nix/store/kf9zaq46m1w8ckmsrvsavhgg6jqlxncm-bundler-2.1.4/lib/ruby/gems/2.5.0/gems/bundler-2.1.4/lib"

I think this is the best way to go, but I’m not sure why the nixpkgs manual section I linked isn’t actually in the nixpkgs manual on the website.

1 Like

@kamron_m That’s really helpful - thank you. I hadn’t seen that section of documentation before - it’s a bit of a goldmine!

No problem :+1:t3: I think there’s some weird interaction with having both an xml and markdown file for the ruby language section that’s causing a problem, but it’s been a while since I’ve dealt with Makefiles. I’m gonna try to take a look this weekend; it would be great if it were more discoverable.

1 Like