There are three basic categories of ruby packages: ruby gems, non-gem ruby packages, and applications written in ruby. These guidelines contain sections common to all of these as well as sections which apply to each one individually. Be sure to read all the guidelines relevant to the type of ruby package you are building.
Each Ruby package must indicate it depends on a Ruby interpreter (this does not apply to ruby gem packages). Use ruby(release) virtual requirement to achieve that:
Requires: ruby(release)
If the package requires Ruby of certain version(s), make the requirement versioned like this:
Requires: ruby(release) >= 1.9.1
Most of the pure Ruby packages will work on all Ruby interpreters. There are however cases, when the packages use interpreter-specific functions (like fork()
) and won't run on other interpreters (JRuby). In this case, the package should require that interpreter. For example, a package that uses fork
should explicitly specify Requires:
ruby
.
In case of such package, packager should file a bug to ask upstream to provide support for other interpreter(s). This should be documented in specfile.
On Fedora, /usr/bin/ruby
is implemented via Rubypick. Rubypick is a tool similar to RVM or rbenv. It allows choosing interpreter to execute Ruby script. Rubypick routes anything executed via /usr/bin/ruby
to /usr/bin/ruby-mri
or /usr/bin/jruby
. By default, it runs MRI (Matz's Ruby Implementation), but user can explicitly specify the interpreter by using _mri_
or _jruby_
as a first parameter. For example:
ruby _jruby_ jruby_script.rb
gem _mri_ install foo
rails _jruby_ s
Using the RUBYPICK
environment variable can achieve the same results. The environment variable can be used to set one interpreter as the global default:
export RUBYPICK=_jruby_
ruby jruby_script.rb # Will use jruby
gem install foo # Will also use jruby
Ruby executables that are known to only run on one Ruby implementation should use that specific implementation in their shebang - #!/usr/bin/ruby-mri
or #!/usr/bin/jruby
to ensure that they run using that implementation. All other code should use #!/usr/bin/ruby
.
rubygem-%{gem_name}
.UPSTREAM
name. For example: ruby-UPSTREAM
. If the upstream name UPSTREAM
contains ruby
, that should be dropped from the name. For example, the SQLite database driver for ruby is called sqlite3-ruby
. The corresponding Fedora package should be called ruby-sqlite3
, and not ruby-sqlite3-ruby
.Non-gem ruby packages and ruby gem packages install to certain standard locations. The ruby-devel
and rubygems-devel
packages contain macros useful for the respective package types. Alternate ruby interpreters will have equivalent locations (To be added to this table later)
Macro Expanded path Usage
From ruby-devel; intended for non-gem packages.
%{ruby_vendorarchdir}
/usr/lib{64}/ruby/vendor_ruby Place for architecture specific (e.g. *.so) files.
%{ruby_vendorlibdir}
/usr/share/ruby/vendor_ruby Place for architecture independent (e.g. *.rb) files.
%{ruby_sitearchdir}
/usr/local/lib{64}/ruby/site_ruby Place for local architecture specific (e.g. *.so) files.
%{ruby_sitelibdir}
/usr/local/share/ruby/site_ruby Place for local architecture independent (e.g. *.rb) files.
From rubygems-devel; intended for gem packages
%{gem_dir}
/usr/share/gems Top directory for the Gem structure.
%{gem_instdir}
%{gem_dir}/gems/%{gem_name}-%{version} Directory with the actual content of the Gem.
%{gem_libdir}
%{gem_instdir}/lib The lib
folder of the Gem.
%{gem_cache}
%{gem_dir}/cache/%{gem_name}-%{version}.gem The cached Gem.
%{gem_spec}
%{gem_dir}/specifications/%{gem_name}-%{version}.gemspec The Gem specification file.
%{gem_docdir}
%{gem_dir}/doc/%{gem_name}-%{version} The rdoc documentation of the Gem.
%{gem_extdir_mri}
%{_libdir}/gems/ruby/%{gem_name}-%{version} The directory for MRI Ruby Gem extensions.
You may have noticed that the table above has different directories for non-gem libraries on different ruby interpreters but only a single set of directories for rubygem libraries. This is because code written for one ruby interpreter will often run on all ruby interpreters that Fedora ships (ruby, jruby, etc). However, some code uses methods that are not available on all interpreters (see Different Interpreters Compatibility). Rubygems have a facility to ship different versions of the code in the same gem so that the gem can run on all versions of the interpreter so we only need to have one common directory for rubygems that all the interpreters can use.
The standard ruby %{vendorlib}
directories lack this facility. For this reason, non-gem libraries need to be placed in per-interpreter directories and must have a separate subpackage (or package depending on upstream) for each interpreter that they support.
These guidelines only apply to Ruby packages whose main purpose is providing a Ruby library; packages that mainly provide user-level tools that happen to be written in Ruby must follow the ruby applications guidelines instead.
RubyGems are Ruby's own packaging format. Gems contain a lot of the same metadata that RPM's need, making fairly smooth interoperation between RPM and Gems possible. This guideline ensures that Gems are packaged as RPM's in a way that such RPM's fit cleanly with the rest of the distribution and to make it possible for the end user to satisfy dependencies of a Gem by installing the appropriate RPM-packaged Gem.
Both RPM's and Gems use similar terminology --- there are specfiles, package names, dependencies etc. for both. To keep confusion to a minimum, terms relating to Gem concepts will be explicitly refereed to with the word 'Gem' prefixed, eg 'Gem specification' (.gemspec). An unqualified 'package' in the following always means an RPM.
%{gem_name}
which is the name from the Gem's specification.Source
of the package must be the full URL to the released Gem archive; the version of the package must be the Gem's version.BuildRequires:
rubygems-devel
to pull in the macros needed to build.Requires
nor Provides
listed, since those are autogenerated.Requires:
ruby(release)
, unless you want to explicitly specify Ruby version compatibility. Automatically generated dependency on RubyGems (Requires:
ruby(rubygems)
) is enough.Runtime requires and provides are automatically generated by RPM's dependency generator. However, it can sometimes throw in additional dependencies contrary to reality. To fix this, the dependency generator needs to be overridden so that the additional dependencies can be filtered out. See
Since gems aren't a standard archive format that rpm knows about and they encapsulate both an archive format and information to build the ruby library building an rpm from a gem looks a little different from other rpms.
A sample spec for building gems would look like this:
%prep
gem unpack %{SOURCE0}
%setup -q -D -T -n %{gem_name}-%{version}
gem spec %{SOURCE0} -l --ruby > %{gem_name}.gemspec
# Modify the gemspec if necessary with a patch or sed
# Also apply patches to code if necessary
%patch0 -p1
%build
# Create the gem as gem install only works on a gem file
gem build %{gem_name}.gemspec
# %%gem_install compiles any C extensions and installs the gem into ./%%gem_dir
# by default, so that we can move it into the buildroot in %%install
%gem_install
%install
mkdir -p %{buildroot}%{gem_dir}
cp -a ./%{gem_dir}/* %{buildroot}%{gem_dir}/
# If there were programs installed:
mkdir -p %{buildroot}%{_bindir}
cp -a ./%{_bindir}/* %{buildroot}%{_bindir}
# If there are C extensions, copy them to the extdir.
mkdir -p %{buildroot}%{gem_extdir_mri}
cp -a .%{gem_extdir_mri}/{gem.build_complete,*.so} %{buildroot}%{gem_extdir_mri}/
Since gems aren't an archive format that rpm recognizes, the first thing we have to do is explicitly use gem
unpack
to extract the source from the gem. Then we call %setup
-n
%{gem_name}-%{version}
to tell rpm what the directory the gem has unpacked into. The -T
and -D
flags tell %setup
that we've already unpacked the code
We then run gem
spec
to output the metadata from the gem into a file. This .gemspec
file will be used to rebuild the gem later. If we need to modify the .gemspec
(for instance, if the version of dependencies is wrong for Fedora or the .gemspec
is using old, no longer supported fields) we would do it here. Patches to the code itself can also be done here.
Next we build the gem. Because %gem_install
only operates on gem archives, we next recreate the gem with gem
build
. The gem file that is created is then used by %gem_install
to build and install the code into the temporary directory, which is ./%{gem_dir}
by default. We do this because the %gem_install
command both builds and installs the code in one step so we need to have a temporary directory to place the built sources before installing them in %install
section.
%gem_install
macro accepts two additional options:
-n
-d
Here we actually install into the %{buildroot}
. We create the directories that we need and then copy what was installed into the temporary directories into the %{buildroot}
hierarchy. Finally, if this ruby gem creates shared objects the shared objects are moved into the arch specific %{gem_extdir_mri}
path.
One common patching need is to change overly strict version requirements in the upstream gemspec. This may be because upstream's gemspec only mentions versions that they've explicitly tested against but we know that a different version will also work or because we know that the packages we ship have applied fixes for problematic behaviour without bumping the version number (for instance, backported fixes). To fix these issues, find the add_runtime_dependency
call in the gemspec and patch it with the corrected version similar to this:
Gem::Specification.new do |s|
# [...]
- s.add_runtime_dependency(%q<mail>, ["~> 2.2.19"])
+ s.add_runtime_dependency(%q<mail>, [">= 2.3.0"])
# [...]
end
Non-Gem Ruby packages must require ruby-devel package at build time with a BuildRequires:
ruby-devel
, and may indicate the minimal ruby version they need for building.
The following only affects the files that the package installs into %{ruby_vendorarchdir}
and %{ruby_vendorlibdir}
(the actual Ruby library files). All other files in a Ruby package must adhere to the general Fedora packaging conventions.
Pure Ruby packages must be built as noarch packages.
The Ruby library files in a pure Ruby package must be placed into %{ruby_vendorlibdir}
(or its proper subdirectory). The specfile must use this macro.
For packages with binary content, e.g., database drivers or any other Ruby bindings to C libraries, the package must be architecture specific.
The binary files in a Ruby package with binary content must be placed into %{ruby_vendorarchdir}
(or its proper subdirectory). The Ruby files in such a package should be placed into %{ruby_vendorlibdir}. The specfile must use these macros.
For packages which create C shared libraries using extconf.rb
export CONFIGURE_ARGS="--with-cflags='%{optflags}'"
should be used to pass CFLAGS
to Makefile
correctly. Also, to place the files into the correct folders during build, pass --vendor
to extconf.rb
like this:
extconf.rb --vendor
Applications are
The RPM packages must obey FHS rules. They should be installed into %{_datadir}
. The following macro can help you:
%global app_root %{_datadir}/%{name}
These packages typically have no "Provides" section, since no other libraries or applications depend on them.
Here's an abbreviated example:
%global app_root %{_datadir}/%{name}
Summary: Deltacloud REST API
Name: deltacloud-core
Version: 0.3.0
Release: 3%{?dist}
Group: Development/Languages
License: ASL 2.0 and MIT
URL: http://incubator.apache.org/deltacloud
Source0: http://gems.rubyforge.org/gems/%{name}-%{version}.gem
Requires: rubygem-haml
#...
Requires(post): chkconfig
#...
BuildRequires: rubygem-haml
#...
BuildArch: noarch
%description
The Deltacloud API is built as a service-based REST API.
You do not directly link a Deltacloud library into your program to use it.
Instead, a client speaks the Deltacloud API over HTTP to a server
which implements the REST interface.
%package doc
Summary: Documentation for %{name}
Group: Documentation
Requires:%{name} = %{version}-%{release}
%description doc
Documentation for %{name}
%prep
gem unpack -V %{SOURCE0}
%setup -q -D -T -n %{name}-%{version}
%build
%install
mkdir -p %{buildroot}%{app_root}
mkdir -p %{buildroot}%{_initddir}
mkdir -p %{buildroot}%{_bindir}
cp -r * %{buildroot}%{app_root}
mv %{buildroot}%{app_root}/support/fedora/%{name} %{buildroot}%{_initddir}
find %{buildroot}%{app_root}/lib -type f | xargs chmod -x
chmod 0755 %{buildroot}%{_initddir}/%{name}
chmod 0755 %{buildroot}%{app_root}/bin/deltacloudd
rm -rf %{buildroot}%{app_root}/support
rdoc --op %{buildroot}%{_defaultdocdir}/%{name}
%post
# This adds the proper /etc/rc*.d links for the script
/sbin/chkconfig --add %{name}
%files
%{_initddir}/%{name}
%{_bindir}/deltacloudd
%dir %{app_root}/
%{app_root}/bin
#...
%files doc
%{_defaultdocdir}/%{name}
%{app_root}/tests
%{app_root}/%{name}.gemspec
%{app_root}/Rakefile
%changelog
#...
Note, that although the source is a RubyGem, we have to install the files manually under %{_datadir}/%{name}, %{_bindir}, etc. to follow FHS and general packaging guidelines. If additional Fedora specific files (systemd .service
files, configurations) are required, they should be
%SOURCE
tagsSource1: deltacloudd-fedora
%install
stageinstall -m 0755 %{SOURCE1} %{buildroot}%{_bindir}/deltacloudd
If there is test suite available for the package (even separately, for example not included in the gem but available in the upstream repository), it should be run in %check
. The test suite is the only automated tool which can assure basic functionality of the package. Running it is especially helpful when mass rebuilds are required. You may skip test suite execution when not all build dependencies are met but this must be documented in the specfile. The missing build dependencies to enable the test suite should be packaged for Fedora as soon as possible and the test suite re-enabled.
The tests should not be run using Rake - Rake almost always draws in some unnecessary dependencies like hoe or gemcutter.
To run tests with different Ruby implementation such as JRuby, you need to BuildRequires:
jruby
. Then use Rubypick's interpreter switching:
ruby _jruby_ -Ilib -e 'Dir.glob "./test/test_*.rb", &method(:require)'
If your package is running unittests for ruby-mri and it is intended to run under alternate interpreters then it needs to run the unittests under all alternate interpreters as well. This is the only method we have to check compatibility of the code under each interpreter. The same rules apply that you can omit this if libraries you need are unavailable for a specific alternate interpreter but you must have a comment to explain.
The Ruby community supports many testing frameworks. The following sections demonstrate how several to execute several of the more common test suites.
MiniTest is the default testing framework shipped with Ruby. It is unbundled in Fedora (you must use BuildRequires:
rubygem-minitest
). To run tests using MiniTest you can use something like:
%check
ruby -Ilib -e 'Dir.glob "./test/test_*.rb", &method(:require)'
The testrb
utility is deprecated and is not present in newer Ruby versions. If a gem's tests support Minitest, the testrb
utility should not be used during %check. Instead of testrb
, users should call ruby
directly in the %check section as shown above.
You may need to adjust -Ilib
to be -Ilib:test
, or you may need to use a slightly different matching pattern for test_*.rb
, etc. Packagers are expected to use the right pattern for each gem.
To run tests using Test::Unit you must BuildRequires:
rubygem-test-unit-
and use something like this:
%check
testrb2 -Ilib test
To run tests using RSpec >= 2 you must BuildRequires:
rubygem-rspec
and use something like:
%check
rspec -Ilib spec
Sometimes you have to get the tests separately from upstream's gem package. As an example lets suppose you're packaging rubygem-delorean
, version 1.2.0, which is hosted on Github. Tests are not included in the Gem itself, so you need to get them and adjust the specfile accordingly:
# git clone https://github.com/bebanjo/delorean.git && cd delorean
# git checkout v1.2.0
# tar -czf rubygem-delorean-1.2.0-specs.tgz spec/
Source1: %{name}-%{version}-specs.tgz
# ...
%prep
%setup -q -D -T -n %{gem_name}-%{version}
tar -xzf %{SOURCE1}
# ...
%check
cp -pr spec/ ./%{gem_instdir}
pushd ./%{gem_instdir}
# Run tests
rm -rf spec
popd
# ...
%{SOURCE1}
(and this will remind you to update the tests, too).