m (moved Uer:Toshio/RubyPackagingDraft to User:Toshio/RubyPackagingDraft: Typo in URL) |
(Some other virt provides to package deps) |
||
(40 intermediate revisions by the same user not shown) | |||
Line 11: | Line 11: | ||
Requires: ruby(abi) = 1.9.1 | Requires: ruby(abi) = 1.9.1 | ||
</pre> | </pre> | ||
{{admon/note|Alternate interpreters| In the future, alternate ruby interpreters (for instance, jruby) may also <code>Provide: ruby(abi)</code>. For the time being, <code>ruby(abi)</code> refers to the standard ruby interpreter. A site that wishes to run their ruby code on jruby but uses rpm packaged gems will end up with the standard ruby interpreter installed on the system even though their code runs on jruby.}} | |||
{{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''' | * 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 == | ||
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 32: | Line 33: | ||
!Usage | !Usage | ||
|- | |- | ||
! colspan="3"|From ruby-devel; intended for non-gem packages | ! colspan="3"|From ruby-devel; intended for non-gem packages. Alternate ruby interpreters will have equivalent locations (To be added to this table later) | ||
|- | |- | ||
|<code>%{ruby_vendorarchdir}</code> | |<code>%{ruby_vendorarchdir}</code> | ||
Line 53: | Line 54: | ||
|- | |- | ||
|<code>%{gem_dir}</code> | |<code>%{gem_dir}</code> | ||
|/usr/share/ | |/usr/share/rubygems | ||
|Top directory for the Gem structure. | |Top directory for the Gem structure. | ||
|- | |- | ||
Line 81: | Line 82: | ||
|} | |} | ||
== | === 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, <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. | |||
The standard ruby <code>%{vendorlib}</code> 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 == | == 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. | ||
=== 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 | [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. | ||
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> | * 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. | ||
Line 134: | Line 108: | ||
<pre> | <pre> | ||
%prep | %prep | ||
gem unpack %{SOURCE0} | gem unpack %{SOURCE0} | ||
%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 | ||
%build | %build | ||
mkdir -p . | mkdir -p .%{gem_dir} | ||
# 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 | |||
%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. | ||
# | # 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: | # the gemspec file. It will typically be either "lib" or "ext". For instance: | ||
# s.require_paths = ["lib"] | # s.require_paths = ["lib"] | ||
mkdir -p %{buildroot}%{gem_extdir}/ | mkdir -p %{buildroot}%{gem_extdir}/REQUIRE_PATHS | ||
mv %{buildroot}%{gem_instdir}/ | mv %{buildroot}%{gem_instdir}/REQUIRE_PATHS/shared_object.so %{buildroot}%{gem_extdir}/REQUIRE_PATHS/ | ||
</pre> | </pre> | ||
===== %prep ===== | ===== %prep ===== | ||
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 | |||
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. | 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. | ||
===== %build ===== | ===== %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 | 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. | ||
===== %install ===== | ===== %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. | 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. | ||
==== | ==== 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: | |||
<pre> | |||
Gem::Specification.new do |s| | |||
# [...] | |||
- s.add_runtime_dependency(%q<mail>, ["~> 2.2.19"]) | |||
+ s.add_runtime_dependency(%q<mail>, [">= 2.3.0"]) | |||
# [...] | |||
end | |||
</pre> | |||
{{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.}} | |||
=== Packaging for Gem and non-Gem use === | |||
{{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).}} | |||
=== Non-Gem Packages === | === Non-Gem Packages === | ||
Non-Gem Ruby packages '''must''' require ruby-devel package at build time with a <code>BuildRequires: ruby-devel</code>, and '''may''' indicate the minimal ruby version they need for building. | Non-Gem Ruby packages '''must''' require ruby-devel package at build time with a <code>BuildRequires: ruby-devel</code>, and '''may''' indicate the minimal ruby version they need for building. | ||
Line 204: | Line 186: | ||
==== Build Architecture and File Placement ==== | ==== Build Architecture and File Placement ==== | ||
The following only affects the files that the package installs into <code>%{ | 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 236: | Line 218: | ||
== Applications == | == Applications == | ||
Applications are | |||
* | |||
* 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 | 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. | ||
Here's an abbreviated example: | |||
<pre> | <pre> | ||
%global app_root %{_datadir}/%{name} | %global app_root %{_datadir}/%{name} | ||
Line 258: | Line 243: | ||
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 | Requires: rubygem-haml | ||
#... | #... | ||
Requires(post): chkconfig | Requires(post): chkconfig | ||
#... | #... | ||
BuildRequires: rubygem | BuildRequires: rubygem-haml | ||
#... | #... | ||
BuildArch: noarch | BuildArch: noarch | ||
Line 281: | Line 266: | ||
%prep | %prep | ||
%setup -q - | gem unpack -V %{SOURCE0} | ||
%setup -q -D -T -n %{name}-%{version} | |||
%build | %build | ||
Line 290: | Line 275: | ||
mkdir -p %{buildroot}%{_initddir} | mkdir -p %{buildroot}%{_initddir} | ||
mkdir -p %{buildroot}%{_bindir} | mkdir -p %{buildroot}%{_bindir} | ||
cp -r | 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 319: | Line 304: | ||
</pre> | </pre> | ||
Note, that although the source is a RubyGem, | 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 330: | Line 314: | ||
</pre> | </pre> | ||
== Running test | == Running test suites == | ||
If there is test suite available for the package (even separately, for example not included in the | 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 | 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 | 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 349: | Line 337: | ||
==== Test::UNIT ==== | ==== Test::UNIT ==== | ||
To run | 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> | ||
{{ | {{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 | 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 | === 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: | |||
<pre> | <pre> | ||
# git clone https://github.com/bebanjo/delorean.git && cd delorean | # git clone https://github.com/bebanjo/delorean.git && cd delorean | ||
Line 371: | Line 364: | ||
# 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 | ||
cp -pr spec/ .%{gem_instdir} | |||
pushd .%{gem_instdir} | pushd .%{gem_instdir} | ||
# Run tests | |||
rm -rf spec | |||
popd | popd | ||
Line 382: | Line 383: | ||
</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. | ||
== Some notes == | |||
* Need to move the rubygems library into the per-interpreter directories as it is a non-gem library. | |||
** Need to rebuild ruby and rubygems package to use the new location | |||
** Need to rebuild rubygem packages to use the new interpreter-neutral rubygem library location. | |||
* Should there be more information about jruby, etc in the introductory portion (naming and | |||
* gem2rpm and rpmdev-newrpmspec can be updated with the new template | |||
[[Category:Packaging guidelines drafts]] | [[Category:Packaging guidelines drafts]] |
Latest revision as of 18:10, 28 March 2012
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 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 start with the interpreter it is built for (ruby, jruby, etc) and then the
UPSTREAM
name. For example:ruby-UPSTREAM
. If the upstream nameUPSTREAM
containsruby
, that should be dropped from the name. For example, the SQLite database driver for ruby is calledsqlite3-ruby
. The corresponding Fedora package should be calledruby-sqlite3
, and notruby-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. Alternate ruby interpreters will have equivalent locations (To be added to this table later) | ||
%{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}
|
%{_libdir}/gems/exts/%{gem_name}-%{version} | The directory for binary 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.
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 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: # s.require_paths = ["lib"] mkdir -p %{buildroot}%{gem_extdir}/REQUIRE_PATHS mv %{buildroot}%{gem_instdir}/REQUIRE_PATHS/shared_object.so %{buildroot}%{gem_extdir}/REQUIRE_PATHS/
%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
Packaging for Gem and non-Gem use
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.
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.
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.
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
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.
Some notes
- Need to move the rubygems library into the per-interpreter directories as it is a non-gem library.
- Need to rebuild ruby and rubygems package to use the new location
- Need to rebuild rubygem packages to use the new interpreter-neutral rubygem library location.
- Should there be more information about jruby, etc in the introductory portion (naming and
- gem2rpm and rpmdev-newrpmspec can be updated with the new template