Build Service Tutorial · Tips & Tricks · Cross Distribution Howto · Packaging checks
Desktop menu categories · RPM Macros · Scriptlets · Init scripts · How to write good changes
Packaging Ruby itself
Ruby packaging up to and including 13.1
Ruby was packaged as 'multi-version' up to and including openSUSE 13.1
ruby- A wrapper package, setting up symlinks to the actual binaries
ruby19, ... - The actual binaries, installing version-specific versions like
ruby-common- A set of RPM macros required to package Ruby GEMs
Ruby packaging scheme after 13.1
The following packaging scheme applies to openSUSE after 13.1
ruby- This provides binaries (
gem, ...) and a minimal set of documentation (
As a user, installing
ruby is sufficient to get started. All other required packages are installed via dependencies.
ruby-common package, containing RPM macros for packaging gems, is merged with
Packaging (non-binary) gems now no longer requires
libruby2- This provides the
libruby2.1.so.2.0.0shared library in accordance to the openSUSE shared library packaging policy
ruby-stdlib- This provides the
Having this seperately allows for smaller maintainance updates in case a fix of the standard library is needed.
ruby-doc- This provides the full Ruby documentation including samples, reducing the size of the old Ruby package by almost 6MB.
ruby-doc-ri- These contain all files required to build software using the Ruby API.
ruby-devel-extracontains include files of the Ruby-internal API used by some gems.
This scheme has been discussed on the opensuse-ruby mailing list
Why was the packaging scheme changed after openSUSE 13.1 ?
- To revert the
Initially done to allow multiple Ruby versions in parallel.
This ability wasn't used much, rubygem packages didn't follow, and developers use
rbenv to achieve the
rom a buildservice perspective, this split cause more headaches than it provided value.
- Ruby is part of
As you know, size matters. Looking at the
ruby20 package, it has an install size of 18MB.
du -sh /usr/share/doc/packages/ruby20 reports 5.9 MB just for documentation.
- Better maintenance support
A split between binaries, shared libraries, and Ruby stdlib is desirable.
Having shared libraries in separate packages also complies with the openSUSE shared library packaging policy
Updating from previous openSUSE releases
Installing the new
ruby package will remove your former multiversion Ruby packages.
I want multiple Ruby versions in parallel !
For application deployment requiring different Ruby versions, Linux containers and Docker provide a much better solution.
How to package Ruby gems into rpms
Ruby library modules are distributed around the Ruby community mostly in the form of gems, Ruby's own package format. In some cases it makes sense to repackage gems as rpms for openSUSE via the Open Build Service (see openSUSE:Ruby Gem Strategies for many details on the pros and cons of doing this). In those cases, you can follow the below process for packaging the gems, which should be pretty easy.
A recipe for packaging gems
zypper in rubygem\(gem2rpm\)
Add a new package in your home project (or preferably in a subproject like
osc mkpac rubygem-foo cd rubygem-foo
If you have been using rvm, make sure it doesn't get in the way of the below steps, by ensuring you are using the system-wide Ruby installation:
rvm use system
Download the respective gem file and generate the RPM <code.spec file:
gem2rpm -o rubygem-foo.spec --fetch foo
.spec file as needed:
In particular, ensure that the
License: field is correct.
gem2rpm deliberately omits
BuildRequires dependencies on other gems (see below).
Provides fields are automatically generated for gem dependencies via the
ruby-common package (although you'll need to build directly against
devel:languages:ruby rather than against
openSUSE:12.2 so that the build uses version 1.0 from
devel:languages:ruby, rather than version 1.9.3 from the main 12.2 repository), so you should only include manual
Provides for non-gem packages. See below for how this works.
Add a changelog entry, build the package locally, test, then commit your package:
osc vc .... osc build --local osc vc .... osc ci
If you feel your package is ready for wider adoption, file a submitrequest against devel:languages:ruby:extensions build service repository:
osc sr devel:languages:ruby:extensions
Updating gem packages to a new gem version
If you want to update a gem package to a newer version of the gem but other packages still depend on the old version, then you should ensure that there are two separate packages, with the older one containing a suffix in the package name. For example, when updating gem
foo from 0.6.3 to 0.7.1, if another gem depends on
foo ~> 0.6 then there should be a
rubygem-foo-0_6 package containing 0.6.3 alongside
rubygem-foo which contains the latest version (0.7.1). This suffixing scheme is known to be sub-optimal, but there appears to be some inertia or resistance to addressing it at the time of writing.
If you want to update a gem package which uses the suffix scheme to a newer version of the gem, and you are sure that no other packages still depend on the old version, then please take the opportunity to drop the suffix. However, for historical reasons there are some packages such as
rails for which the suffix should never be dropped.
(FIXME: this section needs more info)
gem2rpm takes the license from the output file when there is no license in the gemspec, so updating an existing
.spec file can be done via
gem2rpm *.gem -o *.spec
and then following the same steps as above.
Omission of BuildRequires
gem2rpm deliberately omits
BuildRequires dependencies on other gems to reduce build latency in the Build Service - see this post for more details. To prevent
gem install failing in the
%install phase due to missing
gem install-time gem dependencies, the template from which
.spec files invokes the
%gem_install macro with the
Compensating for lack of BuildRequires
The disadvantage of the above optimization is no edges of the rpm install-time dependency tree are checked at rpm build-time, so you would only discover broken dependencies when you actually installed the gem rpms. (The rpm is installed immediately after being built, but with
--force --nodeps so dependencies are ignored.)
For example, if A and B are two gems within the project where A depends on B, OBS could build A successfully without knowing whether B installs successfully. B might have unresolvable automatically generated run-time dependencies (i.e. Requires) on other gems or Ruby itself, or unresolvable manually specified run-time dependencies, or it might simply conflict with files from other packages (gems or otherwise).
As a workaround, you can implement a full check of the rpm install-time dependency tree of all gems in your OBS project which happens at build-time, by creating a small package that itself BuildRequires all gems in the repository. This has the added advantage of checking for conflicts between gems on independent branches of the dependency tree.
There is an example of such a package in
all-good. That means for a rails update, the scheduler will only need one round of updates: update all gems and then let the scheduler calculate if
all-good can be expanded or if something misses dependencies.
ruby-common has a generate_buildrequires.sh script which will automatically update the BuildRequires for you.)
How gem dependencies are automatically handled
ruby-common package from
devel:languages:ruby includes various rpm macros and scripts which combine to automatically calculate and embed into the gem package
Requires: headers which reflect the dependencies from this gem onto other gems, and also
Provides: headers which support any required dependencies of other gems on this gem.
%install section of the gem's
.spec file should contain a line
This triggers a macro defined in
/etc/rpm/macros.suse-ruby which invokes
/usr/lib/rpm/gem_install.sh, which will install the gem into
rpmbuild's install root (a.k.a. buildroot), and also install a
foo.gemspec file (which is generated from the YAML-serialized
metadata.gz embedded in the gem file) under a
specifications/ subdirectory in the same hierarchy.
%install phase of the
rpmbuild process, rpm's automatic dependency generator mechanism kicks in. At this point, the
%__rubygems_path macro defined in
/usr/lib/rpm/fileattrs/rubygems.attr, which is a regular expression matching the full path to the
foo.gemspec file generated above, causes rpm to run the
%__rubygems_provides macros defined in the same file. These run
/usr/lib/rpm/rubygemsdeps.rb which extracts the dependencies from the
foo.gemspec file and converts them into
Be aware that there are currently border cases for which this system does not work: if the
.gemspec file has version ranges e.g.
s.add_dependency "net-ssh", ">= 2.6.6", "< 2.8.0"
then the system cannot guarantee this constraint since
zypper could falsely satisfy it by having
rubygem-net-ssh-2.8.0-26.1.x86_64 co-installed. Therefore it will probably be safer to patch it so that the constraint is tightened up to use the
~> operator, e.g.
s.add_dependency "net-ssh", "~> 2.6.6"
Future versions of rpm may support version ranges, at which time it would be possible to close this loophole.
Note that if your project builds for a distribution which has
rpm < 4.9.0, such as openSUSE 11.4 and SLE11, then it will not have the
fileattrs pluggable dependency generator available. Therefore the automatic generation can only be triggered by using a patched version of
rpm. This has been done for openSUSE 11.4 and SLE11 in the
Format of automatically generated Requires: headers
Here is an example for version 0.3.2 of the
The automatically generated
Requires: headers are:
ruby(abi) = 1.9.1 rubygem(1.9.1:oa-core) = 0.3.2 rubygem(1.9.1:rack-openid:1.3) >= 1.3.1 rubygem(1.9.1:ruby-openid-apps-discovery:1.2) >= 1.2.0
This is equivalent to the following Ruby dependencies:
oa-core = 0.3.2 rack-openid ~> 1.3.1 ruby-openid-apps-discovery ~> 1.2.0
And in fact that is exactly what is listed at
so we can see it works nicely. There is a subtlety around the
way the pessimistic
~> version constraint operator is implemented.
If the gem's
rack-openid dependency was instead
rack-openid => 1.3.1
then the corresponding
Requires: header would have been:
rubygem(1.9.1:rack-openid) >= 1.3.1
i.e. without the
:1.3 in the symbol.
Format of automatically generated Provides: headers
The automatically generated
Provides: headers provide
symbols in the same format as the
rubygem(1.9.1:oa-openid) = 0.3.2 rubygem(1.9.1:oa-openid:0) = 0.3.2 rubygem(1.9.1:oa-openid:0.3) = 0.3.2 rubygem(oa-openid) = 0.3.2
(Of course this is necessary, otherwise the whole system would break down.)
But they also cover the old version-suffixed naming scheme for backwards compatibility:
rubygem-oa-openid-0 = 0.3.2 rubygem-oa-openid-0_3 = 0.3.2
It is hoped we can drop these suffixed symbols in the future.
Common problems building rubygems
My rubygem-* package fails to build
rubygem-* package does not build on Factory, regenerate the .spec file with
gem2rpm <foo>.gem > rubygem-<foo>.spec
Typical gem packaging problems
The gem contains binary parts, fails to install because it does not honor the new pathes
Use the new
%gem_* macros as outlined above
The gem contains binary parts, fails to build because ruby.h is missing
ruby-devel to the
BuildRequires(Formerly, all rubygems BuildRequired
ruby-common. However, the majority of rubygems don't have binaries and build/package fine with just ruby installed.)
- New Ruby packaging for openSUSE > 13.1
- The thread starting with this post has a lot of details on the background to the current policy.