bsdpower.com

Installing ruby extensions like openssl with rbenv

The ruby community has adopted quite a "for dummies" mindset: everything is made to work out of the box with zero configuration needed, on all environments. This, of course, is not without cost, and given that both features and development effort can be assumed to be infinite, the cost is performance. Ruby application bootstrap times are outrageous, memory consumption is off the charts as well, rubygems and bundler are one slower than the other, etc.

Today though I want to consider a case when the software plain does not work, specifically when rbenv installs ruby with extensions missing.

Suppose you did something like this:

% rbenv install 1.9.3-p448
Downloading yaml-0.1.4.tar.gz...
-> http://dqw8nmjcqpjn7.cloudfront.net/36c852831d02cf90508c29852361d01b
Installing yaml-0.1.4...
Installed yaml-0.1.4 to /home/me/.rbenv/versions/1.9.3-p448

Downloading ruby-1.9.3-p448.tar.gz...
-> http://dqw8nmjcqpjn7.cloudfront.net/a893cff26bcf351b8975ebf2a63b1023
Installing ruby-1.9.3-p448...
Installed ruby-1.9.3-p448 to /home/me/.rbenv/versions/1.9.3-p448

Ruby installed! Let's try using it:

% bundle install

Could not load OpenSSL.
You must recompile Ruby with OpenSSL support or change the sources in your Gemfile from 'https' to
'http'. Instructions for compiling with OpenSSL using RVM are available at rvm.io/packages/openssl.

Uh-oh. My ruby is useless for my project.

Now I could, like a good dummy, follow the instructions at rvm.io/packages/openssl (which more or less say "give rvm a root shell and let it take care of everything, you don't need to know the details") but I like knowing what is going on on my system and I don't give root shells to any program that asks for them. So, we are going to toss this recommendation into /dev/null.

I don't know if there is a way to pry the actual reason why openssl was not installed out of rbenv, but it does not matter for the following discussion anyway.

Installing ruby extensions

The first thing you need to know is that ruby installation is performed in two parts. The first part builds and installs the ruby interpreter itself, the second part builds and installs extensions that are included with the interpreter. Without some extensions (like date, io or stringio) most programs won't run. Other extensions (like openssl) are required by some programs but ignored by others. Still other extensions (gdbm, win32ole) are so obscure that you may have never heard of them, and they may well not exist on your machine. Ruby-the-interpreter can be installed without many of the extensions it ships with, which explains how rbenv reports installation success yet produces a ruby installation that is useless for a particular application.

The second thing you need to know is that ruby extensions can be installed at any time into an existing ruby installation. There is no requirement that the interpreter and extensions are built together. This means to install the missing openssl extension we just need to build that one extension, not rebuild the entire ruby installation.

Now, for how to actually do it. First, we need to obtain the ruby source code. rbenv deleted it after "successful" installation, but told us where the source came from: http://dqw8nmjcqpjn7.cloudfront.net/a893cff26bcf351b8975ebf2a63b1023 above. Therefore:

% wget http://dqw8nmjcqpjn7.cloudfront.net/a893cff26bcf351b8975ebf2a63b1023
...
2014-01-27 22:14:01 (6.34 MB/s) - a893cff26bcf351b8975ebf2a63b1023 saved [12559260/12559260]

This is really a tarball, which we can unpack:

% tar xf a893cff26bcf351b8975ebf2a63b1023

If your tar is smart, it should detect gzip and bzip2 compressed tarballs. If it does not, try xfz or xfj in place of xf. Now we have a ruby-1.9.3-p448 directory with ruby source in it. Navigate to the extension we are trying to install:

% cd ruby-1.9.3-p448/ext/openssl

If you have not done so already, activate the ruby you want to install the extension to which should be the same version:

% rbenv shell 1.9.3-p448

Now configure the extension:

% ruby extconf.rb

You will get a bunch of output. If all goes well, you'll see something like this at the end:

=== Checking done. ===
creating extconf.h
creating Makefile
Done.

Chances are, all does not go well, as otherwise openssl would have been installed automatically. If so you might see an error like this:

=== Checking for required stuff... ===
checking for openssl/ssl.h... no
=== Checking for required stuff failed. ===
Makefile wasn't created. Fix the errors above.

This system is missing openssl headers.

Resolve the issue if any, patch the source if necessary, then rerun ruby extconf.rb. Repeat until it succeeds.

Once the extension is configured you can build it:

% make

And once it's built, you can install it:

% make install

Go back to your application and see if it now works!

This recipe works for readline and any other extension shipped with ruby.