From Fedora Project Wiki

Revision as of 00:22, 14 February 2012 by Toshio (talk | contribs) (Take out idea of changing vendorlibdir to vendorlib as it's a public rubyism)

This page is a draft only
It is still under construction and content may change. Do not rely on the information on this page.
Different guidelines for oldeer Fedora and RHEL
In RHEL4, 5, and 6 and Fedora before Fedora 16, a different ruby package was installed. If you're packaging for those distributions you should follow the old ruby guidelines instead.

There are three basic categories of ruby packages: ruby gems, non-gem ruby packages, and applications written in ruby. There are a few things that are common to all of them and many differences. Because of the differences we have separate sections of this guideline for each of them. Be sure to read both the common information and the specific section for the type of ruby package you are building.

Ruby ABI

Each Ruby package must indicate the Ruby ABI version it depends on with a line like

Requires: ruby(abi) = 1.9.1

Naming Guidelines

  • Packages that contain Ruby Gems must be called rubygem-%{gem_name}.
  • The name of a ruby extension/library package must be of the form 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.
  • Application packages that mainly provide user-level tools that happen to be written in Ruby must follow the general NamingGuidelines instead.

Macros

Interpreter independence
Related to the below text about interpreter independence. Move vendorlib/vendorarch to a non-C-ruby specific location

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.

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} %{_libdir}/gems/exts/%{gem_name}-%{version} The directory for binary Gem extensions.

Dependencies

Due to having multiple types of libraries (gems and non-gems) there are two ways to specify dependencies.

Confirm change: rubygems to provide ruby()
Since rubygems now work with plain require the distinction between ruby() and rubygem() doesn't seem as necessary. I've reworked the requires and provides to tak this into account. Someone from ruby sig should confirm that this seems sensible.

Requires

If your package uses gem 'rubylibrary' or you're packaging a ruby gem that depends on the library in its .gemspec (See the section on building gems for more information) you Must use Requires: rubygem(rubylibrary) so the rpm will pull in the correct ruby library.

If your package just uses require 'rubylibrary' it's always fine to use Requires: ruby(rubylibrary). If you know that the ruby library is provided as a gem despite using require 'rubylibrary' you may use Requires: rubygem(rubylibrary) instead but this is not required as it forces packagers to know unnecessary details of how another ruby package has been created.

When packaging rubygems the gem itself carries some dependency information in the "gem specification". That specification may contain precise versions of dependent gems. If Fedora has packaged a different version of the gem you may have to adjust the .gemspec to work with the Fedora version of the package (see the section on building gems for more information).

Please ensure that the package works properly with the dependencies specified in the rpm spec file.

Provides

Non-gem ruby libraries Must indicate what they provide with Provides: ruby(RUBYLIBRARY) = VERSION. The string RUBYLIBRARY Must be the same as what is used in the require statement in a Ruby script that uses the library. The VERSION should be the upstream version of the library, as long as upstream follows a sane versioning scheme. For example, a Ruby script using the SQLite database driver will include it with require 'sqlite3'. The specfile for the corresponding Fedora package must contain a line Provides: ruby(sqlite3) = 1.1.0, assuming the package contains version 1.1.0 of the library.

Gem packages must have two provides:

Provides: ruby(RUBYLIBRARY) = VERSION
Provides: rubygem(%{gem_name}) = %{version}

This allows people who are packaging something that requires the gem to use either form of the dependency to get the proper package. Since their package may only have a require 'rubylibrary' in the code, there's no reason for them to have to figure out whether we've packaged it as a gem or a non-gem library. The rubygem(%{gem_name}) form of the dependency allows packages which depend on having the gem metadata to specify that the Fedora package must be packaging a gem form of the library, not a non-gem form of the library.

Libraries

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.

Move text about interpreter independence to here
In Fedora we strive to make rubylibraries run on all versions of the interpreter (ruby, jruby, etc) that we ship. We may carry patches so that the libraries run on all versions of the interpreter and we install them to interpreter neutral locations.

RubyGems

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 ensures (1) that such RPM's fit cleanly with the rest of the distribution and (2) make it possible for the end user to satisfy dependencies of a Gem by installing the appropriate RPM-packaged Gem.

See above
About moving the next bit of text to the overall libraries portion.

In Fedora we strive to make rubygems run on all versions of the interpreter (ruby, jruby, etc) that we ship. We may carry patches so that gems run on all versions of the interpreter and we install them to interpreter neutral locations.

Both RPM's and Gems use similar terminology --- there are specfiles, package names, dependencies etc. for both. To keep confusion to a minimum, whenever the term from the Gem world is meant, it is explicitly called the 'Gem specification' (.gemspec). An unqualified 'package' in the following always means an RPM.

  • Spec files must contain a definition of %{gem_name}, which is the name from the Gem's specification.
  • The 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.
Confirm change:
I removed the Requires: ruby(rubygems) as ruby requires rubygems and the ruby api test will bring in ruby.
  • The package must BuildRequires: rubygems-devel to pull in the macros needed to build.

Building gems

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
%setup -q -T -n  %{gem_name}-%{version}
gem unpack %{SOURCE0}

pushd %{gem_name}-%{version}
gem spec %{SOURCE0} -l --ruby > %{gem_name}.gemspec

# Apply patches and modify the gemspec for different versions here
%patch0 -p1

popd

%build
mkdir -p ./%{gem_dir}

# Create the gem as gem install only works on a gem file
gem build %{gem_name}.gemspec
export CONFIGURE_ARGS="--with-cflags='%{optflags}'"
# gem install compiles any C extensions and installs into a directory
gem install -V --local --install-dir ./%{gem_dir} \
	--force	--rdoc ../%{gem_name}-%{version}/%{gem_name}-%{version}.gem

%install
mkdir -p %{buildroot}%{gem_dir}
mkdir -p %{buildroot}%{gem_extdir}/foo
cp -a .%{gem_dir}/* %{buildroot}%{gem_dir}/

# If there are C extensions, mv them to the extdir.
mv %{buildroot}%{gem_instdir}/foo/shared_object.so %{buildroot}%{gem_extdir}/foo/
Replacement instructions
Replace all of the following build instructions with the one above. The one above conforms to the standard spec file %prep, %build, and %install sections and allows patching in all instances.
TODO
Continue to merge the below instructions and explanations into this section.
  • The %prep section should contain the local gem install similar to this (assuming that the %{gem_name}-%{version}.gem is SOURCE0):
%setup -q -c -T
mkdir -p .%{gem_dir}
gem install \
	-V \
	--local \
        --install-dir .%{gem_dir} \
	--force \
	--rdoc \
	%{SOURCE0}
  • If the Gem contains executable files, you must ad --bindir .%{_bindir} option to the gem install command.
  • The %build section of the specfile should be empty.
  • The %install section should then be used to copy the files to appropriate directory structure under %{buildroot}, for example:
mkdir -p %{buildroot}%{gem_dir}
cp -a .%{gem_dir}/* \
        %{buildroot}%{gem_dir}/
  • If the Gem contains executable files, you must also add:
mkdir -p %{buildroot}%{_bindir}
cp -a .%{_bindir}/* \
        %{buildroot}%{_bindir}/
  • The Gem must be installed into %{gem_dir}.
  • The package must own the following files and directories:
%dir %{gem_instdir}
%{gem_libdir}
%{gem_spec}
%doc %{gem_docdir}
  • Since the Gem is installed using RPM, you must exclude the .gem file. This file is used typically with gem pristine command to restore the Gem into its original state, but this could be achieved by equivalent RPM command. Exclude the cached Gem like this:
%exclude %{gem_cache}
  • If the Gem only contains pure Ruby code, it must be marked as BuildArch: noarch. If the Gem contains binary content (e.g., for a database driver), it must be marked as architecture specific.

RubyGem with extension libraries written in C

Some Ruby Gems may contain extension libraries written in C. These Gems carry an extensions section in their Gem specification file. Their specfiles have to be slightly adjusted to reflect the presence of binary files.

  • The %prep section is the same as for pure Ruby Gem, but it must add CONFIGURE_ARGS before the gem install command:
%setup -q -c -T
mkdir -p .%{gem_dir}
export CONFIGURE_ARGS="--with-cflags='%{optflags}'"
gem install \
	-V \
	--local \
        --install-dir .%{gem_dir} \
	--force \
	--rdoc \
	%{SOURCE0}
  • The -V option should be used with gem install to check if CFLAGS is correctly honored.
  • During the %install section all architecture specific content must be moved from the %{gem_instdir} to the %{gem_extdir}. Usage of %{gem_extdir} assures that the binary files are placed under the proper directory under /usr according to FHS.
%install
mkdir -p %{buildroot}%{gem_dir}
mkdir -p %{buildroot}%{gem_extdir}/foo
cp -a .%{gem_dir}/* %{buildroot}%{gem_dir}/

mv %{buildroot}%{gem_instdir}/foo/shared_object.so %{buildroot}%{gem_extdir}/foo/
Library placement
Please note the foo subdirectory of %{buildroot}%{gem_extdir}. The foo must be replaced by first path defined by require_paths in Gem specification. Typically, it is replaced either by lib or ext.
  • The Gem package with C extension must own the %{gem_extdir} directory.
  • Installed C codes (usually under %{gem_instdir}/ext) may be removed even if gem contents %{gem_name} reports that installed C codes should be found there.

Packaging for Gem and non-Gem use

Packaging for non-Gem use is no longer needed
Originally, rubygem modules were not placed in ruby's library path, so we packaged rubygems for use with both gems and non-gems. The current rubygem module adds all gems to the ruby library path when it is require'd. So, packagers must not create non-Gem subpackages of rubygems for new packages. Since the majority of Ruby packages in Fedora are now packaged as installed gems, you may need to use require('rubygem') as early in the program as possible to ensure that these ruby components are properly found. Packages for ruby gems that currently create a non-gem subpackage should be adapted to stop shipping the non-gem subpackage (with a proper Obsoletes and Provides in the main rubygem package).

Applying Patches

There are several cases, when you need to apply patch to your gem. The following paragraphs tries to demonstrate how to solve the most basic scenarios.

Ruby Code

If you are applying a patch to the Ruby code (which is platform independent), the only thing you need to do is just to apply it in the %prep section after the gem install command, for example:

%prep
gem install \
	-V \
	--local \
        --install-dir .%{gem_dir} \
	--force \
	--rdoc \
	%{SOURCE0}

pushd .%{gem_instdir}
%patch0 -p1
popd
Binary Extensions

Sometimes, although you are able to successfully install the gem, you will later encounter some bug in its binary extension which needs patching. In this case, you first need to use the %patch macro as in the previous example and then you must recompile the extension in %build section. For example:

%prep
export CONFIGURE_ARGS="--with-cflags='%{optflags}'"
gem install \
	-V \
	--local \
        --install-dir .%{gem_dir} \
	--force \
	--rdoc \
	%{SOURCE0}

pushd .%{gem_instdir}
%patch0 -p1
popd

%build
pushd .%{gem_instdir}/ext
make %{?_smp_mflags}
popd
Binary Extension Fails to Build
Consider alternative package
If you find out that you need this approach, then probably the gem you are about to package is old and unmaintained. Please consider if there is not some more viable alternative.

If you are packaging a gem with binary extension, which is not ABI/API compatible with Ruby, the gem installation probably fails during compilation of the extension. In this case, you need to apply patch to fix this issue prior the installation. Unfortunately, there is no easy straight forward way how to achieve it ATM. Nevertheless, you can repack the gem with applied patch in %prep section and later install as always. For example:

%prep
%setup -q -c -T
pushd ..
gem unpack %{SOURCE0}

pushd %{gem_name}-%{version}
gem spec %{SOURCE0} -l --ruby > %{gem_name}.gemspec

%patch0 -p1

gem build %{gem_name}.gemspec
popd
popd

mkdir -p ./%{gem_dir}
export CONFIGURE_ARGS="--with-cflags='%{optflags}'"
gem install --local --install-dir ./%{gem_dir} \
	--force	../%{gem_name}-%{version}/%{gem_name}-%{version}.gem
Might help you
Please note that the unpacked gem might already contain:
  • Gem specification (.gemspec). If you like to use it, you must make sure that it is up-to-date.
  • Some upstream authors might have pre-prepared rake task, which can package the gem for you, instead of gem build command.
However, for consistency, you should to stay with the approach demonstrated by example above.

Tips for Packagers

Gems carry a lot of metadata; gem2rpm is a tool to generate an initial specfile and/or source RPM from a Gem. The generated specfile still needs some hand-editing, but conforms to 90% with this guideline.

Non-Gem Packages

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.


Build Architecture and File Placement

The following only affects the files that the package installs into %{_libdir}/ruby/vendor_ruby (architecture specific) and %{_datadir}/ruby/vendor_ruby (architecture independent), i.e. Ruby library files. All other files in a Ruby package must adhere to the general Fedora Extras packaging conventions.


Site versus Vendor
Previously, %{ruby_sitelibdir} and %{ruby_sitearchdir} were used. However, as they are meant only for local installations, please use %{ruby_vendorlibdir} and %{ruby_vendorarchdir} instead.

Pure Ruby packages

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.

Ruby packages with binary content/shared libraries

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

The CONFIGURE_ARGS definition applies to Ruby Gems, too.

Applications

By applications we mean:

  • programmes that provide user-level tools or
  • web applications, typically built using Rails, Sinatra or similar frameworks.

The RPM packages must obey FHS rules, it means that they should be installed into %{_datadir}. 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.

A good example is the initial specfile of deltacloud-core package (shortened here):

%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
%setup -q -c -T 
gem unpack -V --target=%{_builddir} %{SOURCE0}

%build

%install
mkdir -p %{buildroot}%{app_root}
mkdir -p %{buildroot}%{_initddir}
mkdir -p %{buildroot}%{_bindir}
cp -r %{_builddir}/%{name}-%{version}/* %{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, gem unpack is used instead of common gem install to extract the application and library files. These are then placed 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

  • added via another %SOURCE tags
Source1: deltacloudd-fedora
  • placed into appropriate locations during %install stage
install -m 0755 %{SOURCE1} %{buildroot}%{_bindir}/deltacloudd

Running test suite

If there is test suite available for the package (even separately, for example not included in the Gem, but available in the upstream repository), you should run it. The test suite is the only tool which could assure basic functionality of package. This is helpful when mass rebuild is required for example. You could skip test suite execution only in case when not all build dependencies are met, but this must be documented in specfile. Nevertheless, the build dependencies should be imported into Fedora as soon as possible.

The tests should not be run using Rake - Rake almost always draws in some unnecessary dependencies like hoe or gemcutter.

Testing frameworks usage

Ruby community provides and supports various testing frameworks. Following paragraphs demonstrates how the test suite can be executed. Please note that the list is not exhaustive.

MiniTest

MiniTest is default testing framework shipped together with Ruby, however unbundled in Fedora (you must use BuildRequires: rubygem(minitest)). To run the tests using MiniTest, you can usually use something like

testrb -Ilib test

Test::UNIT

To run the tests using Test::Unit (you must use BuildRequires: rubygem(test-unit)), you can usually use something like

testrb2 -Ilib test

Please note that test suite which runs using Test::UNIT can be typically executed also by MiniTest. In that case, please prefer MiniTest.

RSpec

To run the tests using RSpec >= 2 (you must use BuildRequires: rubygem(rspec-core)), you can usually use something like

rspec -Ilib spec

Test suite not included in package

Typical way of getting the tests separately from upstream follows - lets suppose you're packaging rubygem-delorean, version 1.2.0, which is hosted on Git. 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

# ...

%check
pushd .%{gem_instdir}
tar xzf %{SOURCE1}
popd

# ...

  • Make sure to include the version of the tests in the source name, so that when updating to new version, rpmbuild will fail because it won't find the proper %SOURCE1 (and this will remind you to update the tests, too).
  • Add the commands you used to get the tests into the specfile as comments. This will make it a lot easier the next time you will need to get them.
  • Run the tests as you normally would.