Building Ruby-1.9.3 for CentOS-6.3

We use an ‘enterprisey’ Linux distro based on Red Hat’s EL6 (RHEL6) called CentOS-6. ‘Enterprisey’ distroes place their emphasis on stable (everything works and nothing much changes) releases. Therefore the software that ships with these distroes is often several minor, and sometimes major, releases older than the current versions available. And those distroes are around for a long, long time. This is a problem for us.

The source of our problem is that we develop and run web applications using the Ruby on Rails (Rails or RoR) framework. Rails is still a fairly new technology (was it really nine years ago that I started with Rails?) and is evolving rapidly. Its acceptance by a large community of application developers has in large measure driven the Ruby community to get Ruby 2.0 (née 1.9) out the door.

In any case Ruby 1.9.3 is now the baseline version for the forthcoming Rails-4, which likely will deprecate Ruby v1.9 for v2.0 soon after the latter stabilizes. RHEL6 on the other hand shipped with Ruby 1.8.7, which is rather long in the tooth at this point and simply will not run Rails-4 apps. And Redhat will keep shipping that version until EL6 reaches its end of life in Q4 of 2023. Waiting for RHEL-7 may not prove fruitful either as that release might not include Ruby-1.9 when it ships in 2013. And if Ruby-1.9 does not make it into RHEL7 then it will be 2015 before the release of RHEL8 might contain either Ruby-1.9 or Ruby-2.0

The way around this for many is to embrace one of the Ruby version managers available for user-space deployment, Ruby Version Manager (RVM) or RBENV. These tools developed from the environment many web application developers find themselves, isolated from the system level utilities on their deployment hosts and unable to influence system wide decisions. This situation also led directly to the development of Bundler as a project site specific RubyGem manager that can be isolated from the system level gem files.

Together the rvm/rbenv and bundle applications shield Rails application deployments from dealing with archaic system level utilities. However, this shield comes at a price of increased complexity, maintenance and the ever-present possibility of getting a gem / ruby version mismatch. Or, worse, running the obsolete system level ruby when the local updated version is required and then dealing with the outcome, whatever that may entail.

When one has neither the authority nor the influence to alter the system level ruby then this is all one has and one must make the best of it. However, if one has the ability to determine the system level ruby then one is still left with the technical problem of changing it. On an ‘enterprisey’ distro some form of software package management is almost always present and whose use is nearly invariantly required. Thus it is necessary to locate a suitable package for the software one wishes to install.

I have searched for more recent versions of ruby packaged as rpms for my CentOS-6 hosts and have not been successful. On CentOS-5 I used checkinstall to build Ruby-1.9.x from source and produce an rpm package simply to satisfy our system administration’s packaging requirements. But, I was never able to get checkinstall to work on CentOS-6. Eventually it dawned on me that I was simply avoiding the issue of building my own rpms in the approved manner. I was looking for a simple canned recipe so that I could avoid spending time learning the ins and outs of yet another ancillary technology.

So I decided to bite the bullet and do the work properly. And the whole thing proved painlessly easy in the end (other than the chagrin at having wasted countless hours trying to avoid doing the right thing to begin with) . Here then is a recipe to build your own rpm packages for Ruby-1.9.Xpyyy on a CentOS-6 based host. It might work on other releases and other distroes but there is a limit to the amount of time I can give over to this so I have no evidence one way or the other.

The Recipe

What this recipe does is create a build environment and construct rpm packages from the source files provided by ruby-lang.org

Note:
Because WordPress truncates code blocks I have added line breaks (\) in the code below that do not exist in the original. Significantly these appear in overly long paths. I have attempted to break paths so that a leading ‘/’ character appears for each portion. You will have to detect and splice these breaks by hand.

First, you need a build host with the appropriate OS installed. I am using CentOS-6 on a KVM virtual guest emulating a 2 core x86_64. Second you need to have the system administrator install the following packages:

sudo yum install rpm-build redhat-rpm-config rpmdevtools mock

Note that mock is provided by the epel folks and so the epel repo must be enabled to get it via yum.

Next you need a build userid. It is recommended that this not be an ordinary user but a purpose-built userid employed only for building packages. If you do not share the build system among different individual users then you may well decide to avoid this complication. But, you have been advised.

sudo adduser builder --home-dir /home/builder \
  --create-home --user-group --groups mock \
  --shell /bin/bash --comment "rpm package builder"

What this did is create a new user with its home directory in /home and assign it secondary membership to the mock group. The mock group is added by the mock package installation and membership in it is required for any userid using mock to build packages

Next we need a build environment.

su -l builder
rpmdev-setuptree

What this does is assume the user identity of the builder and go to their home directory in a login shell. Then we create the rpm development tree using the script provided. This basically does the following in the user’s home directory:

mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
echo '%_topdir %(echo $HOME)/rpmbuild' > ~/.rpmmacros

We now have a file system tree structure like this:

~
└── rpmbuild
    ├── BUILD
    ├── RPMS
    ├── SOURCES
    ├── SPECS
    └── SRPMS

Now we need to obtain two vital pieces of data, the source code and a working rpm specification file for ruby-1.9.3 on CentOS. The first is trivially obtained from ruby-lang.org.

cd ~/rpmbuild/SOURCES
wget ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p374.tar.gz

The second was not so easy to locate. Lacking the knowledge to create a spec file from scratch I eventually located and stole one from a kind soul who put it on github. Of course, you need to make a few changes here and there. As this is a recipe I provide the contents I used:

Note Bene: Watch for line breaks (\)

%define rubyver         1.9.3
%define rubyminorver    p374

Name:           ruby
Version:        %{rubyver}%{rubyminorver}
Release:        2%{?dist}
License:        Ruby License/GPL - see COPYING
URL:            http://www.ruby-lang.org/
BuildRoot:      %{_tmppath}/%{name}-%{version}-\
%{release}-root-%(%{__id_u} -n)
BuildRequires:  gcc
BuildRequires:  make
BuildRequires:  byacc
BuildRequires:  gdbm gdbm-devel
BuildRequires:  glibc-devel
BuildRequires:  db4-devel
BuildRequires:  libyaml-devel
BuildRequires:  ncurses ncurses-devel
BuildRequires:  openssl-devel
BuildRequires:  readline readline-devel
BuildRequires:  tcl-devel
BuildRequires:  unzip

Source0:        ftp://ftp.ruby-lang.org/pub/ruby\
/ruby-%{rubyver}-%{rubyminorver}.tar.gz
Summary:        Interpreter for an object-oriented scripting language
Group:          Development/Languages
Provides: ruby(abi) = 1.9
Provides: ruby-irb
Provides: ruby-rdoc
Provides: ruby-libs
Provides: ruby-devel
Provides: rubygems
Obsoletes: ruby
Obsoletes: ruby-libs
Obsoletes: ruby-irb
Obsoletes: ruby-rdoc
Obsoletes: ruby-devel
Obsoletes: rubygems

%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 \
  --enable-shared \
  --disable-rpath \
  --without-X11 \
  --without-tk \
  --includedir=%{_includedir}/ruby \
  --libdir=%{_libdir}

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)
%{_bindir}
%{_includedir}
%{_datadir}
%{_libdir}

%changelog
* Thu Jan 24 2013 James B. Byrne  - 1.9.3-p374
- Update ruby patch to 1.9.3-p374

* Wed Jan 18 2012 Mandi Walls  - 1.9.3-p0
- Update ruby version to 1.9.3-p0

* Mon Aug 29 2011 Gregory Graf  - 1.9.2-p290
- Update ruby version to 1.9.2-p290

* Sat Jun 25 2011 Ian Meyer  - 1.9.2-p180-2
- Remove non-existant --sitearchdir and --vedorarchdir from %configure
- Replace --sitedir --vendordir with simpler --libdir
- Change %{_prefix}/share to %{_datadir}

* Tue Mar 7 2011 Robert Duncan  - 1.9.2-p180-1
- Update prerequisites to include make
- Update ruby version to 1.9.2-p180
- Install /usr/share documentation
- (Hopefully!?) platform agnostic

* Sun Jan 2 2011 Ian Meyer  - 1.9.2-p136-1
- Initial spec to replace system ruby with 1.9.2-p136

The main thing to change is the version and patch level to match those in the source file archives downloaded into SOURCES. This file we name ruby193.spec and place it in ~/rpmbuild/SPECS

At this point we are ready to involve mock. Mock is a self-described simple script that creates a pristine chroot environment and performs the rpm build in that tree. This isolates the build process from cross contamination by other installed packages on the host system. In the mock tree if a package is not part of the basic system and is not listed as a dependency then the package simply is not available to the build. Otherwise, mock simply uses the rpm tools like any other user would.

# go home
cd ~
# Initialize the build tree (/var/lib/mock is default)
# Make sure you have enough disk space available.
# This is why your build user must belong to the mock group.
mock --init
# Build the source package
mock --buildsrpm \
  --spec=./rpmbuild/SPECS/ruby193.spec \
  --sources=./rpmbuild/SOURCES
# Build the binary packages
mock --no-clean \
  --rebuild /var/lib/mock\
    /epel-6-x86_64/result/ruby-1.9.3p374-2.el6.src.rpm

The output from the source package build will look somewhat like this:

mock --buildsrpm \
  --spec=./rpmbuild/SPECS/ruby193.spec \
  --sources=./rpmbuild/SOURCES
INFO: mock.py version 1.1.28 starting...
.  .  .
Finish: rpmbuild -bs
Finish: buildsrpm
INFO: Done(ruby193.spec) Config(default) 0 minutes 16 seconds
INFO: Results and/or logs in: 
  /var/lib/mock/epel-6-x86_64/result

The --no-clean option was set on the binary build because we started by building the SRPM in a clean root and there is nothing gained by purging it and re-installing all the dependencies simply to build the binary RPM. However, in all other cases this switch should be left off and mock allowed to purify the build environment. Alternatively we could have told mock to build the SRPM and RPM simultaneously.

The output from the binary package build will look like this:

mock --no-clean \
  --rebuild /var/lib/mock\
/epel-6-x86_64/result/ruby-1.9.3p374-2.el6.src.rpm
INFO: mock.py version 1.1.28 starting...
.  .  .
Finish: build phase for ruby-1.9.3p374-2.el6.src.rpm
INFO: Done(/var/lib/mock\
/epel-6-x86_64/result/ruby-1.9.3p374-2.el6.src.rpm) Config(default) 5 minutes 9 seconds
INFO: Results and/or logs in:
  /var/lib/mock/epel-6-x86_64/result
Finish: run

The contents of /var/lib/mock/epel-6-x86_64/result should be similar to:

ll /var/lib/mock/epel-6-x86_64/result
total 25220
-rw-rw-r--. 1 builder mock. . . build.log
-rw-rw-r--. 1 builder mock. . . root.log
-rw-rw-r--. 1 builder mock. . .\
 ruby-1.9.3p374-2.el6.src.rpm
-rw-rw-r--. 1 builder mock. . .\
 ruby-1.9.3p374-2.el6.x86_64.rpm
-rw-rw-r--. 1 builder mock. . .\
 ruby-debuginfo-1.9.3p374-2.el6.x86_64.rpm
-rw-rw-r--. 1 builder mock. . .\
 state.log

And that is it. You now have rpm packages for ruby-1.9.3 on CentOS-6 that can be added to your local yum repository or installed by hand using yum localinstall or rpm -i.

I also took the time to create a CentOS-6 virtual guest on which I installed every single package available from CentOS Base. I then ran rpm -q --whatrequires ruby which produced nothing. Apparently as of 2013-Jan-30 there are no RHEL6 distro level dependencies on Ruby whatsoever. So, installing a new Ruby on any RHEL6 based system that does not already have running ruby applications should pose no problems whatsoever. Of course you get no guarantee but to the best of my knowledge and belief that should be the result.

About these ads
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: