From Fedora Project Wiki
mNo edit summary
(Initial rewrite)
Line 7: Line 7:
There are three basic categories of ruby packages: [[#RubyGems | ruby gems]], [[#Non-Gem Packages | non-gem ruby packages]], and [[#ruby_applications| 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.
There are three basic categories of ruby packages: [[#RubyGems | ruby gems]], [[#Non-Gem Packages | non-gem ruby packages]], and [[#ruby_applications| 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.


== Ruby ABI ==
== Ruby Compatibility ==


Each Ruby package '''must''' indicate the Ruby ABI version it depends on with a line like
Each Ruby package '''must''' indicate it depends on a Ruby interpreter. Use ruby(release) virtual requirement to achieve that:
<pre>
<pre>
Requires: ruby(abi) = 1.9.1
Requires: ruby(release)
</pre>
</pre>
If the package requires Ruby of certain version(s), make the requirement versioned like this:
<pre>
Requires: ruby(release) >= 1.9.1
</pre>
{{admon/note|Alternate interpreters| Alternate Ruby interpreters (currently JRuby) also <code>Provide: ruby(release)</code>. This implies, that pure RubyGems packages (these are shared among interpreters) '''must not''' have <code>Requires: ruby</code> or <code>Requires: jruby</code> to have their dependencies satisfied by any of these interpreters.}}
{{admon/note|Over specified <code>ruby(release)</code> versioning|Please note, that if the  <code>ruby(release)</code> version requirement is too specific, it may cause an unexpected interpreter to be drawn in. E.g. <code>ruby(release) = 1.8</code> will require JRuby package, since it is the only package that provides it.}}


{{Anchor|ruby_naming}}
{{Anchor|ruby_naming}}
== Naming Guidelines ==
== Naming Guidelines ==


* Packages that contain Ruby Gems '''must''' be called <code>rubygem-%{gem_name}</code>.
* Packages that contain Ruby Gems '''must''' be called <code>rubygem-%{gem_name}</code>.


* The name of a ruby extension/library package '''must''' be of the form <code>ruby-UPSTREAM</code>. If the upstream name <code>UPSTREAM</code> contains <code>ruby</code>, that '''should''' be dropped from the name. For example, the SQLite database driver for ruby is called <code>sqlite3-ruby</code>. The corresponding Fedora package should be called <code>ruby-sqlite3</code>, and not <code>ruby-sqlite3-ruby</code>.
* The name of a ruby extension/library package '''must''' start with the interpreter it is built for (ruby, jruby, etc) and then the <code>UPSTREAM</code> name.  For example: <code>ruby-UPSTREAM</code>. If the upstream name <code>UPSTREAM</code> contains <code>ruby</code>, that '''should''' be dropped from the name. For example, the SQLite database driver for ruby is called <code>sqlite3-ruby</code>. The corresponding Fedora package should be called <code>ruby-sqlite3</code>, and not <code>ruby-sqlite3-ruby</code>.


* Application packages that mainly provide user-level tools that happen to be written in Ruby '''must''' follow the general [[Packaging/NamingGuidelines|  NamingGuidelines]]  instead.
* Application packages that mainly provide user-level tools that happen to be written in Ruby '''must''' follow the general [[Packaging/NamingGuidelines|  NamingGuidelines]]  instead.


== Macros ==
== Macros ==
{{admon/question|Interpreter independence|Related to the below text about interpreter independence.  Move vendorlib/vendorarch to a non-MRI specific location}}
{{admon/tip|Interpreter indendence|The vendordirs are intentionally placed under the MRI Ruby directory, because many libraries and applications use them to install their Ruby bindings - bindings that are meant only for MRI Ruby. Therefore, the vendordirs should stay where they are now.}}


Non-gem ruby packages and ruby gem packages install to certain standard locations.  The <code>ruby-devel</code> and <code>rubygems-devel</code> packages contain macros useful for the respective package types.
Non-gem ruby packages and ruby gem packages install to certain standard locations.  The <code>ruby-devel</code> and <code>rubygems-devel</code> packages contain macros useful for the respective package types.
Line 36: Line 39:
!Usage
!Usage
|-
|-
! colspan="3"|From ruby-devel; intended for non-gem packages
! colspan="3"|From ruby-devel; intended for non-gem packages.
|-
|-
|<code>%{ruby_vendorarchdir}</code>
|<code>%{ruby_vendorarchdir}</code>
Line 57: Line 60:
|-
|-
|<code>%{gem_dir}</code>
|<code>%{gem_dir}</code>
|/usr/share/gems
|/usr/share/rubygems
|Top directory for the Gem structure.
|Top directory for the Gem structure.
|-
|-
Line 80: Line 83:
|The rdoc documentation of the Gem.
|The rdoc documentation of the Gem.
|-
|-
|<code>%{gem_extdir}</code>
|<code>%{gem_extdir_mri}</code>
|%{_libdir}/gems/exts/%{gem_name}-%{version}
|%{_libdir}/gems/ruby/%{gem_name}-%{version}
|The directory for binary Gem extensions.
|The directory for MRI Ruby Gem extensions.
|}
|}


== Dependencies ==
=== Interpreter independence and directory macros ===
 
Due to having multiple types of libraries (gems and non-gems) there are two ways to specify dependencies.
 
{{admon/question|Confirm change: rubygems to provide ruby()| Since rubygems now work with plain <code>require</code> 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.}}
{{admon/tip|Confirm change: rubygems to provide ruby()|This is not a good move. The ruby() provides should be left to non-gem libraries, that can be required even with the <code>--disable-gems</code> option, while rubygem() provides should be only present for packages, which are only loadable via RubyGems library.}}


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 (for instance, <code>fork</code>).  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.


=== Requires ===
The standard ruby <code>%{vendorlib}</code> directories lack this facilityFor 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.
If your package uses <code>gem 'rubylibrary'</code> or you're packaging a ruby gem that depends on the library in its <code>.gemspec</code> (See the section on [[#Building gems| building gems]] for more information) you '''Must''' use <code>Requires: rubygem(rubylibrary)</code> so the rpm will pull in the correct ruby library.
 
If your package just uses <code>require 'rubylibrary'</code> it's always fine to use <code>Requires: ruby(rubylibrary)</code>.  If you know that the ruby library is provided as a gem despite using <code>require 'rubylibrary'</code> you may use <code>Requires: rubygem(rubylibrary)</code> instead but this is not required as it forces packagers to know unnecessary details of how another ruby package has been created.
{{admon/tip|Not true in Ruby world|The upstream of the package in Ruby world always uses a specific type of a library. Gems in most cases, sometimes non-gem libraries. But they always need the specific one (again, take <code>--disable-gems</code> into account).}}
 
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 <code>.gemspec</code> to work with the Fedora version of the package (see the section on [[#Building gems| 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 <code>Provides: ruby(RUBYLIBRARY) = VERSION</code>.  The string <code>RUBYLIBRARY</code> '''Must''' be the same as what is used in the <code>require</code> statement in a Ruby script that uses the library.  The <code>VERSION</code> '''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 <code>require 'sqlite3'</code>.  The specfile for the corresponding Fedora package must contain a line <code>Provides: ruby(sqlite3) = 1.1.0</code>, assuming the package contains version 1.1.0 of the library.
 
Gem packages '''must''' have two provides:
<pre>
Provides: ruby(RUBYLIBRARY) = VERSION
Provides: rubygem(%{gem_name}) = %{version}
</pre>
 
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 <code>require 'rubylibrary'</code> 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 <code>rubygem(%{gem_name})</code> 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 ==
== 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|ruby applications guidelines]] instead.
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|ruby applications guidelines]] instead.
{{admon/question|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.}}
{{admon/tip|Interpreter indendence|Basically the only libraries shareable (and that are meant to be shareable) among all the interpreters are platform independent Gems. These are currently under /usr/share/gems, so this is in fact done. The non-Gem libraries are often usable only on one platform by making some assumptions in their code. This is another reason for keeping the vendordirs under MRI Ruby structure.}}


=== RubyGems ===
=== RubyGems ===


[http://www.rubygems.org/ 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.
[http://www.rubygems.org/ 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.
 
{{admon/question|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, 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.
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.


* Spec files '''must''' contain a definition of <code>%{gem_name}</code>, which is the name from the Gem's specification.
* Spec files '''must''' contain a definition of <code>%{gem_name}</code> which is the name from the Gem's specification.
* The <code>Source</code> of the package '''must''' be the full URL to the released Gem archive; the version of the package '''must''' be the Gem's version.
* The <code>Source</code> of the package '''must''' be the full URL to the released Gem archive; the version of the package '''must''' be the Gem's version.
* The package '''must''' <code>BuildRequires: rubygems-devel</code> to pull in the macros needed to build.
* The package '''must''' <code>BuildRequires: rubygems-devel</code> to pull in the macros needed to build.
* The package '''must''' <code>Requires: rubygems</code>, since it owns the RubyGems directory structure.
* If the package is pure Ruby, it '''must not''' <code>Requires: ruby</code> or <code>Requires: jruby</code>.
* If the package contains C extensions, it '''must''' <code>Requires: ruby</code>.


==== Building gems ====
==== Building gems ====
Line 142: Line 117:
<pre>
<pre>
%prep
%prep
%setup -q -T -n  %{gem_name}-%{version}
gem unpack %{SOURCE0}
gem unpack %{SOURCE0}
pushd %{gem_name}-%{version}
%setup -q -D -T -n  %{gem_name}-%{version}


gem spec %{SOURCE0} -l --ruby > %{gem_name}.gemspec
gem spec %{SOURCE0} -l --ruby > %{gem_name}.gemspec
# Modify the gemspec if necessary with a patch or sed
# Modify the gemspec if necessary with a patch or sed
# Also apply patches to code if necessary
# Also apply patches to code if necessary
%patch0 -p1
%patch0 -p1
popd


%build
%build
mkdir -p ./%{gem_dir}
mkdir -p ./%{gem_dir}
mkdir -p ./%{_bindir}


# Create the gem as gem install only works on a gem file
# Create the gem as gem install only works on a gem file
gem build %{gem_name}.gemspec
gem build %{gem_name}.gemspec
export CONFIGURE_ARGS="--with-cflags='%{optflags}'"
export CONFIGURE_ARGS="--with-cflags='%{optflags}'"
# gem install compiles any C extensions and installs into a directory
# gem install compiles any C extensions and installs into a directory
# We set that to be a local directory so that we can move it into the
# buildroot in %%install
gem install -V \
gem install -V \
         --local \
         --local \
         --install-dir ./%{gem_dir} \
         --install-dir ./%{gem_dir} \
         --bindir .%{_bindir} \
         --bindir ./%{_bindir} \
         --force \
         --force \
         --rdoc
         --rdoc \
         ../%{gem_name}-%{version}/%{gem_name}-%{version}.gem
         %{gem_name}-%{version}.gem


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


# If there were programs installed:
# If there were programs installed:
mkdir -p %{buildroot}%{_bindir}
mkdir -p %{buildroot}%{_bindir}
cp -a .%{_bindir}/* %{buildroot}%{_bindir}
cp -a ./%{_bindir}/* %{buildroot}%{_bindir}
 


# If there are C extensions, mv them to the extdir.
# If there are C extensions, mv them to the extdir.
# $REQUIRE_PATHS is taken from the first value of the require_paths field in
# You must replace REQUIRE_PATHS according to your gem specifics.
# the gemspec file.  It will typically be either "lib" or "ext".  For instance:
mkdir -p %{buildroot}%{gem_extdir}/REQUIRE_PATHS
#  s.require_paths = ["lib"]
mv %{buildroot}%{gem_instdir}/REQUIRE_PATHS/shared_object.so %{buildroot}%{gem_extdir}/REQUIRE_PATHS/
mkdir -p %{buildroot}%{gem_extdir}/$REQUIRE_PATHS
mv %{buildroot}%{gem_instdir}/$REQUIRE_PATHS/shared_object.so %{buildroot}%{gem_extdir}/$REQUIRE_PATHS/
</pre>
</pre>


===== %prep =====
You should replace REQUIRE_PATHS with the first value of the require_paths field in the gemspec file.  It will typically be either "lib" or "ext"For instance if you find following line in your gemspec file:
In the <code>%prep</code> section we first use <code>%setup -n %{gem_name}-%{version}</code> to tell rpm what the directory the gem will unpack into.  We use the <code>-T</code> flag to tell <code>%setup</code> not to try unpacking the source on its own.  Then we explicitly use <code>gem unpack</code> to extract the source from the gem and switch into the directory.  This is equivalent to the steps that <code>%setup</code> would take if gem was a recognized archive format.
 
We then run <code>gem spec</code> to output the metadata from the gem into a file.  This <code>.gemspec</code> file will be used to rebuild the gem laterIf we need to modify the <code>.gemspec</code> (for instance, if the version of dependencies is wrong for Fedora or the <code>.gemspec</code> is using old, no longer supported fields) we would do it here.  Patches to the code itself can also be done here.
 
{{admon/question|Give examples?|We could give examples of modifying the version of a dependency and of commond fields that might need to be removed here.}}
 
===== %build =====
Next we build the gem.  The first step is to create directories in which to temporarily install the built sources.  We do this because the <code>gem install</code> 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 <code>%install</code>.  Because <code>gem install</code> only operates on gem archives, we next recreate the gem with <code>gem build</code>.  The gem file that is created can then be used by <code>gem install</code> to build and install the code into the temporary directory we created earlier.
 
{{admon/question|Do we want to tell what the arguments to gem install do?| The previous guideline did this only for -V (allows checking that CFLAGS was honored)}}
 
===== %install =====
Here we actually install into the <code>%{buildroot}</code>.  We create the directories that we need and then copy what was installed into the temporary directories into the <code>%{buildroot}</code> hierarchy.  Finally, if this ruby gem creates shared objects the shared objects are moved into the arch specific <code>%{gem_extdir}</code> path.
 
{{admon/question|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.}}
{{admon/question|TODO| Continue to merge the below instructions and explanations into this section.}}
 
{{admon/tip|Following lines summarize the approach suggested by Ruby SIG|Advantages: significantly shorter; obvious to people from Ruby world, who would want to start packaging; effective in 99 % of cases; doesn't include the unnecessary patching of gemspec generated by <code>gem spec</code> command. Recommendation: the only case, when the FPC suggested approach is really needed is when the gem extension fails to build, so leave it as a fallback for these cases (has been needed for one out of cca 250 packages so far).}}
 
* The <code>%prep</code> section '''should''' contain the local <code>gem install</code> similar to this (assuming that the <code>%{gem_name}-%{version}.gem</code> is <code>SOURCE0</code>):
<pre>
<pre>
%setup -q -c -T
  s.require_paths = ["lib"]
mkdir -p .%{gem_dir}
gem install \
-V \
--local \
        --install-dir .%{gem_dir} \
--force \
--rdoc \
%{SOURCE0}
</pre>
</pre>
* If the Gem contains executable files, you '''must''' ad <code>--bindir .%{_bindir}</code> option to the <code>gem install</code> command.
* The <code>%build</code> section of the specfile '''should''' be empty.
* The <code>%install</code> section should then be used to copy the files to appropriate directory structure under <code>%{buildroot}</code>, for example:
<pre>
mkdir -p %{buildroot}%{gem_dir}
cp -a .%{gem_dir}/* \
        %{buildroot}%{gem_dir}/
</pre>
* If the Gem contains executable files, you '''must''' also add:
<pre>
mkdir -p %{buildroot}%{_bindir}
cp -a .%{_bindir}/* \
        %{buildroot}%{_bindir}/
</pre>
* The Gem '''must''' be installed into <code>%{gem_dir}</code>.
* The package '''must''' own the following files and directories:
<pre>
%dir %{gem_instdir}
%{gem_libdir}
%{gem_spec}
%doc %{gem_docdir}
</pre>
* Since the Gem is installed using RPM, you '''must''' exclude the .gem file. This file is used typically with <code>gem pristine</code> command to restore the Gem into its original state, but this could be achieved by equivalent RPM command. Exclude the cached Gem like this:
<pre>
%exclude %{gem_cache}
</pre>
* If the Gem only contains pure Ruby code, it '''must''' be marked as <code>BuildArch: noarch</code>. 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 ====
then the example above would look like:
 
Some Ruby Gems may contain extension libraries written in C. These Gems carry an [http://docs.rubygems.org/read/chapter/20#extensions 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|CONFIGURE_ARGS]] before the <code>gem install</code> command:
<pre>
<pre>
%setup -q -c -T
mkdir -p %{buildroot}%{gem_extdir}/lib
mkdir -p .%{gem_dir}
mv %{buildroot}%{gem_instdir}/lib/shared_object.so %{buildroot}%{gem_extdir}/lib/
export CONFIGURE_ARGS="--with-cflags='%{optflags}'"
gem install \
-V \
--local \
        --install-dir .%{gem_dir} \
--force \
--rdoc \
%{SOURCE0}
</pre>
</pre>
* The <code>-V</code> option '''should''' be used with <code>gem install</code> to check if <code>CFLAGS</code> is correctly honored.
* During the <code>%install</code> section all architecture specific content '''must''' be moved from the <code>%{gem_instdir}</code> to the <code>%{gem_extdir}</code>. Usage of %{gem_extdir} assures that the binary files are placed under the proper directory under <code>/usr</code> according to FHS.
<pre>
%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/
===== %prep =====
</pre>
Since gems aren't an archive format that rpm recognizes, the first thing we have to do is explicitly use <code>gem unpack</code> to extract the source from the gem.  Then we call <code>%setup -n %{gem_name}-%{version}</code> to tell rpm what the directory the gem has unpacked into.  The [http://www.rpm.org/max-rpm/s1-rpm-inside-macros.html#S3-RPM-INSIDE-SETUP-T-OPTION <code>-T</code>] and [http://www.rpm.org/max-rpm/s1-rpm-inside-macros.html#S3-RPM-INSIDE-SETUP-D-OPTION <code>-D</code>] flags tell <code>%setup</code> that we've already unpacked the code


{{admon/note|Library placement|Please note the <code>foo</code> subdirectory of <code>%{buildroot}%{gem_extdir}</code>. The <code>foo</code> '''must''' be replaced by first path defined by <code>require_paths</code> in Gem specification. Typically, it is replaced either by <code>lib</code> or <code>ext</code>.}}
We then run <code>gem spec</code> to output the metadata from the gem into a file.  This <code>.gemspec</code> file will be used to rebuild the gem later.  If we need to modify the <code>.gemspec</code> (for instance, if the version of dependencies is wrong for Fedora or the <code>.gemspec</code> is using old, no longer supported fields) we would do it here.  Patches to the code itself can also be done here.


* The Gem package with C extension '''must''' own the <code>%{gem_extdir}</code> directory.
===== %build =====
* Installed C codes (usually under <code>%{gem_instdir}/ext</code>) '''may''' be removed even if <code>gem contents %{gem_name}</code> reports that installed C codes should be found there.
Next we build the gem.  The first step is to create directories in which to temporarily install the built sources.  We do this because the <code>gem install</code> 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 <code>%install</code>.  Because <code>gem install</code> only operates on gem archives, we next recreate the gem with <code>gem build</code>.  The gem file that is created is then used by <code>gem install</code> to build and install the code into the temporary directory we created earlier.


==== Packaging for Gem and non-Gem use ====
===== %install =====
Here we actually install into the <code>%{buildroot}</code>.  We create the directories that we need and then copy what was installed into the temporary directories into the <code>%{buildroot}</code> hierarchy.  Finally, if this ruby gem creates shared objects the shared objects are moved into the arch specific <code>%{gem_extdir}</code> path.


{{admon/caution|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-gemsThe current <code>rubygem</code> module adds all gems to the ruby library path when it is <code>require</code>'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 <code>require('rubygem')</code> 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 [[Packaging:Guidelines#Renaming.2FReplacing_Existing_Packages | proper Obsoletes and Provides]] in the main rubygem package).}}
==== Patching required gem versions ====
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 <code>add_runtime_dependency</code> call in the gemspec and patch it with the corrected version similar to this:


{{admon/tip|Has this been changed by FPC?|AFAIK, this should say something like: "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 <code>rubygem</code> module adds all gems to the ruby library path when it is <code>require</code>'d. So, packagers '''must not''' create non-Gem subpackages of rubygems for new packages. Another reason for not creating the non-gem subpackages for majority of gems is the danger of breaking applications, that rely on some library being unrequireable with <code>--disable-gems</code> option."}}
==== 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 <code>gem install</code> command, for example:
<pre>
<pre>
%prep
Gem::Specification.new do |s|
gem install \
  # [...]
-V \
- s.add_runtime_dependency(%q<mail>, ["~> 2.2.19"])
--local \
+  s.add_runtime_dependency(%q<mail>, [">= 2.3.0"])
        --install-dir .%{gem_dir} \
  # [...]
--force \
end
--rdoc \
%{SOURCE0}
 
pushd .%{gem_instdir}
%patch0 -p1
popd
</pre>
</pre>


===== Binary Extensions =====
{{admon/warning|Be sure to test|Do not simply change versions without testing that the new version works.  There are times the upstream is overly strict but there are also times when the version requirement was specified because a specific bug was encountered or the API changed in a minor release.}}


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:
=== Packaging for Gem and non-Gem use ===
<pre>
%prep
export CONFIGURE_ARGS="--with-cflags='%{optflags}'"
gem install \
-V \
--local \
        --install-dir .%{gem_dir} \
--force \
--rdoc \
%{SOURCE0}


pushd .%{gem_instdir}
{{admon/caution|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. This allowed code that used <code>require('ARubyModulePackagedAsAGem');</code> to function. The current <code>rubygem</code> module adds all gems to the ruby library path when it is <code>require</code>'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 patch the code to use <code>require('rubygem')</code> 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 [[Packaging:Guidelines#Renaming.2FReplacing_Existing_Packages | proper Obsoletes and Provides]] in the main rubygem package).}}
%patch0 -p1
popd
 
%build
pushd .%{gem_instdir}/ext
make %{?_smp_mflags}
popd
</pre>
 
===== Binary Extension Fails to Build =====
 
{{admon/warning|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:
 
<pre>
%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
</pre>
 
{{admon/note|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 <code>gem build</code> command.
 
However, for consistency, you '''should''' to stay with the approach demonstrated by example above.}}
 
==== Tips for Packagers ====
 
Gems carry a lot of metadata; [https://github.com/lutter/gem2rpm 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 Packages ===
Line 373: Line 205:


==== Build Architecture and File Placement ====
==== Build Architecture and File Placement ====
The following only affects the files that the package installs into <code>%{_libdir}/ruby/vendor_ruby</code> (architecture specific) and <code>%{_datadir}/ruby/vendor_ruby</code> (architecture independent), i.e. Ruby library files. All other files in a Ruby package must adhere to the general Fedora Extras packaging conventions.
The following only affects the files that the package installs into <code>%{ruby_vendorarchdir} </code> and <code>%{ruby_vendorlibdir}</code> (the actual Ruby library files). All other files in a Ruby package must adhere to the general Fedora packaging conventions.




Line 405: Line 237:
== Applications ==
== Applications ==


By applications we mean:
Applications are
* programmes that provide user-level tools or
 
* programs that provide user-level tools or
* web applications, typically built using Rails, Sinatra or similar frameworks.  
* 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:
The RPM packages '''must''' obey FHS rules.  They should be installed into <code>%{_datadir}</code>. The following macro can help you:
<pre>
<pre>
%global app_root %{_datadir}/%{name}
%global app_root %{_datadir}/%{name}
</pre>
</pre>
These packages typically have no "Provides" section, since no other libraries or applications depend on them.
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):
Here's an abbreviated example:
 
<pre>
<pre>
%global app_root %{_datadir}/%{name}
%global app_root %{_datadir}/%{name}
Line 427: Line 262:
URL: http://incubator.apache.org/deltacloud
URL: http://incubator.apache.org/deltacloud
Source0: http://gems.rubyforge.org/gems/%{name}-%{version}.gem
Source0: http://gems.rubyforge.org/gems/%{name}-%{version}.gem
Requires: rubygem(haml)
Requires: rubygem-haml
#...
#...
Requires(post):  chkconfig
Requires(post):  chkconfig
#...
#...
BuildRequires: rubygem(haml)
BuildRequires: rubygem-haml
#...
#...
BuildArch: noarch
BuildArch: noarch
Line 450: Line 285:


%prep
%prep
%setup -q -c -T  
gem unpack -V %{SOURCE0}
gem unpack -V --target=%{_builddir} %{SOURCE0}
%setup -q -D -T -n %{name}-%{version}


%build
%build
Line 459: Line 294:
mkdir -p %{buildroot}%{_initddir}
mkdir -p %{buildroot}%{_initddir}
mkdir -p %{buildroot}%{_bindir}
mkdir -p %{buildroot}%{_bindir}
cp -r %{_builddir}/%{name}-%{version}/* %{buildroot}%{app_root}
cp -r * %{buildroot}%{app_root}
mv %{buildroot}%{app_root}/support/fedora/%{name} %{buildroot}%{_initddir}
mv %{buildroot}%{app_root}/support/fedora/%{name} %{buildroot}%{_initddir}
find %{buildroot}%{app_root}/lib -type f | xargs chmod -x
find %{buildroot}%{app_root}/lib -type f | xargs chmod -x
Line 488: Line 323:
</pre>
</pre>


Note, that although the source is a RubyGem, <code>gem unpack</code> is used instead of common <code>gem install</code> to extract the application and library files. These are then placed under %{_datadir}/%{name}, %{_bindir}, etc. to follow FHS and general packaging guidelines.
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 <code>.service</code> files, configurations) are required, they '''should''' be
If additional Fedora specific files (systemd <code>.service</code> files, configurations) are required, they '''should''' be
* added via another <code>%SOURCE</code> tags
* added via another <code>%SOURCE</code> tags
<pre>
<pre>
Line 499: Line 333:
</pre>
</pre>


== Running test suite ==
== Running test suites ==


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 automated tool which could assure basic functionality of package. This is helpful when mass rebuild is required for example. You could skip test suite execution 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.
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 <code>%check</code>. 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.
The tests '''should not''' be run using Rake - Rake almost always draws in some unnecessary dependencies like hoe or gemcutter.
{{admon/note|Do not ship tests|Normally tests are only run at package buildtime.  They should not be included in the binary rpms that users install on their systems.  You may make an exception for this if the package makes public use of the test suite at runtime (for instance, an application package that has a <code>--selftest</code> command line switch that runs its testsuite.)}}


=== Testing frameworks usage ===
=== 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.
The Ruby community supports many testing frameworks. The following sections demonstrate how several to execute several of the more common test suites.


==== MiniTest ====
==== 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
MiniTest is the default testing framework shipped with Ruby.  It is unbundled in Fedora (you must use <code>BuildRequires: rubygem-minitest</code>). To run tests using MiniTest you can use something like:
 
<pre>
<pre>
%check
testrb -Ilib test
testrb -Ilib test
</pre>
</pre>
Line 518: Line 356:
==== Test::UNIT ====
==== Test::UNIT ====


To run the tests using Test::Unit (you must use <code>BuildRequires: rubygem(test-unit)</code>), you can usually use something like
To run tests using Test::Unit you must <code>BuildRequires: rubygem-test-unit-</code> and use something like this:
 
<pre>
<pre>
%check
testrb2 -Ilib test
testrb2 -Ilib test
</pre>
</pre>


{{Template:Note}} Please note that test suite which runs using Test::UNIT can be typically executed also by MiniTest. In that case, please prefer MiniTest.
{{admon/note|| Test suites which run using Test::UNIT can typically also be executed by MiniTest. In that case, please prefer MiniTest.}}


==== RSpec ====
==== RSpec ====


To run the tests using RSpec >= 2 (you must use <code>BuildRequires: rubygem(rspec)</code>), you can usually use something like
To run tests using RSpec >= 2 you must <code>BuildRequires: rubygem-rspec</code> and use something like:
 
<pre>
<pre>
%check
rspec -Ilib spec
rspec -Ilib spec
</pre>
</pre>


=== Test suite not included in package ===
=== Test suites not included in the package ===
 
Sometimes you have to get the tests separately from upstream's gem package.  As an example lets suppose you're packaging <code>rubygem-delorean</code>, 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:


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:
<pre>
<pre>
# git clone https://github.com/bebanjo/delorean.git && cd delorean
# git clone https://github.com/bebanjo/delorean.git && cd delorean
Line 540: Line 383:
# tar -czf rubygem-delorean-1.2.0-specs.tgz spec/
# tar -czf rubygem-delorean-1.2.0-specs.tgz spec/
Source1: %{name}-%{version}-specs.tgz
Source1: %{name}-%{version}-specs.tgz
# ...
%prep
%setup -q -D -T -n %{gem_name}-%{version}
tar -xzf %{SOURCE1}


# ...
# ...


%check
%check
pushd .%{gem_instdir}
cp -pr spec/ ./%{gem_instdir}
tar xzf %{SOURCE1}
pushd ./%{gem_instdir}
# Run tests
rm -rf spec
popd
popd


Line 551: Line 402:


</pre>
</pre>
* 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).
* 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 <code>%{SOURCE1}</code> (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.
* 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.
* Run the tests as you normally would.
[[Category:Packaging guidelines drafts]]

Revision as of 11:47, 15 January 2013

Warning.png
This page is a draft only
It is still under construction and content may change. Do not rely on the information on this page.
Idea.png
Suggestions by Ruby SIG
Comments marked as this one are notes to FPC changes from Ruby SIG.
Warning.png
Different guidelines for older Fedora and RHEL
In RHEL 4, 5, and 6 and Fedora before Fedora 16, a ruby version that does not have all the features needed to support these guidelines exists. 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. 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.

Ruby Compatibility

Each Ruby package must indicate it depends on a Ruby interpreter. 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
Note.png
Alternate interpreters
Alternate Ruby interpreters (currently JRuby) also Provide: ruby(release). This implies, that pure RubyGems packages (these are shared among interpreters) must not have Requires: ruby or Requires: jruby to have their dependencies satisfied by any of these interpreters.
Note.png
Over specified ruby(release) versioning

Naming Guidelines

  • Packages that contain Ruby Gems must be called rubygem-%{gem_name}.
  • The name of a ruby extension/library package must start with the interpreter it is built for (ruby, jruby, etc) and then the 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.
  • Application packages that mainly provide user-level tools that happen to be written in Ruby must follow the general NamingGuidelines instead.

Macros

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/rubygems 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.

Interpreter independence and directory macros

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 (for instance, fork). 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.

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.

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 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.

  • 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.
  • The package must BuildRequires: rubygems-devel to pull in the macros needed to build.
  • The package must Requires: rubygems, since it owns the RubyGems directory structure.
  • If the package is pure Ruby, it must not Requires: ruby or Requires: jruby.
  • If the package contains C extensions, it must Requires: ruby.

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
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
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
# We set that to be a local directory so that we can move it into the
# buildroot in %%install
gem install -V \
        --local \
        --install-dir ./%{gem_dir} \
        --bindir ./%{_bindir} \
        --force \
        --rdoc \
        %{gem_name}-%{version}.gem

%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, mv them to the extdir.
# You must replace REQUIRE_PATHS according to your gem specifics.
mkdir -p %{buildroot}%{gem_extdir}/REQUIRE_PATHS
mv %{buildroot}%{gem_instdir}/REQUIRE_PATHS/shared_object.so %{buildroot}%{gem_extdir}/REQUIRE_PATHS/

You should replace REQUIRE_PATHS with the first value of the require_paths field in the gemspec file. It will typically be either "lib" or "ext". For instance if you find following line in your gemspec file:

   s.require_paths = ["lib"]

then the example above would look like:

mkdir -p %{buildroot}%{gem_extdir}/lib
mv %{buildroot}%{gem_instdir}/lib/shared_object.so %{buildroot}%{gem_extdir}/lib/
%prep

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.

%build

Next we build the gem. The first step is to create directories in which to temporarily install the built sources. 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. 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 we created earlier.

%install

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} path.

Patching required gem versions

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
Warning.png
Be sure to test
Do not simply change versions without testing that the new version works. There are times the upstream is overly strict but there are also times when the version requirement was specified because a specific bug was encountered or the API changed in a minor release.

Packaging for Gem and non-Gem use

Stop (medium size).png
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. This allowed code that used require('ARubyModulePackagedAsAGem'); to function. 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 patch the code 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).

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 %{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.


Warning.png
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

Applications

Applications are

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

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

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

Running test suites

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.

Note.png
Do not ship tests
Normally tests are only run at package buildtime. They should not be included in the binary rpms that users install on their systems. You may make an exception for this if the package makes public use of the test suite at runtime (for instance, an application package that has a --selftest command line switch that runs its testsuite.)

Testing frameworks usage

The Ruby community supports many testing frameworks. The following sections demonstrate how several to execute several of the more common test suites.

MiniTest

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
testrb -Ilib test

Test::UNIT

To run tests using Test::Unit you must BuildRequires: rubygem-test-unit- and use something like this:

%check
testrb2 -Ilib test
Note.png
Test suites which run using Test::UNIT can typically also be executed by MiniTest. In that case, please prefer MiniTest.

RSpec

To run tests using RSpec >= 2 you must BuildRequires: rubygem-rspec and use something like:

%check
rspec -Ilib spec

Test suites not included in the package

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

# ...

  • 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.