Pautas de empaquetamiento de Ruby

JRuby Gems: Although Fedora has fully functioning JRuby integrated with system RubyGems, we have decided to not include the JRuby specific packaging guidelines here, as they need some more work. They will appear here as soon as we feel that we’ve got everything covered properly. You can contact us on Ruby-SIG mailing list in case of any questions about the prepared JRuby packaging guidelines.

There are three basic categories of ruby packages: RubyGems, 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.

Compatibilidad con Ruby

Cada paquete de Ruby DEBE indicar sus dependencias en un interpretador de Ruby (esto no se aplica a RubyGems. Utilice requerimiento virtual ruby(release) para lograr eso:

Requires: ruby(release)

Si el paquete requiere Ruby de cierta(s) versión(es), haga con la versión del requerimiento como este:

Requires: ruby(release) >= 1.9.1
Intérpretes alternos: Alterna los intérpretes de Ruby (actualmente JRuby) además Provide: ruby(release). Esto implica, que paquetes de RubyGems puro (estos están compartidos entre intérpretes) NO TENDRÍAN Requires: ruby o Requires: jruby para tener sus dependencias satisfechas por cualquiera de estos intérpretes.
Over specified ruby(release) versioning: Please note that if the ruby(release) version requirement is too specific, it might cause an unexpected interpreter to be drawn in. E.g. ruby(release) = 1.8 will require JRuby package, since it is the only package that provides it.

Compatibilidad de Diferentes Intérpretes

Most of the pure Ruby packages will work on all Ruby interpreters. There are however cases when the packages use interpreter-specific functions (like fork()) and won’t run on other interpreters (JRuby). In this case, the package SHOULD require that interpreter. For example, a package that uses fork SHOULD explicitly specify Requires: ruby. In case of such package, packager SHOULD file a bug to ask upstream to provide support for other interpreter(s). This SHOULD be documented in specfile.

Líneas shebang

En Fedora, /usr/bin/ruby está implementado por medio de Rubypick. Rubypick es una herramienta similar a RVM o rbenv. Permite elegir intérprete para ejecutar guion de Ruby. Rubypick enruta cualquiera ejecutado por medio de /usr/bin/ruby a /usr/bin/ruby-mri o /usr/bin/jruby. Por defecto, ejecuta MRI (Implementación Ruby de Matz), pero el usuario puede especificar explícitamente el intérprete utilizando _mri_ o _jruby_ como un primer parámetro. Por ejemplo:

ruby _jruby_ jruby_script.rb
gem _mri_ install foo
rails _jruby_ s

Using the RUBYPICK environment variable can achieve the same results. The environment variable can be used to set one interpreter as the global default:

export RUBYPICK=_jruby_
ruby jruby_script.rb  # Will use jruby
gem install foo        # Will also use jruby

Ruby executables that are known to only run on one Ruby implementation SHOULD use that specific implementation in their shebang (#!/usr/bin/ruby-mri or #!/usr/bin/jruby) to ensure that they run using that implementation. All other code SHOULD use #!/usr/bin/ruby.

Pautas de Nomenclatura

  • Paquetes que contengan Ruby Gems DEBEN ser invocados rubygem-%{gem_name}.

  • El nombre de un paquete de extensión/biblioteca de Ruby DEBE comenzar con el intérprete con el que está compilado (ruby, jruby, etc.) y entonces el nombre UPSTREAM. Por ejemplo: ruby-UPSTREAM. Si el nombre último UPSTREAM contiene ruby, ese SERÍA descartado desde el nombre. Por ejemplo, el controlador de base de datos SQLite para ruby es invocado sqlite2-ruby. El paquete correspondiente de Fedora SERÍA invocado ruby-sqlite3-ruby.

  • Paquetes de aplicación que mayormente proporcionen herramientas de nivel usuario que sucedan para ser escritos en Ruby DEBEN seguir en su lugar la Guia de Nombrado general.

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. Alternate ruby interpreters will have equivalent locations (to be added to this table later).

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_mri}

%{_libdir}/gems/ruby/%{gem_name}-%{version}

The directory for MRI Ruby Gem extensions.

Interpreter independence and directory macros

You might 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 (see [Different Interpreters Compatibility]). 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 RPMs in a way that such RPMs fit cleanly with the rest of the distribution and makes 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 referred to with the word "Gem" prefixed, e.g., "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 have BuildRequires: rubygems-devel to pull in the macros needed to build.

  • There SHOULD NOT be any rubygem Requires nor Provides listed, since those are autogenerated.

  • There SHOULD NOT be Requires: ruby(release), unless you want to explicitly specify Ruby version compatibility. The automatically generated dependency on RubyGems (Requires: ruby(rubygems)) is enough.

Filtering Requires and Provides

Runtime requires and provides are automatically generated by RPM’s dependency generator. However, it can sometimes throw in additional dependencies contrary to reality. To fix this, the dependency generator needs to be overridden so that the additional dependencies can be filtered out. See AutoProvidesAndRequiresFiltering for details.

Building gems

Since gems aren’t just an archive format but instead encapsulate both an archive and information used for building 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
%autosetup -p1 -n  %{gem_name}-%{version}

# Modifique la especificación de gemspec si es necesario

%build
# Crea la gema como gem install solamente funciona en un archivo gem
gem build ../%{gem_name}-%{version}.gemspec

# %%gem_install compiles any C extensions and installs the gem into ./%%gem_dir
# por defecto, tal que podamos moverlo al buildroot en %%install
%gem_install

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

# Si hubo programas instalados:
mkdir -p %{buildroot}%{_bindir}
cp -a ./%{_bindir}/* %{buildroot}%{_bindir}

# Si hubo extensiones de C, copiadas al extdir.
mkdir -p %{buildroot}%{gem_extdir_mri}
cp -a .%{gem_extdir_mri}/{gem.build_complete,*.so} %{buildroot}%{gem_extdir_mri}/
%prep

RPM (as of 4.14) can directly unpack gem archives, so we can call 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. At the same directory level, the %{gem_name}-%{version}.gemspec file is created automatically as well. 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

Después compilamos la gema: Debido a que %gem_install solamente opera en archivadores gema, después recreamos la gema con gem buils. El archivo gem que es creado es entonces utilizado por %gem_install para compilar e instalar el código en el directorio temporal, el cual es por defecto ./%{gem_dir}. Hacemos esto porque la instrucción %gem_install compila e instala ambos el código dentro de un paso tal que necesitamos tener un directorio temporal para colocar las fuentes de compilación antes de instalarlas en la sección %install.

la macro %gem_install acepta dos opciones adicionales:

-n <gem_file>

Permite sobrescribir gema utilizada para instalación. Esto tal vez obtenga útilies para convertir especificaciones heredadas, tales que quizá especifiquen %{SOURCE0} como una gema para instalación.

-d <install_dir>

Tal vez sustituya el destino de la instalación de gema. Sin embargo no sugerimos utilizar esta opción.

La macro %gem_install NO DEBE ser utilizado para instalar en el %{buildroot}
%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_mri} path.

Parchear requiere versiones de gema

One common patching need is to change overly strict version requirements in the upstream .gemspec. This could 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 behavior without bumping the version number (for instance, backported fixes). To adjust such dependencies, you can use the %gemspec_add_dep and %gemspec_remove_dep macros.

Por ejemplo, si quisiera utilizar cualquier versión de Aruba en lugar de la versión excesivamente específica solicitada por versión siguiente, pudo utilizar dentro de la sección %prep siguiendo dos líneas:

%gemspec_remove_dep -g aruba "~> 0.14.2"
%gemspec_add_dep -g aruba
Use macros only on top of generated .gemspec: The %gemspec_add_dep and %gemspec_remove_dep macros work reliably only on .gemspec generated using the ruby spec command. Please don’t use the macros on upstream .gemspec files.
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

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

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.

Paquetes Pure Ruby

Los paquetes Pure Ruby DEBEN ser compilados como paquetes noarch.

Los archivos de biblioteca de Ruby dentro de un paquete Ruby puro DEBE ser puesto0 en %{ruby_vendorlibdir} (o su propio subdirectorio). EL archivo specfile DEBE utilizar esta macro.

Los paquetes Ruby con contenido binario/bibliotecas compartidas

Para paquetes con contenido binario, p.ej. controladores de BdD o cualquier otro vínculo Ruby a bibliotecas C, el paquete DEBE ser de arquitectura específica.

Los archivos binarios dentro de un paquete Ruby con contenido binario DEBE ser puesto en %{ruby_vendorarchdir} (o su subdirectorio apropiado). Los archivos Ruby es tal que un paquete SERÍA puesto en %{ruby_vendorlibdir}. El archivo específico DEBE utilizar estas macros.

Para paquetes los cuales crean bibliotecas compartidas de C utilizando extconf.rb

export CONFIGURE_ARGS="--with-cflags='%{optflags}'"

SERÍA utilizado para aprobar CFLAGS a Makefile correctamente. Además, para colocar los archivos en las carpetas correctas durante compilación, aprueba --vendor para extconf.rd como esto:

extconf.rb --vendor

Aplicaciones

Las aplicaciones son

  • programas que proporcionan herramientas de nivel-usuario o

  • aplicaciones de web, típicamente compiladas utilizando Rails, Sinatra o marcos de trabajos similares.

Los paquetes RPM DEBEN obedecer reglas FHS. SERÍAN instalados en %{_datadir}. La macro siguiente puede ayudarle:

%global app_root %{_datadir}/%{name}

Estos paquetes típicamente no están en la sección Provides, ya que ningunas otras bibliotecas o aplicaciones dependen de estas.

Aquí hay un ejemplo abreviado:

%global app_root %{_datadir}/%{name}

Sumario: Deltacloud REST API
Nombre deltacloud-core
Versión: 0.3.0
Liberación: 3%{?dist}
Grupo: Development/Languages
Licencia: Apache-2.0 AND MIT
URL: https://incubator.apache.org/deltacloud
Origen:  https://rubygems.org/gems/%{name}-%{version}.gem
Requisitos: rubygem-haml
#...
Requires(post):   chkconfig
#...
BuildRequires: rubygem-haml
#...
BuildArch: noarch

%description
La API Deltacloud es compilada como un API REST basado en servicio.
No enlace directamente una biblioteca Deltacloud en su programa a utilizarla.
En su lugar, un cliente habla con el API de Deltacloud sobre HTTP a un servidor
el cual implementa el interfaz de REST.

%package doc
Summary: Documentación para %{name}
Group: Documentación
Requires:%{name} = %{version}-%{release}

%description doc
Documentación para %{name}

%prep
%setup -q -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
# Esto agrega los enlaces /etc/rc*.d propios para el 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
#...

Tenga en cuenta que, aunque la fuente es una RubyGem, tenemos que instalar los archivos manualmente bajo %{_datadir}/%{name}, %{_bindir}, etc. para seguir FHS y guias de empaquetado general. si archivos específicos de Fedora adicional (archivos .service de systemd, configuraciones) se requieren, SERÍAN

  • añadidos por medio de otra etiqueta %SOURCE

Source1: deltacloudd-fedora
  • colocado en lugares apropiados durante la etapa %install

install -m 0755 %{SOURCE1} %{buildroot}%{_bindir}/deltacloudd

Ejecutar conjunto de pruebas

Si está disponible un conjunto de pruebas para el paquete (incluso separadamente, por ejemplo no incluido en el gem pero disponible en el repositorio upstream), SERÍA ejecutado en %check. El conjunto de pruebas es la herramienta únicamente automatizada la cual puede asegurar la funcionalidad básica del paquete. Ejecutándolo es especialmente ayudante cuando son requeridas recompilaciones masivas. PUEDE omitir la ejecución del conjunto de pruebas cuando no son necesarias todas las dependencias de compilación pero esto DEBE ser documentada dentro del archivo de especificación specfile. Las dependencias ausentes de compilación para habilitar la suite de prueba SERÍA empaquetada para Fedora tan pronto como sea posible y la suite de prueba re-habilitada.

Las pruebas NO SERÍAN ejecutadas utilizando Rake, como al menos Rake siempre dibuja en algunas dependencias innecesarias como hoe o gemcutter. Para razones similares, una dependencia en Bundler SERÍA evitada. Además, la cobertura del código del marco de trabajo tal como SimpleCov y Coveralls SERÍA evitada.

Probar Con Diferentes Implementaciones de Ruby

Para ejecutar con implementaciones diferentes de Ruby tales como JRuby, añada BuildRequires: jruby Después utilice intercambiador de intérprete de Rubypick:

ruby _jruby_ -Ilib -e 'Dir.glob "./test/test_*.rb", &method(:require)'

If your package is running unittests for ruby-mri and it is intended to run under alternate interpreters then it needs to run the unittests under all alternate interpreters as well. This is the only method we have to check compatibility of the code under each interpreter. The same rules apply that you can omit this if libraries you need are unavailable for a specific alternate interpreter but you MUST have a comment to explain.

Testing frameworks usage

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

MiniTest / Test::UNIT

MiniTest as well as Test::UNIT are shipped with Ruby. To use them, you need to use BuildRequires: rubygem-minitest or BuildRequires: rubygem-testunit respectively. To execute the test suite you can use something like:

%check
ruby -Ilib -e 'Dir.glob "./test/**/*_test.rb", &method(:require)'

You might need to adjust -Ilib to be -Ilib:test, or you could need to use a slightly different matching pattern for test_*.rb, etc. Packagers are expected to use the right pattern for each gem.

RSpec

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

%check
rspec -Ilib spec

Conjunto de pruebas no incluidas dentro del paquete

A veces tiene que obtener las pruebas separadamente desde el paquete de gem de versión superior. Como un ejemplo deja proporcionar que está empaquetando rubygem-delorean, versión 1.2.0, lo cual es hospedado en Github. Las pruebas no incluyen en el mismo Gem, tal que necesite obtenerlas y ajustar el archivo specfile correspondientemente:

# 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 -n %{gem_name}-%{version} -b 1

# ...

%check
pushd ./%{gem_instdir}
# Enlace la suite de pruebas en el lugar correcto en el árbol de fuente.
ln -s %{_builddir}/spec .

# Ejecutar pruebas
rspec 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.

  • Ejecutar las pruebas como normalmente haría.