Ruby Packaging mini-HOWTO: различия между версиями
SirRaorn (обсуждение | вклад) Нет описания правки |
Нет описания правки |
||
(не показаны 24 промежуточные версии 7 участников) | |||
Строка 1: | Строка 1: | ||
{{Устарело}} | |||
{{Stub}} | {{Stub}} | ||
Основные правила сборки приложений и модулей ruby изложены в [[Ruby Policy|Ruby Packaging Policy]]. Цель этого | Основные правила сборки приложений и модулей ruby изложены в [[Ruby Policy|Ruby Packaging Policy]]. Цель этого документа — объяснить на простых примерах, как следует поступать в различных ситуациях при упаковке ruby-приложений, а также показать, как можно собирать простые модули. | ||
<tt>rubygems</tt> не рассматриваются в данном HOWTO, так как их дополнительная функциональность (например, установка нескольких версий | <tt>rubygems</tt> не рассматриваются в данном HOWTO, так как их дополнительная функциональность (например, установка нескольких версий gem’а), необходимая в некоторых случаях (shared hosting, web-приложения), конфликтует с наличием дистрибутивного пакетного менеджера. Другие дистрибутивы занимают аналогичную позицию в отношении к rubygems: [http://pkg-ruby-extras.alioth.debian.org/rubygems.html позиция Debian], [http://pkg-ruby-extras.alioth.debian.org/upstream-devs.html их рекомендации апстриму]. | ||
Для ситуаций, в которых дополнительная функциональность <tt>rubygems</tt> необходима, сам <tt>rubygems</tt> упакован и работает. | Для ситуаций, в которых дополнительная функциональность <tt>rubygems</tt> необходима, сам <tt>rubygems</tt> упакован и работает. | ||
== Модули, которые нельзя собирать никогда-никогда == | |||
Тут перечислены модули, которых всячески стоит избегать. Это гадкие, негодные модули, не надо их использовать. | |||
* [http://rubyforge.org/projects/rubyinline RubyInline] - модуль, позволяющий делать "вставки" на языке C. На самом деле в процессе работы включаемый C-код "заворачивается" в native extension, компилится и скомпилированный модуль загружается в программу. | |||
== Общие принципы сборки модулей == | == Общие принципы сборки модулей == | ||
При сборке пакета в дистрибутив используется | При сборке пакета в дистрибутив используется «нативная» сборка — с помещением файлов в специальные каталоги, которые находятся в <tt>$LOAD_PATH</tt>. | ||
Сборка включает в себя: | Сборка включает в себя: | ||
Строка 25: | Строка 31: | ||
Внутри тарбола с модулем (или программой) могут находиться следующие файлы и каталоги: | Внутри тарбола с модулем (или программой) могут находиться следующие файлы и каталоги: | ||
* '''bin/''' | * '''bin/''' — скрипты, будут установлены в <tt>%_bindir</tt>; | ||
* '''ext/''' | * '''ext/''' — компилируемые модули (при использовании <tt>setup.rb</tt>), будут установлены в <tt>%ruby_sitearchdir</tt>; | ||
* '''lib/''' | * '''lib/''' — pure-ruby модули, будут установлены в <tt>%ruby_sitelibdir</tt>; | ||
* '''data/''' | * '''data/''' — произвольные данные(при использовании <tt>setup.rb</tt>), будут установлены в <tt>%_datadir/ИМЯМОДУЛЯ</tt>; | ||
* '''test/''' | * '''test/''' — unit-тесты; | ||
Также как правило присутствует один или несколько | Также как правило присутствует один или несколько «сценариев сборки», о них расскажу далее. | ||
== Собираем модуль == | == Собираем модуль == | ||
Существует несколько, различного уровня | Существует несколько, различного уровня «стандартности», способов сборки модулей ruby. | ||
Забегая вперёд | Забегая вперёд, хочу сказать, что самым простым и удобным способом будет использование <tt>setup.rb</tt>, скопированного из пакета <tt>ruby-tool-setup</tt> (конечно, кроме случая когда в корне проекта находится файл <tt>extconf.rb</tt>). | ||
=== <tt>extconf.rb</tt> AKA MkMf === | === <tt>extconf.rb</tt> AKA MkMf === | ||
Аналог configure, использует модуль <tt>mkmf</tt>, входящий в стандартную поставку ruby. Внутри скрипта проверяется наличие необходимых заголовочных файлов и библиотек, на выходе генерится Makefile, который обрабатывается стандартным make. Используется только в тех проектах, где есть бинарные модули. Если | Аналог configure, использует модуль <tt>mkmf</tt>, входящий в стандартную поставку ruby. Внутри скрипта проверяется наличие необходимых заголовочных файлов и библиотек, на выходе генерится Makefile, который обрабатывается стандартным make. Используется только в тех проектах, где есть бинарные модули. Если «рядом» с <tt>extconf.rb</tt> находится файл <tt>depend</tt>, его содержимое добавляется к <tt>Makefile</tt>. Исходные тексты и заголовочные файлы бинарного модуля как правило тоже лежат «рядом» с <tt>extconf.rb</tt>. | ||
Типичные секции <tt>%build</tt> и <tt>%install</tt> выглядят следующим образом: | Типичные секции <tt>%build</tt> и <tt>%install</tt> выглядят следующим образом: | ||
Строка 75: | Строка 81: | ||
=== <tt>Rakefile</tt> и остальные случаи === | === <tt>Rakefile</tt> и остальные случаи === | ||
Сценарий для <tt>rake</tt>. Обычно может иметь | Сценарий для <tt>rake</tt>. Обычно может иметь task’и <tt>build</tt> (если есть бинарные модули) и <tt>test</tt>, но последнее время не имеет task’а <tt>install</tt>. Зато использует <tt>rubygems</tt>. | ||
Для вызова <tt>rake</tt> и <tt>rake install</tt> есть два стандартных макроса <tt>%rake</tt> и <tt>%rake_install</tt> соответственно. | Для вызова <tt>rake</tt> и <tt>rake install</tt> есть два стандартных макроса <tt>%rake</tt> и <tt>%rake_install</tt> соответственно. | ||
Строка 85: | Строка 91: | ||
%setup | %setup | ||
%patch -p1 | %patch -p1 | ||
%update_setup_rb | |||
</pre> | </pre> | ||
Строка 94: | Строка 100: | ||
== Запускаем тесты == | == Запускаем тесты == | ||
С осени 2009 года наш rpm поддерживает секцию %check, все тесты имеет смысл вынести в нее: | |||
<pre> | |||
%check | |||
%ruby_test_unit -Ilib:test test | |||
</pre> | |||
По какой-то причине у меня ([[Участник:ТимурБатыршин]]) такая конструкция не заработала, и я воспользовался другой: | |||
<pre> | |||
%check | |||
for test in $(find test/ -name '*_test.rb'); do | |||
ruby -Ilib $test || exit 1 | |||
done | |||
</pre> | |||
== l10n при помощи <tt>ruby-gettext</tt> == | == l10n при помощи <tt>ruby-gettext</tt> == | ||
Существует специальный модуль <tt>ruby-gettext</tt> который обеспечивает локализацию сообщений, совместимую с <tt>gettext</tt>. Если локализация используется, в каталоге с исходниками есть файлы вида <tt>po/ЯЗЫК/ИМЯ-ПАКЕТА.po</tt>. При сборке файлы <tt>*.po</tt> надо скомпилить в <tt>ЯЗЫК.mo</tt>, это делается следующим хуком для <tt>setup.rb</tt>: | |||
<source lang="ruby"> | |||
=begin | |||
post-setup.rb | |||
Copyright (C) 2001-2006 Masao Mutoh | |||
This program is licenced under the same licence as Ruby. | |||
=end | |||
require 'gettext/utils' | |||
begin | |||
GetText.create_mofiles | |||
rescue | |||
puts "GetText.create_mofiles failed." | |||
end | |||
</source> | |||
Этот код надо поместить в файл <tt>post-setup.rb</tt> и положить в корень проекта, рядом с <tt>setup.rb</tt>. Скомпилированные файлы локализации будут помещены в <tt>data/locale/</tt>, после чего <tt>setup.rb</tt> установит их в /usr/share/locale/. Для упаковки этих фалов в пакет можно пользоваться макросом <tt>%find_lang</tt>. | |||
== Пакуем документацию == | == Пакуем документацию == | ||
Строка 124: | Строка 162: | ||
== Добро пожаловать в реальный мир == | == Добро пожаловать в реальный мир == | ||
В теории всё выглядит красиво, однако на практике среднего размера модуль представляет собой | В теории всё выглядит красиво, однако на практике среднего размера модуль представляет собой «нечто», что может работать в любой помойке. Однако мы делаем не помойку, поэтому местечковые хаки нам не нужны. | ||
=== Не загрязняем <tt>$LOAD_PATH</tt> (<tt>$:</tt>) === | === Не загрязняем <tt>$LOAD_PATH</tt> (<tt>$:</tt>) === | ||
Строка 130: | Строка 168: | ||
Очень часто в коде можно увидеть конструкции вида: | Очень часто в коде можно увидеть конструкции вида: | ||
< | <source lang="ruby"> | ||
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib') | $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') | ||
</ | </source> | ||
Эта конструкция добавляет в <tt>$LOAD_PATH</tt> некоторый путь. Сделано это для того, чтобы модуль (или исполняемый скрипт) можно было использовать из любого места. Поскольку в нашем случае все файлы пакуются в стандартные места, подобные конструкции не нужны. В большинстве случаев такие конструкции можно безболезненно удалить. | Эта конструкция добавляет в <tt>$LOAD_PATH</tt> некоторый путь. Сделано это для того, чтобы модуль (или исполняемый скрипт) можно было использовать из любого места. Поскольку в нашем случае все файлы пакуются в стандартные места, подобные конструкции не нужны. В большинстве случаев такие конструкции можно безболезненно удалить. | ||
Строка 138: | Строка 176: | ||
=== Используем существующие модули и размаскируем зависимости === | === Используем существующие модули и размаскируем зависимости === | ||
Поскольку наша помойка не является | Поскольку наша помойка не является «любой», её ТТХ нам известны. Например, известно что это Linux, есть Iconv и так далее. Поэтому специфичный код, предназначенный для работы на других платформах в наших пакетах не нужен (а иногда бывает и вреден, поскольку даже «мёртвый» код может порождать зависимости, которые в некоторых случаях превращаются в unmet’ы). | ||
Также мы можем превратить опциональную зависимость в явную. Пример: | Также мы можем превратить опциональную зависимость в явную. Пример: | ||
< | <source lang="ruby"> | ||
begin | begin | ||
require 'iconv' | require 'iconv' | ||
Строка 151: | Строка 189: | ||
end | end | ||
end | end | ||
</ | </source> | ||
В данном случае '''всю''' эту сложную конструкцию можно заменить на одну строку <tt>require 'iconv'</tt>. Как бонус мы получаем зависимость на <tt>ruby(iconv)</tt> и полную функциональность данного модуля. | В данном случае '''всю''' эту сложную конструкцию можно заменить на одну строку <tt>require 'iconv'</tt>. Как бонус мы получаем зависимость на <tt>ruby(iconv)</tt> и полную функциональность данного модуля. | ||
Строка 163: | Строка 201: | ||
Пример 1: | Пример 1: | ||
< | <source lang="ruby"> | ||
require 'rubygems' | require 'rubygems' | ||
require 'some/module' | require 'some/module' | ||
require 'some/other/module' | require 'some/other/module' | ||
</ | </source> | ||
Пример 2: | Пример 2: | ||
< | <source lang="ruby"> | ||
begin | begin | ||
require 'rubygems' | require 'rubygems' | ||
Строка 180: | Строка 218: | ||
require 'some/other/module' | require 'some/other/module' | ||
end | end | ||
</ | </source> | ||
Пример 3: | Пример 3: | ||
< | <source lang="ruby"> | ||
begin | begin | ||
require 'some/module' | require 'some/module' | ||
Строка 193: | Строка 231: | ||
require 'some/other/module' | require 'some/other/module' | ||
end | end | ||
</ | </source> | ||
Все эти три примера можно привести к одному виду: | Все эти три примера можно привести к одному виду: | ||
< | <source lang="ruby"> | ||
require 'some/module' | require 'some/module' | ||
require 'some/other/module' | require 'some/other/module' | ||
</ | </source> | ||
Этим мы убираем зависимость на rubygems и размаскируем скрытые зависимости. Таким образом наш модуль автоматически получает зависимсти на <tt>ruby(some/module)</tt> и <tt>ruby(some/other/module)</tt>. | Этим мы убираем зависимость на rubygems и размаскируем скрытые зависимости. Таким образом наш модуль автоматически получает зависимсти на <tt>ruby(some/module)</tt> и <tt>ruby(some/other/module)</tt>. | ||
Строка 206: | Строка 244: | ||
=== Файлы специального вида (темплейты и плагины) === | === Файлы специального вида (темплейты и плагины) === | ||
Очень часто модуль (или приложение) носит с собой какие-то данные, которые обычно находятся в каталоге с | Очень часто модуль (или приложение) носит с собой какие-то данные, которые обычно находятся в каталоге с gem’ом. Выглядит это приблизительно вот так: | ||
< | <source lang="ruby"> | ||
def mock_framework_path(framework_name) | def mock_framework_path(framework_name) | ||
File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "plugins", "mock_frameworks", framework_name)) | File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "plugins", "mock_frameworks", framework_name)) | ||
end | end | ||
</ | </source> | ||
В таком случае этот каталог (в нашем примере | В таком случае этот каталог (в нашем примере «<tt>plugins</tt>») помещается в <tt>/usr/share/ИМЯМОДУЛЯ</tt> а код заменяется на такой: | ||
< | <source lang="ruby"> | ||
def mock_framework_path(framework_name) | def mock_framework_path(framework_name) | ||
File.expand_path(File.join("/usr/share/ИМЯМОДУЛЯ/plugins/mock_frameworks", framework_name)) | File.expand_path(File.join("/usr/share/ИМЯМОДУЛЯ/plugins/mock_frameworks", framework_name)) | ||
end | end | ||
</ | </source> | ||
=== Потрошим новый пакет === | === Потрошим новый пакет === | ||
При сборке нового пакета следует пробежать глазами его код на тему вышеописанных конструкций. Начать можно с <tt>grep -r</tt> по каталогам <tt>lib/</tt> и <tt>bin/</tt> по паттернам '<tt>__FILE__</tt>', '<tt>ubygems</tt>', '<tt>LoadError</tt>', '<tt>\$:</tt>', '<tt>\$LOAD_PATH</tt>'. | При сборке нового пакета следует пробежать глазами его код на тему вышеописанных конструкций. Начать можно с <tt>grep -r</tt> по каталогам <tt>lib/</tt> и <tt>bin/</tt> по паттернам '<tt>__FILE__</tt>', '<tt>ubygems</tt>', '<tt>LoadError</tt>', '<tt>\$:</tt>', '<tt>\$LOAD_PATH</tt>'. | ||
== Некоторые хитрости упаковки плагинов == | |||
Как правило любой «плагин» можно разделить на три или менее частей — «модули», «данные» и «скрипт инициализации». Обычно при загрузке «плагина» его «скрипт инициализации» загружает «модули» и в процессе работы приложения код «модулей» использует «данные». Из чего состоит «плагин», как он загружается и как работает зависит от приложения. Существует несколько довольно известных приложений, под которые написано довольно много плагинов. Некоторые из них возможно упаковать в rpm пакеты. | |||
=== Ruby On Rails === | |||
Ruby on Rails считает «плагином» всё, где в корне проекта есть файл <tt>init.rb</tt>. В свою очередь, «проектом» может считаться любой каталог в <tt>vendor/plugins/</tt> и все gem’ы. В нашей сборке RoR к дефолтному списку путей поиска плагинов (состоящему только из <tt>vendor/plugins/</tt>) добавлен <tt>%_datadir/rails/plugins/</tt>. | |||
# Пакет должен называться <tt>rails-plugin-ИМЯПЛАГИНА</tt>. | |||
# Пакет должен иметь зависимость на <tt>ruby-railties >= 2.1.0-alt2</tt>. | |||
# Сборка и установка «модулей» производится при помощи <tt>setup.rb</tt> как описано выше. | |||
# Файл <tt>init.rb</tt> и всё что может понадобиться плагину (<tt>about.yml</tt>, <tt>generators</tt>, <tt>tasks</tt> и т. п.) нужно скопировать в <tt>%buildroot%_datadir/rails/plugins/ИМЯПЛАГИНА/</tt>. | |||
Однако, бывают случаи, когда собранный таким образом плагин не работает с криками про Uninitialized constant. Скорее всего в таком плагине используется механизм автозагрузки модулей из ActiveSupport. Он не ищет модули в стандартном $LOAD_PATH, поэтому устанавливать «модули» приходится в базовый каталог «плагина», например используя опцию <tt>--rbdir=%_datadir/rails/plugins/ИМЯПЛАГИНА/lib</tt> для <tt>%ruby_config</tt>. | |||
=== GemPlugin === | |||
Эта система плагинов появилась из проекта [http://mongrel.rubyforge.org/ mongrel], но в настоящее используется и в других проектах. Я не буду описывать принцип работы этой системы, расскажу только что нужно для того, чтобы «упакеченый» плагин можно было использовать. | |||
# Пакет должен иметь зависимость на каталог <tt>%_datadir/gem_plugin</tt>. | |||
# Файл <tt>lib/ИМЯПЛАГИНА/init.rb</tt> перед установкой должен быть перемещён в <tt>init.rb</tt> (в корень проекта). | |||
# Сборка и установка «модулей» производится при помощи <tt>setup.rb</tt> как описано выше. | |||
# Каталог <tt>resources</tt> нужно скопировать в <tt>%buildroot%_datadir/gem_plugin/ИМЯПЛАГИНА/</tt>. | |||
# Файл <tt>init.rb</tt> (который перед установкой вытащили из <tt>lib/</tt>) тоже нужно скопировать в <tt>%buildroot%_datadir/gem_plugin/ИМЯПЛАГИНА/</tt>. | |||
# Записать в файл <tt>%buildroot%_datadir/gem_plugin/ИМЯПЛАГИНА/dependencies.yml</tt> список зависимостей «плагина». | |||
Файл dependencies.yml нужен для самого gem_plugin, чтобы решить какие именно плагины загружать для приложения, которое это попросило. Его можно создать посмотрев в GEM Specification, которая находится в файле <tt>Rakefile</tt> или <tt>ИМЯПЛАГИНА.gemspec</tt>: | |||
<source lang="ruby"> | |||
Gem::Specification.new do |s| | |||
# ... | |||
s.add_dependency(%q<gem_plugin>, [">= 0.2.3"]) | |||
s.add_dependency(%q<daemons>, [">= 1.0.3"]) | |||
s.add_dependency(%q<fastthread>, [">= 1.0.1"]) | |||
# ... | |||
end | |||
</source> | |||
Нас интересуют только строки с <tt>add_dependency</tt> и только имена gem’ов. В нашем примере файл <tt>dependencies.yml</tt> должен выглядеть следующим образом: | |||
<source lang="text"> | |||
--- | |||
- gem_plugin | |||
- daemons | |||
- fastthread | |||
</source> | |||
== Генератор пакетов == | |||
Я заметил, что при упаковке нового пакета мне приходится производить много одинаковых действий и заниматься копипастеньем. Поэтому я нарисовал [http://git.altlinux.org/people/raorn/public/?p=ruby-spec-templates.git примерный генератор] git-репозитория для нового ruby-пакета. | |||
<pre> | |||
.../mkrubypkgrepo [--arch] [--doc] [--rails] path/to/name-version.tar.gz | |||
</pre> | |||
* '''--arch''': генерить спек для архитектурно-зависимого пакета | |||
* '''--doc''': паковать RI документацию в %name-doc | |||
* '''--rails''': генерить спек для RoR плагина | |||
Скрипт инициализирует в текущем каталоге git репозиторий, импортирует распакованный тарболл в ветку upstream и генерирует начальный спек. После генерации как обычно обработать напильником. | |||
<!-- vim: set ft=mediawiki spell spelllang=ru,en wrap lbr: --> | <!-- vim: set ft=mediawiki spell spelllang=ru,en wrap lbr: --> | ||
[[Категория:Packaging]] | |||
[[Категория:Ruby]] | |||
{{Category navigation|title=HOWTO|category=HOWTO|sortkey={{SUBPAGENAME}}}} |
Текущая версия от 16:44, 21 декабря 2023
Основные правила сборки приложений и модулей ruby изложены в Ruby Packaging Policy. Цель этого документа — объяснить на простых примерах, как следует поступать в различных ситуациях при упаковке ruby-приложений, а также показать, как можно собирать простые модули.
rubygems не рассматриваются в данном HOWTO, так как их дополнительная функциональность (например, установка нескольких версий gem’а), необходимая в некоторых случаях (shared hosting, web-приложения), конфликтует с наличием дистрибутивного пакетного менеджера. Другие дистрибутивы занимают аналогичную позицию в отношении к rubygems: позиция Debian, их рекомендации апстриму.
Для ситуаций, в которых дополнительная функциональность rubygems необходима, сам rubygems упакован и работает.
Модули, которые нельзя собирать никогда-никогда
Тут перечислены модули, которых всячески стоит избегать. Это гадкие, негодные модули, не надо их использовать.
- RubyInline - модуль, позволяющий делать "вставки" на языке C. На самом деле в процессе работы включаемый C-код "заворачивается" в native extension, компилится и скомпилированный модуль загружается в программу.
Общие принципы сборки модулей
При сборке пакета в дистрибутив используется «нативная» сборка — с помещением файлов в специальные каталоги, которые находятся в $LOAD_PATH.
Сборка включает в себя:
- компиляцию бинарных модулей (если они есть);
- выполнение тестов (если они есть и их выполнение возможно в hasher);
- установку файлов в соответствующие каталоги;
- генерацию документации в формате ri (class reference);
Модули устанавливаются в так называемый vendor dir. В ALT Linux это /usr/share/ruby/vendor_ruby/RUBY.VERSION и /usr/lib/ruby/RUBY.VERSION/ARCHITECTURE. Поскольку по умолчанию установка модулей идёт в site dir, при сборке пакета надо использовать модуль vendor_specific, вызывая интерпретатор ruby как ruby -rvendor_specific. Для этого есть макрос %ruby.
Внутри тарбола
Внутри тарбола с модулем (или программой) могут находиться следующие файлы и каталоги:
- bin/ — скрипты, будут установлены в %_bindir;
- ext/ — компилируемые модули (при использовании setup.rb), будут установлены в %ruby_sitearchdir;
- lib/ — pure-ruby модули, будут установлены в %ruby_sitelibdir;
- data/ — произвольные данные(при использовании setup.rb), будут установлены в %_datadir/ИМЯМОДУЛЯ;
- test/ — unit-тесты;
Также как правило присутствует один или несколько «сценариев сборки», о них расскажу далее.
Собираем модуль
Существует несколько, различного уровня «стандартности», способов сборки модулей ruby.
Забегая вперёд, хочу сказать, что самым простым и удобным способом будет использование setup.rb, скопированного из пакета ruby-tool-setup (конечно, кроме случая когда в корне проекта находится файл extconf.rb).
extconf.rb AKA MkMf
Аналог configure, использует модуль mkmf, входящий в стандартную поставку ruby. Внутри скрипта проверяется наличие необходимых заголовочных файлов и библиотек, на выходе генерится Makefile, который обрабатывается стандартным make. Используется только в тех проектах, где есть бинарные модули. Если «рядом» с extconf.rb находится файл depend, его содержимое добавляется к Makefile. Исходные тексты и заголовочные файлы бинарного модуля как правило тоже лежат «рядом» с extconf.rb.
Типичные секции %build и %install выглядят следующим образом:
%build %ruby_configure <опции extconf.rb> %make_build %install %make_install DESTDIR=%buildroot install
setup.rb имени Minero Aoki
Скрипт сборки и установки общего назначения. Как правило используется для сборки и установки pure-ruby модулей. Имеет некоторое количество стандартных опций, может собирать бинарные модули, находящиеся в каталоге ext/ (как правило там присутствует extconf.rb, см. выше).
Типичные секции %build и %install выглядят следующим образом:
%build %ruby_config <опции setup.rb> %ruby_build %install %ruby_install
install.rb
Иногда это самописный скрипт, иногда встречается одна из первых версий setup.rb. Как правило используется только для установки pure-ruby модулей. Стандартных макросов для поддержки install.rb нет.
Rakefile и остальные случаи
Сценарий для rake. Обычно может иметь task’и build (если есть бинарные модули) и test, но последнее время не имеет task’а install. Зато использует rubygems.
Для вызова rake и rake install есть два стандартных макроса %rake и %rake_install соответственно.
Если task install не определён или вообще отсутствует установочный скрипт (в случае pure-ruby модуля), можно использовать setup.rb из пакета ruby-tool-setup примерно следующим образом:
%prep %setup %patch -p1 %update_setup_rb
Далее используются макросы %ruby_config, %ruby_build и %ruby_install.
Очень часто в Rakefile включена поддержка создания .gem. Поскольку rubygems использовать при сборке пакетов нельзя, следует воспользоваться рецептом из Debian.
Запускаем тесты
С осени 2009 года наш rpm поддерживает секцию %check, все тесты имеет смысл вынести в нее:
%check %ruby_test_unit -Ilib:test test
По какой-то причине у меня (Участник:ТимурБатыршин) такая конструкция не заработала, и я воспользовался другой:
%check for test in $(find test/ -name '*_test.rb'); do ruby -Ilib $test || exit 1 done
l10n при помощи ruby-gettext
Существует специальный модуль ruby-gettext который обеспечивает локализацию сообщений, совместимую с gettext. Если локализация используется, в каталоге с исходниками есть файлы вида po/ЯЗЫК/ИМЯ-ПАКЕТА.po. При сборке файлы *.po надо скомпилить в ЯЗЫК.mo, это делается следующим хуком для setup.rb:
=begin
post-setup.rb
Copyright (C) 2001-2006 Masao Mutoh
This program is licenced under the same licence as Ruby.
=end
require 'gettext/utils'
begin
GetText.create_mofiles
rescue
puts "GetText.create_mofiles failed."
end
Этот код надо поместить в файл post-setup.rb и положить в корень проекта, рядом с setup.rb. Скомпилированные файлы локализации будут помещены в data/locale/, после чего setup.rb установит их в /usr/share/locale/. Для упаковки этих фалов в пакет можно пользоваться макросом %find_lang.
Пакуем документацию
Документация в формате RI генерируется при помощи утилиты rdoc, находящейся в пакете ruby-tool-rdoc. Для этого существует стандартный макрос %rdoc предназначенный для использования в секции %install (обычно одной из последних строк). В качестве аргументов этого макроса перечисляются файлы и каталоги с исходниками и при необходимости другие опции утилиты rdoc.
Для pure-ruby модулей как правило используется конструкция:
%rdoc lib/
Если присутствуют бинарные модули:
%rdoc *.c lib/
Документацию желательно паковать в подпакет %name-doc. При этом паковать следует только документацию для основных классов модуля, описание расширений сторонних классов паковать не нужно.
Складываем файлы в пакеты
Pure-ruby модули помещаются в %ruby_sitelibdir, бинарные модули в %ruby_sitearchdir, документация в формате RI в %ruby_ri_sitedir (при этом файл %ruby_ri_sitedir/created.rid упаковывать не нужно).
Добро пожаловать в реальный мир
В теории всё выглядит красиво, однако на практике среднего размера модуль представляет собой «нечто», что может работать в любой помойке. Однако мы делаем не помойку, поэтому местечковые хаки нам не нужны.
Не загрязняем $LOAD_PATH ($:)
Очень часто в коде можно увидеть конструкции вида:
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
Эта конструкция добавляет в $LOAD_PATH некоторый путь. Сделано это для того, чтобы модуль (или исполняемый скрипт) можно было использовать из любого места. Поскольку в нашем случае все файлы пакуются в стандартные места, подобные конструкции не нужны. В большинстве случаев такие конструкции можно безболезненно удалить.
Используем существующие модули и размаскируем зависимости
Поскольку наша помойка не является «любой», её ТТХ нам известны. Например, известно что это Linux, есть Iconv и так далее. Поэтому специфичный код, предназначенный для работы на других платформах в наших пакетах не нужен (а иногда бывает и вреден, поскольку даже «мёртвый» код может порождать зависимости, которые в некоторых случаях превращаются в unmet’ы).
Также мы можем превратить опциональную зависимость в явную. Пример:
begin
require 'iconv'
rescue LoadError
module Iconv
# Далее следует некоторый код, который в результате предоставляет API
# похожий на API модуля iconv, возможно урезанный функционально.
end
end
В данном случае всю эту сложную конструкцию можно заменить на одну строку require 'iconv'. Как бонус мы получаем зависимость на ruby(iconv) и полную функциональность данного модуля.
Реальные примеры можно посмотреть в пакете ruby-gettext.
Отрываем rubygems
В реальной жизни можно встретить множество вариантов использования rubygems. Вот самые распространённые:
Пример 1:
require 'rubygems'
require 'some/module'
require 'some/other/module'
Пример 2:
begin
require 'rubygems'
require 'some/module'
require 'some/other/module'
rescue LoadError
require 'some/module'
require 'some/other/module'
end
Пример 3:
begin
require 'some/module'
require 'some/other/module'
rescue LoadError
require 'rubygems'
require 'some/module'
require 'some/other/module'
end
Все эти три примера можно привести к одному виду:
require 'some/module'
require 'some/other/module'
Этим мы убираем зависимость на rubygems и размаскируем скрытые зависимости. Таким образом наш модуль автоматически получает зависимсти на ruby(some/module) и ruby(some/other/module).
Файлы специального вида (темплейты и плагины)
Очень часто модуль (или приложение) носит с собой какие-то данные, которые обычно находятся в каталоге с gem’ом. Выглядит это приблизительно вот так:
def mock_framework_path(framework_name)
File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "plugins", "mock_frameworks", framework_name))
end
В таком случае этот каталог (в нашем примере «plugins») помещается в /usr/share/ИМЯМОДУЛЯ а код заменяется на такой:
def mock_framework_path(framework_name)
File.expand_path(File.join("/usr/share/ИМЯМОДУЛЯ/plugins/mock_frameworks", framework_name))
end
Потрошим новый пакет
При сборке нового пакета следует пробежать глазами его код на тему вышеописанных конструкций. Начать можно с grep -r по каталогам lib/ и bin/ по паттернам '__FILE__', 'ubygems', 'LoadError', '\$:', '\$LOAD_PATH'.
Некоторые хитрости упаковки плагинов
Как правило любой «плагин» можно разделить на три или менее частей — «модули», «данные» и «скрипт инициализации». Обычно при загрузке «плагина» его «скрипт инициализации» загружает «модули» и в процессе работы приложения код «модулей» использует «данные». Из чего состоит «плагин», как он загружается и как работает зависит от приложения. Существует несколько довольно известных приложений, под которые написано довольно много плагинов. Некоторые из них возможно упаковать в rpm пакеты.
Ruby On Rails
Ruby on Rails считает «плагином» всё, где в корне проекта есть файл init.rb. В свою очередь, «проектом» может считаться любой каталог в vendor/plugins/ и все gem’ы. В нашей сборке RoR к дефолтному списку путей поиска плагинов (состоящему только из vendor/plugins/) добавлен %_datadir/rails/plugins/.
- Пакет должен называться rails-plugin-ИМЯПЛАГИНА.
- Пакет должен иметь зависимость на ruby-railties >= 2.1.0-alt2.
- Сборка и установка «модулей» производится при помощи setup.rb как описано выше.
- Файл init.rb и всё что может понадобиться плагину (about.yml, generators, tasks и т. п.) нужно скопировать в %buildroot%_datadir/rails/plugins/ИМЯПЛАГИНА/.
Однако, бывают случаи, когда собранный таким образом плагин не работает с криками про Uninitialized constant. Скорее всего в таком плагине используется механизм автозагрузки модулей из ActiveSupport. Он не ищет модули в стандартном $LOAD_PATH, поэтому устанавливать «модули» приходится в базовый каталог «плагина», например используя опцию --rbdir=%_datadir/rails/plugins/ИМЯПЛАГИНА/lib для %ruby_config.
GemPlugin
Эта система плагинов появилась из проекта mongrel, но в настоящее используется и в других проектах. Я не буду описывать принцип работы этой системы, расскажу только что нужно для того, чтобы «упакеченый» плагин можно было использовать.
- Пакет должен иметь зависимость на каталог %_datadir/gem_plugin.
- Файл lib/ИМЯПЛАГИНА/init.rb перед установкой должен быть перемещён в init.rb (в корень проекта).
- Сборка и установка «модулей» производится при помощи setup.rb как описано выше.
- Каталог resources нужно скопировать в %buildroot%_datadir/gem_plugin/ИМЯПЛАГИНА/.
- Файл init.rb (который перед установкой вытащили из lib/) тоже нужно скопировать в %buildroot%_datadir/gem_plugin/ИМЯПЛАГИНА/.
- Записать в файл %buildroot%_datadir/gem_plugin/ИМЯПЛАГИНА/dependencies.yml список зависимостей «плагина».
Файл dependencies.yml нужен для самого gem_plugin, чтобы решить какие именно плагины загружать для приложения, которое это попросило. Его можно создать посмотрев в GEM Specification, которая находится в файле Rakefile или ИМЯПЛАГИНА.gemspec:
Gem::Specification.new do |s|
# ...
s.add_dependency(%q<gem_plugin>, [">= 0.2.3"])
s.add_dependency(%q<daemons>, [">= 1.0.3"])
s.add_dependency(%q<fastthread>, [">= 1.0.1"])
# ...
end
Нас интересуют только строки с add_dependency и только имена gem’ов. В нашем примере файл dependencies.yml должен выглядеть следующим образом:
---
- gem_plugin
- daemons
- fastthread
Генератор пакетов
Я заметил, что при упаковке нового пакета мне приходится производить много одинаковых действий и заниматься копипастеньем. Поэтому я нарисовал примерный генератор git-репозитория для нового ruby-пакета.
.../mkrubypkgrepo [--arch] [--doc] [--rails] path/to/name-version.tar.gz
- --arch: генерить спек для архитектурно-зависимого пакета
- --doc: паковать RI документацию в %name-doc
- --rails: генерить спек для RoR плагина
Скрипт инициализирует в текущем каталоге git репозиторий, импортирует распакованный тарболл в ветку upstream и генерирует начальный спек. После генерации как обычно обработать напильником.