(Delete all the sections that are extraneous with the new build steps) |
(Mark where I have to still review and rework) |
||
Line 197: | Line 197: | ||
=== Non-Gem Packages === | === Non-Gem Packages === | ||
{{admon/question||I haven't gotten to reviewing what may need changing from here on}} | |||
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. |
Revision as of 17:04, 15 February 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 be of the form
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 | ||
%{ruby_vendorarchdir}
|
/usr/lib{64}/ruby/vendor_ruby | Place for architecture specific (e.g. *.so) files. |
%{ruby_vendorlibdir}
|
/usr/share/ruby/vendor_ruby | Place for architecture independent (e.g. *.rb) files. |
%{ruby_sitearchdir}
|
/usr/local/lib{64}/ruby/site_ruby | Place for local architecture specific (e.g. *.so) files. |
%{ruby_sitelibdir}
|
/usr/local/share/ruby/site_ruby | Place for local architecture independent (e.g. *.rb) files. |
From rubygems-devel; intended for gem packages | ||
%{gem_dir}
|
/usr/share/gems | Top directory for the Gem structure. |
%{gem_instdir}
|
%{gem_dir}/gems/%{gem_name}-%{version} | Directory with the actual content of the Gem. |
%{gem_libdir}
|
%{gem_instdir}/lib | The lib folder of the Gem.
|
%{gem_cache}
|
%{gem_dir}/cache/%{gem_name}-%{version}.gem | The cached Gem. |
%{gem_spec}
|
%{gem_dir}/specifications/%{gem_name}-%{version}.gemspec | The Gem specification file. |
%{gem_docdir}
|
%{gem_dir}/doc/%{gem_name}-%{version} | The rdoc documentation of the Gem. |
%{gem_extdir}
|
%{_libdir}/gems/exts/%{gem_name}-%{version} | The directory for binary Gem extensions. |
Dependencies
Due to having multiple types of libraries (gems and non-gems) there are two ways to specify dependencies.
Requires
If your package uses gem 'rubylibrary'
or you're packaging a ruby gem that depends on the library in its .gemspec
(See the section on building gems for more information) you Must use Requires: rubygem(rubylibrary)
so the rpm will pull in the correct ruby library.
If your package just uses require 'rubylibrary'
it's always fine to use Requires: ruby(rubylibrary)
. If you know that the ruby library is provided as a gem despite using require 'rubylibrary'
you may use Requires: rubygem(rubylibrary)
instead but this is not required as it forces packagers to know unnecessary details of how another ruby package has been created.
When packaging rubygems the gem itself carries some dependency information in the "gem specification". That specification may contain precise versions of dependent gems. If Fedora has packaged a different version of the gem you may have to adjust the .gemspec
to work with the Fedora version of the package (see the section on building gems for more information).
Please ensure that the package works properly with the dependencies specified in the rpm spec file.
Provides
Non-gem ruby libraries Must indicate what they provide with Provides: ruby(RUBYLIBRARY) = VERSION
. The string RUBYLIBRARY
Must be the same as what is used in the require
statement in a Ruby script that uses the library. The VERSION
should be the upstream version of the library, as long as upstream follows a sane versioning scheme. For example, a Ruby script using the SQLite database driver will include it with require 'sqlite3'
. The specfile for the corresponding Fedora package must contain a line Provides: ruby(sqlite3) = 1.1.0
, assuming the package contains version 1.1.0 of the library.
Gem packages must have two provides:
Provides: ruby(RUBYLIBRARY) = VERSION Provides: rubygem(%{gem_name}) = %{version}
This allows people who are packaging something that requires the gem to use either form of the dependency to get the proper package. Since their package may only have a require 'rubylibrary'
in the code, there's no reason for them to have to figure out whether we've packaged it as a gem or a non-gem library. The rubygem(%{gem_name})
form of the dependency allows packages which depend on having the gem metadata to specify that the Fedora package must be packaging a gem form of the library, not a non-gem form of the library.
Libraries
These guidelines only apply to Ruby packages whose main purpose is providing a Ruby library; packages that mainly provide user-level tools that happen to be written in Ruby must follow the ruby applications guidelines instead.
RubyGems
RubyGems are Ruby's own packaging format. Gems contain a lot of the same metadata that RPM's need, making fairly smooth interoperation between RPM and Gems possible. This guideline ensures that Gems are packaged as RPM's in a way that ensures (1) that such RPM's fit cleanly with the rest of the distribution and (2) make it possible for the end user to satisfy dependencies of a Gem by installing the appropriate RPM-packaged Gem.
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.
- 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 %setup -q -T -n %{gem_name}-%{version} gem unpack %{SOURCE0} pushd %{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 popd %build mkdir -p ./%{gem_dir} mkdir -p ./%{_bindir} # Create the gem as gem install only works on a gem file gem build %{gem_name}.gemspec export CONFIGURE_ARGS="--with-cflags='%{optflags}'" # gem install compiles any C extensions and installs into a directory gem install -V \ --local \ --install-dir ./%{gem_dir} \ --bindir .%{_bindir} \ --force \ --rdoc ../%{gem_name}-%{version}/%{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. # $REQUIRE_PATHS is taken from 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
In the %prep
section we first use %setup -n %{gem_name}-%{version}
to tell rpm what the directory the gem will unpack into. We use the -T
flag to tell %setup
not to try unpacking the source on its own. Then we explicitly use gem unpack
to extract the source from the gem and switch into the directory. This is equivalent to the steps that %setup
would take if gem was a recognized archive format.
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 can then be 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.
Packaging for Gem and non-Gem use
Tips for Packagers
Gems carry a lot of metadata; gem2rpm is a tool to generate an initial specfile and/or source RPM from a Gem. The generated specfile still needs some hand-editing, but conforms to 90% with this guideline.
Non-Gem Packages
Non-Gem Ruby packages must require ruby-devel package at build time with a BuildRequires: ruby-devel
, and may indicate the minimal ruby version they need for building.
Build Architecture and File Placement
The following only affects the files that the package installs into %{_libdir}/ruby/vendor_ruby
(architecture specific) and %{_datadir}/ruby/vendor_ruby
(architecture independent), i.e. Ruby library files. All other files in a Ruby package must adhere to the general Fedora Extras packaging conventions.
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
By applications we mean:
- programmes that provide user-level tools or
- web applications, typically built using Rails, Sinatra or similar frameworks.
The RPM packages must obey FHS rules, it means that they should be installed into %{_datadir}. Following macro can help you:
%global app_root %{_datadir}/%{name}
These packages typically have no "Provides" section, since no other libraries or applications depend on them.
A good example is the initial specfile of deltacloud-core package (shortened here):
%global app_root %{_datadir}/%{name} Summary: Deltacloud REST API Name: deltacloud-core Version: 0.3.0 Release: 3%{?dist} Group: Development/Languages License: ASL 2.0 and MIT URL: http://incubator.apache.org/deltacloud Source0: http://gems.rubyforge.org/gems/%{name}-%{version}.gem Requires: rubygem(haml) #... Requires(post): chkconfig #... BuildRequires: rubygem(haml) #... BuildArch: noarch %description The Deltacloud API is built as a service-based REST API. You do not directly link a Deltacloud library into your program to use it. Instead, a client speaks the Deltacloud API over HTTP to a server which implements the REST interface. %package doc Summary: Documentation for %{name} Group: Documentation Requires:%{name} = %{version}-%{release} %description doc Documentation for %{name} %prep %setup -q -c -T gem unpack -V --target=%{_builddir} %{SOURCE0} %build %install mkdir -p %{buildroot}%{app_root} mkdir -p %{buildroot}%{_initddir} mkdir -p %{buildroot}%{_bindir} cp -r %{_builddir}/%{name}-%{version}/* %{buildroot}%{app_root} mv %{buildroot}%{app_root}/support/fedora/%{name} %{buildroot}%{_initddir} find %{buildroot}%{app_root}/lib -type f | xargs chmod -x chmod 0755 %{buildroot}%{_initddir}/%{name} chmod 0755 %{buildroot}%{app_root}/bin/deltacloudd rm -rf %{buildroot}%{app_root}/support rdoc --op %{buildroot}%{_defaultdocdir}/%{name} %post # This adds the proper /etc/rc*.d links for the script /sbin/chkconfig --add %{name} %files %{_initddir}/%{name} %{_bindir}/deltacloudd %dir %{app_root}/ %{app_root}/bin #... %files doc %{_defaultdocdir}/%{name} %{app_root}/tests %{app_root}/%{name}.gemspec %{app_root}/Rakefile %changelog #...
Note, that although the source is a RubyGem, gem unpack
is used instead of common gem install
to extract the application and library files. These are then placed under %{_datadir}/%{name}, %{_bindir}, etc. to follow FHS and general packaging guidelines.
If additional Fedora specific files (systemd .service
files, configurations) are required, they should be
- added via another
%SOURCE
tags
Source1: deltacloudd-fedora
- placed into appropriate locations during
%install
stage
install -m 0755 %{SOURCE1} %{buildroot}%{_bindir}/deltacloudd
Running test suite
If there is test suite available for the package (even separately, for example not included in the Gem, but available in the upstream repository), you should run it. The test suite is the only 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.
The tests should not be run using Rake - Rake almost always draws in some unnecessary dependencies like hoe or gemcutter.
Testing frameworks usage
Ruby community provides and supports various testing frameworks. Following paragraphs demonstrates how the test suite can be executed. Please note that the list is not exhaustive.
MiniTest
MiniTest is default testing framework shipped together with Ruby, however unbundled in Fedora (you must use BuildRequires: rubygem(minitest)). To run the tests using MiniTest, you can usually use something like
testrb -Ilib test
Test::UNIT
To run the tests using Test::Unit (you must use BuildRequires: rubygem(test-unit)
), you can usually use something like
testrb2 -Ilib test
Please note that test suite which runs using Test::UNIT can be typically executed also by MiniTest. In that case, please prefer MiniTest.
RSpec
To run the tests using RSpec >= 2 (you must use BuildRequires: rubygem(rspec-core)
), you can usually use something like
rspec -Ilib spec
Test suite not included in package
Typical way of getting the tests separately from upstream follows - lets suppose you're packaging rubygem-delorean, version 1.2.0, which is hosted on Git. Tests are not included in the Gem itself, so you need to get them and adjust the specfile accordingly:
# git clone https://github.com/bebanjo/delorean.git && cd delorean # git checkout v1.2.0 # tar -czf rubygem-delorean-1.2.0-specs.tgz spec/ Source1: %{name}-%{version}-specs.tgz # ... %check pushd .%{gem_instdir} tar xzf %{SOURCE1} popd # ...
- Make sure to include the version of the tests in the source name, so that when updating to new version, rpmbuild will fail because it won't find the proper %SOURCE1 (and this will remind you to update the tests, too).
- Add the commands you used to get the tests into the specfile as comments. This will make it a lot easier the next time you will need to get them.
- Run the tests as you normally would.