openSUSE:Packaging Ruby
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 binariesruby20
,ruby19
, ... - The actual binaries, installing version-specific versions like/usr/bin/ruby20
,/usr/bin/ruby19
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 (ruby
,irb
,rake
,gem
, ...) and a minimal set of documentation (changelog
,readme
,news
, ...)
As a user, installing ruby
is sufficient to get started. All other required packages are installed via dependencies.
The former ruby-common
package, containing RPM macros for packaging gems, is merged with ruby
.
Packaging (non-binary) gems now no longer requires ruby-devel
.
libruby2
- This provides thelibruby2.1.so.2.0.0
shared library in accordance to the openSUSE shared library packaging policy
ruby-stdlib
- This provides the/usr/lib64/ruby/2.1.x/
directory tree.
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-devel
,ruby-devel-extra
,ruby-doc-ri
- These contain all files required to build software using the Ruby API.ruby-devel-extra
contains 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
ruby
,rubyXY
, andruby-common
split
Initially done to allow multiple Ruby versions in parallel.
This ability wasn't used much, rubygem packages didn't follow, and developers use rvm
or rbenv
to achieve the
same effect.
rom a buildservice perspective, this split cause more headaches than it provided value.
- Ruby is part of
inst-sys
(for YaST)
As you know, size matters. Looking at the ruby20
package, it has an install size of 18MB.
However, 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 !
Use rvm or rbenv if you need multiple Ruby versions to work with.
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
For Tumbleweed:
zypper in rubygem\(gem2rpm\)
For other versions, if the dependency from shown above cannot be resolved, add the devel:languages:ruby and devel:languages:ruby:extensions repositories.
Add a new package in your home project (or preferably in a subproject like home:$USER:ruby
):
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
Edit the .spec
file as needed:
vi rubygem-foo.spec
In particular, ensure that the License:
field is correct. gem2rpm
deliberately omits BuildRequires
dependencies on other gems (see below).
Also, Requires
and 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 Requires
/ 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 gem2rpm
generates .spec
files invokes the %gem_install
macro with the -f
flag.
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 devel:languages:ruby:extensions
, called 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
The 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.
The %install
section of the gem's .spec
file should contain a line
%gem_install -f
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.
After the %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_requires
and %__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 Requires:
and Provides:
headers.
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_1-2.1.4-2.7.x86_64
and 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 devel:languages:ruby:backports project
.
Format of automatically generated Requires: headers
Here is an example for version 0.3.2 of the oa-openid
gem.
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
http://rubygems.org/gems/oa-openid/versions/0.3.2
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 Requires
:
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
If your 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
Add ruby-devel
to the BuildRequires
(Formerly, all rubygems BuildRequired ruby-devel
via ruby-common
. However, the majority of rubygems don't have binaries and build/package fine with just ruby installed.)
History
- 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.