21 May 2013
I recently started to learn a little about how Puppet can be used to setup and configure servers. One task I wanted Puppet to automate was the install of Ruby 2.0.0.
It didn't take much reading for me to realise that using Puppet to grab the Ruby source and compile it on each machine is not seen as best practice - a better idea is to build an RPM once and use Puppet to deploy the RPM on each machine as required.
That left me with the problem of how to build an RPM.
I came across some good instructions that suggest using a tool called mock to help with building RPMs. Mock creates a clean chroot environment to build a RPM package in - it will only contain packages that are defined as required by the RPM it is building, so it is easy to spot if something is missing.
You can also build the RPMs using the rpmbuild command without mock - I think the advantage of mock is that it enables the dependency check. Aside from that, it does pretty much the same job as rpmbuild, as it actually invokes rpmbuild behind the scenes.
To use mock, you need to install a few packages:
$ sudo yum install rpm-build redhat-rpm-config rpmdevtools mock
And create an unprivileged user to run mock:
$ sudo adduser builder --home-dir /home/builder \
--create-home --groups mock \
--shell /bin/bash --comment "rpm package builder"
Next switch to the new builder user and create an RPM directory structure:
$ su - builder
$ rpmdev-setuptree
Now we need to get the Ruby source code, and put it into the SOURCES directory:
$ cd ~/rpmbuild/SOURCES
$ wget ftp://ftp.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p195.tar.gz
The final thing we need is an RPM spec file that describes how to build Ruby and any dependencies. Thanks to the instructions above, I located a spec file for Ruby-1.9.3 on Github, so I took it and modified it for what I needed.
One difference between my spec file and the one on Github, is that I have specified an installation prefix for my Ruby install, while the Github one is intended to replace the system Ruby. I decided to put my Ruby install in /opt/rubyies/ruby-2.0.0-p195 which is the location chruby prefers to put different Ruby versions.
Put the following spec file into ~/rpmbuild/SPECS/ruby20.spec
%define rubyver 2.0.0
%define rubyminorver p195
Name: ruby20
Version: %{rubyver}%{rubyminorver}
Release: 1%{?dist}
License: Ruby License/GPL - see COPYING
URL: http://www.ruby-lang.org/
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildRequires: automake zlib zlib-devel readline libyaml libyaml-devel readline-devel ncurses ncurses-devel gdbm gdbm-devel glibc-devel tcl-devel gcc unzip openssl-devel db4-devel byacc make libffi-devel
Requires: libyaml
Source0: ftp://ftp.ruby-lang.org/pub/ruby/ruby-%{rubyver}-%{rubyminorver}.tar.gz
Summary: An interpreter of object-oriented scripting language
Group: Development/Languages
%description
Ruby is the interpreted scripting language for quick and easy
object-oriented programming. It has many features to process text
files and to do system management tasks (as in Perl). It is simple,
straight-forward, and extensible.
%prep
%setup -n ruby-%{rubyver}-%{rubyminorver}
%build
export CFLAGS="$RPM_OPT_FLAGS -Wall -fno-strict-aliasing"
./configure --prefix=/opt/rubies/ruby-2.0.0-p195
make %{?_smp_mflags}
%install
# installing binaries ...
make install DESTDIR=$RPM_BUILD_ROOT
#we don't want to keep the src directory
rm -rf $RPM_BUILD_ROOT/usr/src
%clean
rm -rf $RPM_BUILD_ROOT
%files
%defattr(-, root, root)
/opt/rubies/ruby-2.0.0-p195
%changelog
* Tue May 21 2013
- Initial Version
With all the setup done, it is time to use mock to build the RPM.
First we initialize mock to create a clean chroot area to build the RPM, then we go ahead and build the actual RPM, which requires two steps, the first to build the source RPM and the second to build the binary RPM:
$ cd ~
$ mock --init
$ mock --buildsrpm --spec=./rpmbuild/SPECS/ruby20.spec --sources=./rpmbuild/SOURCES
$ mock --no-clean --rebuild /var/lib/mock/epel-5-x86_64/result/ruby-2.0.0p195-1.el5.centos.src.rp m
On my system, building Ruby takes about 5 minutes, but when it is done you should have a few RPMs in /var/lib/mock/epel-5-x86_64/result/ along with a couple of log files. The binary RPM is called ruby-2.0.0p195-1.el5.centos.x86_64.rpm and is what you actually want to install to provide Ruby.
$ ls -l /var/lib/mock/epel-5-x86_64/result/
-rw-rw-r-- 1 builder mock 548108 May 21 13:20 build.log
-rw-rw-r-- 1 builder mock 51366 May 21 13:20 root.log
-rw-rw-r-- 1 builder mock 13148404 May 21 13:13 ruby-2.0.0p195-1.el5.centos.src.rpm
-rw-rw-r-- 1 builder mock 11011799 May 21 13:19ruby-2.0.0p195-1.el5.centos.x86_64.rpm
-rw-rw-r-- 1 builder mock 7246129 May 21 13:20 ruby-debuginfo-2.0.0p195-1.el5.centos.x86_64.rpm
-rw-rw-r-- 1 builder mock 1835 May 21 13:20 state.log
Now you can install the RPM with the usual command:
[root@localhost result]# rpm -ivh ruby-2.0.0p195-1.el5.centos.x86_64.rpm
Preparing... ########################################### [100%]
1:ruby ########################################### [100%]
$ /opt/rubies/ruby-2.0.0-p195/bin/ruby -v
ruby 2.0.0p195 (2013-05-14 revision 40734) [x86_64-linux]