Packaging Automation/Embedded Language: различия между версиями

Материал из ALT Linux Wiki
 
(не показано 29 промежуточных версий 2 участников)
Строка 17: Строка 17:
утилит для манипуляции spec-файлами и src.rpm пакетами,
утилит для манипуляции spec-файлами и src.rpm пакетами,
таких как srpmnmu, srpmbackport, srpmconvert,
таких как srpmnmu, srpmbackport, srpmconvert,
набора утилит для массовых операций с пакетами girar-nmu,
набора утилит для массовых операций с пакетами [[Git.alt/girar-nmu|girar-nmu]],
утилит repocop-nmu, автономных сервисов, таких как cronbuild,  
утилит repocop-nmu, автономных сервисов, таких как [[Gear/cronbuild|cronbuild]],  
croncopy, cronbackports, autoports, fedoraimport и других.
[[croncopy]], cronbackports, [[Autoports|autoports]], fedoraimport и других.


В основе этих программ лежит библиотека perl-RPM-Source-Editor.
В основе этих программ лежит библиотека perl-RPM-Source-Editor.
Строка 135: Строка 135:
=== Синтаксис файлов --hook ===
=== Синтаксис файлов --hook ===


начнем знакомство с файла template.pl, содержащего шаблон для наиболее
начнем знакомство с файла {{path|hooks/template.pl}}, содержащего шаблон для наиболее
частых правок, которые приходится вносить в спек-файлы при импорте из Fedora.
частых правок, которые приходится вносить в спек-файлы при импорте из Fedora.
На основе такого шаблона при необходимости удобно создавать личный
На основе такого шаблона при необходимости удобно создавать личный
hook пакета с именем %{name}.pl, который в дальнейшем будет
hook пакета с именем {{path|hooks/%{name}.pl}}, который в дальнейшем будет
автоматически использоваться при импорте следующих версий этого пакета
автоматически использоваться при импорте следующих версий этого пакета
в системе fedoraimport.
в системе fedoraimport.


#!/usr/bin/perl -w
Итак, файл {{path|hooks/template.pl}}:  
 
<source lang="perl">
Итак, файл:  
  push @SPECHOOKS,  
  push @SPECHOOKS,  
  sub {
  sub {
Строка 154: Строка 153:
     $spec->get_section('package','')->unshift_body('BuildRequires: '."\n");
     $spec->get_section('package','')->unshift_body('BuildRequires: '."\n");
  };
  };
 
</source>
Собственно, минимально необходимая обвязка - это  
Собственно, минимально необходимая обвязка - это  
 
<source lang="perl">
  push @SPECHOOKS,  
  push @SPECHOOKS,  
  sub {
  sub {
     my ($spec, $parent) = @_;
     my ($spec, $parent) = @_;
  };
  };
</source>


Мы видим, что в наш код утилита передает 2 объекта perl:
Мы видим, что в наш код утилита передает 2 объекта perl:
Строка 174: Строка 174:
spec-файлу, и методы, применяемые к отдельным секциям spec-файла.
spec-файлу, и методы, применяемые к отдельным секциям spec-файла.
Примером методов на уровне spec-файла является метод add_patch.
Примером методов на уровне spec-файла является метод add_patch.
 
<source lang="perl">
  push @SPECHOOKS,  
  push @SPECHOOKS,  
  sub {
  sub {
Строка 180: Строка 180:
     $spec->add_patch('foo-1.2-alt-fix-something.patch',STRIP=>3);
     $spec->add_patch('foo-1.2-alt-fix-something.patch',STRIP=>3);
  };
  };
 
</source>
Этот код скопирует файл foo-1.2-alt-fix-something.patch из ./patches
Этот код скопирует файл foo-1.2-alt-fix-something.patch из ./patches
в %_sourcedir данного пакета (возможно, временный каталог, созданный
в %_sourcedir данного пакета (возможно, временный каталог, созданный
утилитой srpmnmu или ей родственной); Добавит в спек тег  
утилитой srpmnmu или ей родственной); Добавит в спек тег  
PatchXX: foo-1.2-alt-fix-something.patch,
<source lang="rpmspec">
PatchXX: foo-1.2-alt-fix-something.patch
</source>
где XX -- некоторый незанятый номер, и добавит в секцию %prep строку
где XX -- некоторый незанятый номер, и добавит в секцию %prep строку
%patchXX -p3
<source lang="rpmspec">
%patchXX -p3
</source>
где 3 указано через параметр STRIP=>3.
где 3 указано через параметр STRIP=>3.


Строка 193: Строка 197:
для systemd. Насобираем коллекцию .service файлов, названных по имени пакета
для systemd. Насобираем коллекцию .service файлов, названных по имени пакета
(вида %name.service), в папке ./patches. Создадим файл add_systemd_service.pl
(вида %name.service), в папке ./patches. Создадим файл add_systemd_service.pl
 
<source lang="perl">
  push @SPECHOOKS,  
  push @SPECHOOKS,  
  sub {
  sub {
Строка 202: Строка 206:
     $spec->get_section('files')->push_body('%_systemd/%name.service'."n");
     $spec->get_section('files')->push_body('%_systemd/%name.service'."n");
  };
  };
</source>


Передадим этот файл в утилиту  
Передадим этот файл в утилиту  
Строка 227: Строка 232:
Это отражает дизайн: сначала находим в spec-файлe нужную секцию,  
Это отражает дизайн: сначала находим в spec-файлe нужную секцию,  
затем редактируем ее.
затем редактируем ее.
=== Что такое секции ===
spec файл делится на части, которые называются секции,
с помощью специальных заголовков, которые начинаются с %.
Примеры таких заголовков:
%description
%package
%prep
%post
...
%files
У этих заголовков могут быть аргументы, например, има подпакета:
%package devel
Начало spec файла не имеет заголовка, однако по смыслу это тоже отдельная секция %package (без аргументов) пакета по умолчанию.


=== Получаем секции ===
=== Получаем секции ===
Строка 312: Строка 333:
Пример: добавить путь из тега URL: в тег Source:
Пример: добавить путь из тега URL: в тег Source:
(если в spec-файлe написано Source0:, то добавлено будет в Source0:)
(если в spec-файлe написано Source0:, то добавлено будет в Source0:)
<source lang="perl">
  use File::Basename;
  use File::Basename;
  push @SPECHOOKS,  
  push @SPECHOOKS,  
Строка 320: Строка 342:
     $section->set_tag('Source',$urlprefix.'/'.$section->get_tag('Source', RAW=>1));
     $section->set_tag('Source',$urlprefix.'/'.$section->get_tag('Source', RAW=>1));
  };
  };
</source>
==== Работа с телом секции ====


==== Работа с телом секции ====
===== простые методы =====


* Метод push_body нам уже знаком, аккуратно вставить в конец секции, отступая от конца пустые строки, условные макросы %if,...
* Метод push_body (список строк) нам уже знаком. Он позволяет аккуратно вставить свои аргументы в конец секции, при чем если в конце есть пустые строки, условные макросы %if,... то пустые строки, условные макросы останутся в конце.


* Метод unshift_body: вставить в начало секции, после заголовка секции. Пример: вставить в spec-файл %define _unpackaged_files_terminate_build 1.
* Метод unshift_body (список строк): вставить в начало секции, после заголовка секции. Пример: вставить в spec-файл %define _unpackaged_files_terminate_build 1.


* Метод match_body: проверить, не встречается ли в секции заданное регулярное выражение.
* Метод match_body(qr'регулярное выражение'): проверить, не встречается ли в секции заданное регулярное выражение.


Пример: Если в спек-файле не определен макрос
Пример: Если в спек-файле не определен макрос
_unpackaged_files_terminate_build, добавить его.
_unpackaged_files_terminate_build, добавить его.
<pre>
<source lang="perl">
push @SPECHOOKS,  
push @SPECHOOKS,  
sub {
sub {
Строка 340: Строка 364:
') unless $section->match(qr'\%define\s+_unpackaged_files_terminate_build');
') unless $section->match(qr'\%define\s+_unpackaged_files_terminate_build');
};
};
</pre>
</source>
 
* Метод unshift_body_after (qr'регулярное выражение', список строк): вставить после первой строки, подпадающей под qr'регулярное выражение'.
 
* Метод unshift_body_before (qr'регулярное выражение', список строк): вставить перед первой строкой, подпадающей под qr'регулярное выражение'.
 
* Метод subst_body: заменить регулярное выражение old text новым текстом new text во всех строчках данной секции.
* Метод subst_body: заменить регулярное выражение old text новым текстом new text во всех строчках данной секции.
<source lang="perl">
  $section->subst_body(qr'old text','new text');
  $section->subst_body(qr'old text','new text');
</source>
Пример: поменять во всех секциях, кроме %changelog, %{macros1} на %{macros2}.
Пример: поменять во всех секциях, кроме %changelog, %{macros1} на %{macros2}.
 
<source lang="perl">
  push @SPECHOOKS,  
  push @SPECHOOKS,  
  sub {
  sub {
Строка 354: Строка 385:
     }
     }
  };
  };
 
</source>
* Метод subst_body_if: заменить регулярное выражение old text новым текстом new text в строчках данной секции, которые подпадают под регулярное выражение 'anchor'.
* Метод subst_body_if: заменить регулярное выражение old text новым текстом new text в строчках данной секции, которые подпадают под регулярное выражение 'anchor'.
<source lang="perl">
  $section->subst_body_if(qr'anchor', qr'old text','new text');
  $section->subst_body_if(qr'anchor', qr'old text','new text');
 
</source>
Пример: поменять во всех секциях package во всех тегах Requires:
Пример: поменять во всех секциях package во всех тегах Requires:
и BuildRequires: bar-devel на libbar2-devel
и BuildRequires: bar-devel на libbar2-devel
 
<source lang="perl">
  push @SPECHOOKS,  
  push @SPECHOOKS,  
  sub {
  sub {
Строка 369: Строка 401:
     }
     }
  };
  };
 
</source>
* Метод exclude_body: убрать из секции строку, которая попадает под заданное регулярное выражение.
* Метод exclude_body: убрать из секции строку, которая попадает под заданное регулярное выражение.


Пример: убрать из секции %post устаревший макрос %update_desktopdb
Пример: убрать из секции %post устаревший макрос %update_desktopdb
<source lang="perl">
  $spec->get_section('post','')->exclude_body(qr'\%update_desktopdb');
  $spec->get_section('post','')->exclude_body(qr'\%update_desktopdb');
</source>
* Метод multi_exclude_body: убрать из секции несколько строк, которые поочередно попадают под заданный список регулярных выражений.
Пример: убрать из секции %build конструкцию, рассчитанную на другие дистрибутивы:
if ! rpm -E %%cmake|grep -q "cd build"; then
  %__mkdir_p build
  cd build
fi
<source lang="perl">
    $spec->get_section('build')->multi_exclude_body(
        qr'if\s*!\s*rpm\s+-E\s+\%\%cmake\s*\|\s*grep\s+-q\s+"cd\s*build";\s*then',
        qr'(__mkdir_p|mkdir\s+-p)\s+build',
        qr'cd\s+build',
        qr'fi');
</source>
===== как оформлять аргументы для простых методов =====
Их аргументы - строка или регулярное выражение.
Простые правила, для тех, кто мало знаком с perl;
* Добавляемые строки надо заканчивать концом строки  "\n", иначе они слипнутся со следующей строкой. Пример:
<source lang="perl">
# можно так (perl позволяет вставлять внутрь кавычек конец строки)
$spec->main_section->push_body('Requires: foo
');
# или же так
$spec->main_section->push_body('Requires: foo'."\n");
</source>
* в командах push_body, unshift_body, unshift_body_before, unshift_body_after можно свтавить не одну сторку, целый массив.
Кроме того, можно вставлять многострочные куски текста:
<source lang="perl">
$spec->main_section->push_body('# example
Requires: foo
Requires: bar
Requires: baz
');
</source>
* Регулярные выражения лучше брать в специальные кавычки qr'' (можно при необходимости и qr!!, qr{}, и т.д. см. синтаксис perl). Без них вы рискуете тем, что сложное регулярное выражение будет испорчено при подстановке.
<source lang="perl">
# вместо
$spec->main_section->exclude_body('^Requires: foo');
# пишите
$spec->main_section->exclude_body(qr'^Requires: foo');
</source>
===== продвинутые методы =====


Для пользователей, которые хорошо знакомы с perl, есть методы
Для пользователей, которые хорошо знакомы с perl, есть методы
Строка 383: Строка 465:


Пример: реализация subst_body_if через map_body
Пример: реализация subst_body_if через map_body
<source lang="perl">
  $section->map_body(sub {s/old text/new text/ if /anchor/});
  $section->map_body(sub {s/old text/new text/ if /anchor/});
 
</source>
* Метод visit_body -- то же, что и map_body, но возвращаемые значения игнорируются, тело секции не меняется.
* Метод visit_body -- то же, что и map_body, но возвращаемые значения игнорируются, тело секции не меняется.


Пример: реализация match_body через visit_body
Пример: реализация match_body через visit_body
<source lang="perl">
  my $match_found=0;
  my $match_found=0;
  $section->visit_body(sub {$match_found=1 if /pattern/});
  $section->visit_body(sub {$match_found=1 if /pattern/});
</source>


=== работа с предупреждениями ===
=== работа с предупреждениями ===
Строка 434: Строка 519:


=== Добавляем патчи ===
=== Добавляем патчи ===
Пусть у нас есть набор патчей от старой версии,
 
при чем приикладывемых из них только 2.
Пусть у нас есть (Условно) для пакета {{pkg|trinity-tqt3}}, который мы хотим сопровождать роботом импорта,
набор патчей от версии - предка, {{pkg|qt3}}, при чем всего патчей 9, а прикладываемых из них только 5.
В спеке {{pkg|qt3}} есть следуюшее:
<source lang="rpmspec">
<source lang="rpmspec">
# FC
# FC
Строка 455: Строка 542:
# Sergey A. Sukiyazov <sukiyazov@mail.ru>
# Sergey A. Sukiyazov <sukiyazov@mail.ru>
Patch9000: 9000-qt-x11-free-3.3.3-menubar.patch
Patch9000: 9000-qt-x11-free-3.3.3-menubar.patch
</source>


 
из них прикладываются только
%prep
<source lang="rpmspec">
%setup -n %name-%tde_version%{?preversion:~%preversion}
 
%patch21 -p1
%patch21 -p1
%patch22 -p1
%patch22 -p1
Строка 465: Строка 551:
%patch51 -p2
%patch51 -p2
%patch52 -p2
%patch52 -p2
</source>
остальные хотелось бы не прикладывая, подержать под рукой для будущих разборок.


</source>
Создадим папку {{path|patches}} и скопируем туда все патчи.
Создадим папку {{path|patches}} и скопируем туда все патчи.


Преобразуем Patch и %patch в команды add_patch.
Преобразуем Patch и %patch в команды add_patch.
Опцию -pN заменим на STRIP=>N,
Опцию -pN заменим на STRIP=>N,
если патч не применялся, добавим COMMENTED=>1,
если патч не прикладывается, добавим DISABLE=>1,
комментарий сохраним в опции HEADER=>'# comment text'."\n"
комментарий сохраним в опции HEADER=>'# comment text'."\n"


Отметим, что можно сохранить и номер патча, в опции NUMBER => XXX,
Отметим, что можно сохранить и номер патча, в опции NUMBER => XXX.
но не желательно, чтобы не породить в будущем конфликт,
При этом библиотека проверит этот номер на конфликт с уже имеющимися патчами
если апстрим добавит патч с тем же номером.
(вдруг апстрим добавил патч с тем же номером) и при необходимости увеличит номер, чтобы избежать конфликта.
 
Добавим полученные команды в файл {{path|hooks/trinity-tqt3.pl}}, в POSTHOOKS


<source lang="perl">
<source lang="perl">
     $spec->add_patch('qt-3.0.5-nodebug.patch', STRIP=>1, COMMENTED=>1, HEADER=>'# FC'."\n");
    [...]
     $spec->add_patch('qt-3.3.8d-xim.patch', STRIP=>1, COMMENTED=>1);
     $spec->add_patch('qt-3.0.5-nodebug.patch', STRIP=>1, DISABLE=>1, HEADER=>'# FC'."\n");
     $spec->add_patch('qt-3.3.8d-xim.patch', STRIP=>1, DISABLE=>1);


     $spec->add_patch('qt-3.0.5-fix-pyqt-config.patch', STRIP=>1, HEADER => '# MDK'."\n",);
     $spec->add_patch('qt-3.0.5-fix-pyqt-config.patch', STRIP=>1, HEADER => '# MDK'."\n",);
Строка 486: Строка 576:


     $spec->add_patch('qt3-never-strip.diff', STRIP=>0, HEADER => '# SuSE'."\n",);
     $spec->add_patch('qt3-never-strip.diff', STRIP=>0, HEADER => '# SuSE'."\n",);
     $spec->add_patch('shut-up.diff', STRIP=>0, COMMENTED=>1);
     $spec->add_patch('shut-up.diff', STRIP=>0, DISABLE=>1);


     $spec->add_patch('0046-qiconview-no-useless-scrollbar.diff', STRIP=>2, HEADER=>'# Qt-copy'."\n");
     $spec->add_patch('0046-qiconview-no-useless-scrollbar.diff', STRIP=>2, HEADER=>'# Qt-copy'."\n");
     $spec->add_patch('0078-argb-visual-hack.patch', STRIP=>2);
     $spec->add_patch('0078-argb-visual-hack.patch', STRIP=>2);


     $spec->add_patch('9000-qt-x11-free-3.3.3-menubar.patch', STRIP=>1, COMMENTED=>1,
     $spec->add_patch('9000-qt-x11-free-3.3.3-menubar.patch', STRIP=>1, DISABLE=>1,
                    NUMBER => 9000,
    HEADER => '# Sergey A. Sukiyazov <sukiyazov@mail.ru>'."\n",
    HEADER => '# Sergey A. Sukiyazov <sukiyazov@mail.ru>'."\n",
    );
    );
    [...]
</source>
Если сбрасывать патчи от разных пакетов в одну папку {{path|patches}} кажется неэстетичным, то можно
создать в {{path|patches}} подпапку {{path|patches/tqt3}} и переложить патчи туда.
Но тогда надо будет указать эту подпапку в команде add_patch: вместо
<source lang="perl">
$spec->add_patch('0078-argb-visual-hack.patch', STRIP=>2);
</source>
надо будет
<source lang="perl">
$spec->add_patch('tqt3/0078-argb-visual-hack.patch', STRIP=>2);
</source>
=== Работа с подпакетами ===
Подпакет - это набор секций с общим именем.
Пример: в спеке есть подпакет static (название не соответствует Alt Linux policy).
<source lang="rpmspec">
%package static
Group: Development/C
[...]
%description static
static library libfoo.a
%files static
[...]
</source>
* rename_package(oldname, newname)
можно так
<source lang="perl">
$spec->rename_package('static','devel-static');
</source>
или так
<source lang="perl">
$spec->rename_package('-n foo-static','-n foo-devel-static');
</source>
* disable_package(name)
<source lang="perl">
$spec->disable_package('static');
</source>
==== Пример: выделить подпакет. ====
Хочу из tqt3-apps-devel получить два пакета: libtqt3-apps и libtqt3-apps-devel:
<source lang="rpmspec">
%files -n tqt3-apps-devel
%{_libdir}/libtqtdesignercore.prl
%{_libdir}/libtqtdesignercore.so
%{_libdir}/libtqtdesignercore.so.1
%{_libdir}/libtqtdesignercore.so.1.0
%{_libdir}/libtqtdesignercore.so.1.0.0
%{_libdir}/libtqteditor.prl
%{_libdir}/libtqteditor.so
%{_libdir}/libtqteditor.so.1
%{_libdir}/libtqteditor.so.1.0
%{_libdir}/libtqteditor.so.1.0.0
%{_libdir}/libtqassistantclient.prl
%{_libdir}/libtqassistantclient.so
%{_libdir}/libtqassistantclient.so.1
%{_libdir}/libtqassistantclient.so.1.0
%{_libdir}/libtqassistantclient.so.1.0.0
%{_includedir}/tqt3/ntqassistantclient.h
</source>
Получить
<source lang="rpmspec">
%files -n libtqt3-apps
%{_libdir}/libtqtdesignercore.so.1
%{_libdir}/libtqtdesignercore.so.1.0
%{_libdir}/libtqtdesignercore.so.1.0.0
%{_libdir}/libtqteditor.so.1
%{_libdir}/libtqteditor.so.1.0
%{_libdir}/libtqteditor.so.1.0.0
%{_libdir}/libtqassistantclient.so.1
%{_libdir}/libtqassistantclient.so.1.0
%{_libdir}/libtqassistantclient.so.1.0.0
</source>
и
<source lang="rpmspec">
%files -n libtqt3-apps-devel:
%{_libdir}/libtqteditor.so
%{_libdir}/libtqtdesignercore.so
%{_libdir}/libtqtdesignercore.prl
%{_libdir}/libtqteditor.prl
%{_libdir}/libtqassistantclient.prl
%{_libdir}/libtqassistantclient.so
%{_includedir}/tqt3/ntqassistantclient.h
</source>
код преобразования:
<source lang="perl">
# сюда будем складывать тело будущей секции %files -n libtqt3-apps
my @libtqt3_apps_body;
# пройдемся по %files -n libtqt3-apps-devel
$spec->get_section('files','-n libtqt3-apps-devel')->map_body(
  sub {
    if (m!.*_libdir./lib.*\.so.*!) { # если строка из libtqt3-apps
      # move the line to future libtqt3-apps body
      push @libtqt3_apps_body, $_;
      # kill the line in -devel
      $_='';
    }
  }
);
# подпакета libtqt3-apps в спеке нет, поэтому создадим его посекционно
# создадим %files -n libtqt3-apps с телом секции - отобранными ранее строками.
$spec->add_section('files','-n libtqt3-apps')->push_body(@libtqt3_apps_body);
# создадим секцию %package -n libtqt3-apps и добавим в нее текст
$spec->add_section('package','-n libtqt3-apps')->push_body(
'Summary: shared libraries for TQt3 Developer applications
Group: System/Libraries
');
# создадим секцию %description -n libtqt3-apps и добавим в нее текст
$spec->add_section('description','-n libtqt3-apps')->push_body(
'This package is intended for developers who want to develop applications
using the additional libraries that ship with the applications
included with TQt; the TQt Designer and the TQt Assistant.
It allows integrating additional enhancements into the TQt Designer
respectively faciliate the TQt Assistant from within your TQt application
to interactively call the Assistant for displaying online help that the
developer includes with his application.
');
# подпакет libtqt3-apps создан.


# переименуем tqt3-apps-devel -> libtqt3-apps-devel
$spec->rename_package('-n tqt3-apps-devel', '-n libtqt3-apps-devel');
</source>
</source>


Строка 513: Строка 736:
== Ссылки ==
== Ссылки ==
* [http://vimeo.com/42547063 видеозапись выступления на YAPC::Russia + Perl Mova 2012]
* [http://vimeo.com/42547063 видеозапись выступления на YAPC::Russia + Perl Mova 2012]
* [http://ftp.linux.kiev.ua/pub/conference/peers/protva/2012/protva-2012.pdf тезисы доклада в Обнинске, 2012] (с. 57)
* [[Protva_summer_2012|тезисы доклада в Обнинске, 2012]] (с. 57)
* [http://ftp.linux.kiev.ua/pub/conference/2011/reports/vlasenko-package_conversion.pdf слайды доклада в Киеве, 2011]
* [http://mirror.yandex.ru/mirrors/ftp.linux.kiev.ua/conference/2011/reports/vlasenko-package_conversion.pdf слайды доклада в Киеве, 2011]
* [http://ftp.linux.kiev.ua/pub/conference/peers/protva/2011/protva-2011.pdf тезисы доклада в Обнинске, 2011] (с. 36)
* [[Protva_summer_2011|тезисы доклада в Обнинске, 2011]] (с. 36)


{| class="wide"
{| class="wide"

Текущая версия от 16:25, 23 декабря 2017


Утилиты для манипуляции spec-файлами и src.rpm пакетами. Введение во встроенный язык редактирования.

Введение

При работе со специализированной предметной областью хорошей стратегией является начать с создания специализированного языка, и далее продолжать работать с предметной областью на этом специализированном языке.

Эта стратегия была использована при создании различных утилит для манипуляции spec-файлами и src.rpm пакетами, таких как srpmnmu, srpmbackport, srpmconvert, набора утилит для массовых операций с пакетами girar-nmu, утилит repocop-nmu, автономных сервисов, таких как cronbuild, croncopy, cronbackports, autoports, fedoraimport и других.

В основе этих программ лежит библиотека perl-RPM-Source-Editor. Это библиотека perl, и при желании с ней можно работать как с любым другим perl'овым модулем. Однако все вышеперечисленные программы позволяют "на лету" изменять и дополнять свое поведение, подгружая с помощью опций --hook куски кода на perl, в котором можно напрямую работать с уже готовым инициализированным объектом, соответствующим spec-файлу или src.rpm пакету.

Поэтому не зря этот текст и назван введением во встроенный язык редактирования spec-файлов и src.rpm пакетов.

На кого рассчитано это введение? На всех, кто сопровождает более десятка пакетов. Не пристало бесконечно повторять руками монотонные операции, когда возможен unix-way: пишем небольшой скрипт и за 5 минут обрабатываем все 100 своих пакетов или вообще все 12.000 пакетов Сизифа.

В дальнейшем я попытаюсь проиллюстрировать этот текст примерами так, чтобы много простых повседневных задач можно было выполнить с помощью копипаста из приведенных примеров, не требуя знания perl.

Утилиты.

Сначала познакомимся, как пользоваться вышеупомянутыми утилитами, поскольку загружаемые через --hook расширения работают не самостоятельно, а с помощью этих утилит.

Утилиты выполняют начальную распаковку src.rpm пакета, чтение spec-файла, инициализируют необходимые объекты, выполняют свои действия и код, указанный пользователем в опциях --hook, и записывают новый измененный src.rpm либо spec-файл.

Эти утилиты обладают достаточно богатой встроенной функциональностью и разделяют ряд общих опций, унаследованных от модуля RPM::Source::Transform. Рассмотрим их на примере утилит srpmnmu и srpmtool.

Вызов srpmnmu:

srpmnmu --changelog '- Yes! We can!' /path/to/foo-<oldversion>-alt<release>.src.rpm

или

srpmnmu --changelog '- Yes! We can!' -i foo.spec

Опция --changelog может быть сокращена до --ch, а в некоторых утилитах --- и до -c.

Действие srpmnmu по умолчанию - инкрементировать релиз в соответствии со политикой nmuadd (добавить .1 к релизу, если нет точки в релизе; иначе увеличить число после точки; примеры: alt2 -> alt2.1, alt2.1 -> alt2.2 и т.д.) и добавить changelog.

Есть и другие политики увеличения релиза, которые выбираются опцией --next-release-policy (также, --nextrel). Например,

srpmnmu --nextrel=incr -- обычное увеличение релиза (alt2 -> alt3 и т.д.)
srpmnmu --nextrel=nmuappend -- "классический" nmu c расческой из единиц 

(alt2.1 -> alt2.1.1, alt2.1.1 -> alt2.1.1.1 и т.д.) Отключается увеличение релиза

srpmnmu --nextrel=none (это то же самое, что вызвать srpmtool;
srpmtool -- это srpmnmu с политикой увеличения релиза none по умолчанию.

Можно явно указать Release:

srpmnmu --release alt1.rc4_final

Можно явно указать Version:

srpmnmu --version <newversion>

При этом, если "<newversion>" > "<oldversion>", то релиз, если не

указан, будет сброшен в alt1. Если же у нас downgrade, 

"<newversion>" < "<oldversion>", то, если другое не указано, будет

увеличен и Serial, и Release (В присутствии Provides/Obsoletes это
наиболее разумное поведение).

Явно указать Serial: можно опциями --serial и --epoch, в зависимости от того, какой тег мы хотим получить в spec-файле.

С опцией --version, если редактируется src.rpm, а не spec-файл, обычно нужно еще указать архив исходных текстов новой версии. Т.е. набор опций для обновления src.rpm пакета до новой версии выглядит так:

srpmnmu --version <newversion> --copy_to_sources=foo-<newversion>.tar.gz /path/to/foo-<oldversion>-alt<release>.src.rpm

(Здесь без разницы, srpmnmu или srpmtool.)

Для библиотеки вместе с обновлением исходных текстов до новой версии можно выпустить и compat-версию:

srpmnmu --rename foo4 \
  --changelog '- compat library' \
  --group-translate 'Development/C|System/Legacy libraries,Development/C++|System/Legacy libraries' \
  /path/to/foo-<oldversion>-alt<release>.src.rpm

Есть еще опция --uupdate, автоматически обновляющая src.rpm, содержащий .watch файл, --group-translate, меняющая группы rpm, и много других.

Однако, понятно, что каким бы богатым не был бы набор опций, он не в состоянии покрыть всю необходимую функциональность.

Здесь на помощь приходит встроенный язык.

Загрузка полезного кода опциями --hook. hook по умолчанию.

Можно использовать сколько угодно опций --hook.

 srpmnmu --no-default-hook --hook /path/to/hook1.pl --hook ./hook2.pl --hook hook3.pl

В примере выше к hook1.pl и к ./hook2.pl указан путь, к hook3.pl путь не указан. Поэтому hook3.pl будет искаться в HOOKDIR, по умолчанию это ./hooks.

Также для каждого пакета утилиты будут пытаться загрузить его умолчальный файл-hook %name.pl, если явно не запретить такое поведение с помощью опции --no-default-hook,

Удобно, например, при подготовке транзакции с помощью girar-nmu создать папку hooks и складывать туда файлы %name.pl с персональными правками. Это позволит в любой момент сгенерировать транзакцию заново, даже если файлы в Сизифе за это время изменились. Как правило, эти правки стереотипные, т.е. в итоге в ./hooks будет 2-3 уникальных файла, а остальные - симлинки на эти уникальные файлы.

программирование на языке манипуляций spec-файлом

Синтаксис файлов --hook

начнем знакомство с файла hooks/template.pl, содержащего шаблон для наиболее частых правок, которые приходится вносить в спек-файлы при импорте из Fedora. На основе такого шаблона при необходимости удобно создавать личный hook пакета с именем hooks/%{name}.pl, который в дальнейшем будет автоматически использоваться при импорте следующих версий этого пакета в системе fedoraimport.

Итак, файл hooks/template.pl:

 push @SPECHOOKS, 
 sub {
    my ($spec, $parent) = @_;
    $spec->add_patch('',STRIP=>1);
    $spec->get_section('package','')->subst_body(qr'','');
    $spec->get_section('package','')->subst_body_if(qr'','',qr'Requires:');
    $spec->get_section('prep')->push_body(q!!."\n");
    $spec->get_section('package','')->unshift_body('BuildRequires: '."\n");
 };

Собственно, минимально необходимая обвязка - это

 push @SPECHOOKS, 
 sub {
    my ($spec, $parent) = @_;
 };

Мы видим, что в наш код утилита передает 2 объекта perl: $spec, который соответствует текущему редактируемому spec-файлу или src.rpm пакету, и $parent, который соответствует предку редактируемого пакета - например, предыдущей версии этого пакета в Сизифе при импорте или старой версии пакета при обновлении.

$spec определен всегда, $parent может и не быть определен. В наших простых примерах $parent не понадобится.

Методы объекта $spec делятся на методы, применяемые ко всему spec-файлу, и методы, применяемые к отдельным секциям spec-файла. Примером методов на уровне spec-файла является метод add_patch.

 push @SPECHOOKS, 
 sub {
    my ($spec, $parent) = @_;
    $spec->add_patch('foo-1.2-alt-fix-something.patch',STRIP=>3);
 };

Этот код скопирует файл foo-1.2-alt-fix-something.patch из ./patches в %_sourcedir данного пакета (возможно, временный каталог, созданный утилитой srpmnmu или ей родственной); Добавит в спек тег

PatchXX: foo-1.2-alt-fix-something.patch

где XX -- некоторый незанятый номер, и добавит в секцию %prep строку

%patchXX -p3

где 3 указано через параметр STRIP=>3.

Другой пример -- метод add_source. Допустим, мы хотим провести NMU -- добавить в пакеты файлы .service для systemd. Насобираем коллекцию .service файлов, названных по имени пакета (вида %name.service), в папке ./patches. Создадим файл add_systemd_service.pl

 push @SPECHOOKS, 
 sub {
    my ($spec, $parent) = @_;
    my $sourcenum=$spec->add_source('%name.service');
    $spec->get_section('install')->push_body(
    'install -Dm644 %{SOURCE'.$sourcenum.'} %buildroot%_systemd/%name.service'."n");
    $spec->get_section('files')->push_body('%_systemd/%name.service'."n");
 };

Передадим этот файл в утилиту

girar-nmu-prepare --hook add_systemd_service.pl ...

Метод add_source разворачивает '%name.service' в момент выполнения, поэтому наш код будет работать для каждого пакета, обрабатываемого с помощью girar-nmu utils, для которого найдется ./patches/%name.service. Метод add_source скопирует файл, добавит в спек тег

SourceXX: %name.service

Метод add_source возвращает число XX, которое позднее использовано в коде для добавления в секцию %install строки

install -Dm644 %{SOURCEXX} %buildroot%_systemd/%name.service

Методы редактирования на уровне секции.

В предыдущем примере использовался метод редактирования на уровне секции push_body. В действительности, использовалась связка

$spec->get_section('...'[,'...'])->push_body('some text'."\n");

Этот код можно еще переписать

my $section = $spec->get_section('install');
$section->push_body('some text'."\n");

Т.е. сначала получаем объект секции %install, затем добавляем в конец секции строку 'some text'."\n" .

Это отражает дизайн: сначала находим в spec-файлe нужную секцию, затем редактируем ее.

Что такое секции

spec файл делится на части, которые называются секции, с помощью специальных заголовков, которые начинаются с %. Примеры таких заголовков:

%description
%package
%prep
%post
...
%files

У этих заголовков могут быть аргументы, например, има подпакета:

%package devel

Начало spec файла не имеет заголовка, однако по смыслу это тоже отдельная секция %package (без аргументов) пакета по умолчанию.

Получаем секции

Как получить секцию? примеры с get_section:

# одна и та же главная секция (секция,
# с которой начинается любой spec-файл)
$spec->get_section('package',);
$spec->get_section('package');
# выделенный метод специально для главной секции
$spec->get_main_section;
# разные извращения, которые тоже работают,
# и в данном случае (Name: foo) возвращают главную секцию
$spec->get_section('package','-n foo');
$spec->get_section('package','-n %name');
# секция package для подпакета foo-doc
$spec->get_section('package','doc');
$spec->get_section('package','-n foo-doc');
$spec->get_section('package','-n %name-doc');
# секция files для подпакета foo-doc
$spec->get_section('files','doc');
# секция description (основная) для подпакета foo-doc
$spec->get_section('description','doc');
# секция description (ru_RU.CP1251) для подпакета foo-doc
$spec->get_section('description','doc','-l ru_RU.CP1251');

Секции можно найти и по-другому. Переберем все секции, какие есть в spec-файлe, и выберем те, которые нам нужны. Для этого у объекта $section есть методы get_type, get_canonical_package, get_package_name, get_raw_package.

Например, для секции files подпакета foo-doc
get_type равно 'files',
get_canonical_package равно 'doc',
get_package_name равно 'foo-doc'.

Для главной секции
get_type равно 'package',
get_canonical_package равно ,
get_package_name равно 'foo'.
get_raw_package равно .

Для секции description подпакета python-module-foo
get_type равно 'description',
get_canonical_package равно '-n python-module-foo',
get_package_name равно 'python-module-foo'.
get_raw_package равно '-n python-module-%name',

Метод get_raw_package возвращает название подпакета в точности в том виде, как оно записано в заголовке секции, с нераскрытыми макросами и пробелами между -n и названием.

Пример: сосчитаем число секций %changelog. Наш rpm не допускает больше одной секции %changelog, но в сети можно найти всякое.

push @SPECHOOKS, 
sub {
   my ($spec, $parent) = @_;
   my $count_changelog=0;
   foreach my $section ($spec->get_sections()) {
	 $count_changelog++ if $section->get_type() eq 'changelog';
   }
   print "What a horrible spec! $count_changelog changelogs." if $count_changelog>1;
};

Заметим, что метод get_sections создает временный массив. и не годится для некоторых специальных случаев, когда мы в цикле удаляем секции, которых еще не посетили. Для таких особых случаев лучше использовать итератор:

my $section = $spec->get_main_section;
do {
...
} while ($section=$section->next);

Работаем с секциями

Работа с тегами

Методы get_tag/set_tag/clear_tag позволяют работать с тегами (ключевые слова, оканчивающиеся на ":" : Name, Version, Release, Source3, ...). У get_tag есть опция RAW=>1; с этой опцией get_tag возвращает значение так, как оно записано в spec-файле, без нее - с раскрытыми макросами.

Пример: добавить путь из тега URL: в тег Source: (если в spec-файлe написано Source0:, то добавлено будет в Source0:)

 use File::Basename;
 push @SPECHOOKS, 
 sub {
    my ($spec, $parent) = @_;
    my $section=$spec->get_main_section;
    my $urlprefix=dirname($section->get_tag('URL', RAW=>1));
    $section->set_tag('Source',$urlprefix.'/'.$section->get_tag('Source', RAW=>1));
 };

Работа с телом секции

простые методы
  • Метод push_body (список строк) нам уже знаком. Он позволяет аккуратно вставить свои аргументы в конец секции, при чем если в конце есть пустые строки, условные макросы %if,... то пустые строки, условные макросы останутся в конце.
  • Метод unshift_body (список строк): вставить в начало секции, после заголовка секции. Пример: вставить в spec-файл %define _unpackaged_files_terminate_build 1.
  • Метод match_body(qr'регулярное выражение'): проверить, не встречается ли в секции заданное регулярное выражение.

Пример: Если в спек-файле не определен макрос _unpackaged_files_terminate_build, добавить его.

push @SPECHOOKS, 
sub {
   my ($spec, $parent) = @_;
   my $section=$spec->get_main_section;
   $section->unshift_body('# better safe than sorry
%define _unpackaged_files_terminate_build 1
') unless $section->match(qr'\%define\s+_unpackaged_files_terminate_build');
};
  • Метод unshift_body_after (qr'регулярное выражение', список строк): вставить после первой строки, подпадающей под qr'регулярное выражение'.
  • Метод unshift_body_before (qr'регулярное выражение', список строк): вставить перед первой строкой, подпадающей под qr'регулярное выражение'.
  • Метод subst_body: заменить регулярное выражение old text новым текстом new text во всех строчках данной секции.
 $section->subst_body(qr'old text','new text');

Пример: поменять во всех секциях, кроме %changelog, %{macros1} на %{macros2}.

 push @SPECHOOKS, 
 sub {
    my ($spec, $parent) = @_;
    foreach my $section ($spec->get_sections) {
    	next if $section->get_type eq 'changelog';
        $section->subst_body(qr'(?<!%)\%{macros1}','%{macros2}');
        $section->subst_body(qr'(?<!%)\%macros1','%macros2');
    }
 };
  • Метод subst_body_if: заменить регулярное выражение old text новым текстом new text в строчках данной секции, которые подпадают под регулярное выражение 'anchor'.
 $section->subst_body_if(qr'anchor', qr'old text','new text');

Пример: поменять во всех секциях package во всех тегах Requires: и BuildRequires: bar-devel на libbar2-devel

 push @SPECHOOKS, 
 sub {
    my ($spec, $parent) = @_;
    foreach my $section ($spec->get_sections) {
    	next if $section->get_type ne 'package';
        $section->subst_body_if(qr'Requires:',qr'bar-devel','libbar2-devel');
    }
 };
  • Метод exclude_body: убрать из секции строку, которая попадает под заданное регулярное выражение.

Пример: убрать из секции %post устаревший макрос %update_desktopdb

 $spec->get_section('post','')->exclude_body(qr'\%update_desktopdb');
  • Метод multi_exclude_body: убрать из секции несколько строк, которые поочередно попадают под заданный список регулярных выражений.

Пример: убрать из секции %build конструкцию, рассчитанную на другие дистрибутивы:

if ! rpm -E %%cmake|grep -q "cd build"; then
  %__mkdir_p build
  cd build
fi
    $spec->get_section('build')->multi_exclude_body(
        qr'if\s*!\s*rpm\s+-E\s+\%\%cmake\s*\|\s*grep\s+-q\s+"cd\s*build";\s*then',
        qr'(__mkdir_p|mkdir\s+-p)\s+build',
        qr'cd\s+build',
        qr'fi');
как оформлять аргументы для простых методов

Их аргументы - строка или регулярное выражение.

Простые правила, для тех, кто мало знаком с perl;

  • Добавляемые строки надо заканчивать концом строки "\n", иначе они слипнутся со следующей строкой. Пример:
# можно так (perl позволяет вставлять внутрь кавычек конец строки)
$spec->main_section->push_body('Requires: foo
');
# или же так
$spec->main_section->push_body('Requires: foo'."\n");
  • в командах push_body, unshift_body, unshift_body_before, unshift_body_after можно свтавить не одну сторку, целый массив.

Кроме того, можно вставлять многострочные куски текста:

$spec->main_section->push_body('# example
Requires: foo
Requires: bar
Requires: baz
');
  • Регулярные выражения лучше брать в специальные кавычки qr (можно при необходимости и qr!!, qr{}, и т.д. см. синтаксис perl). Без них вы рискуете тем, что сложное регулярное выражение будет испорчено при подстановке.
 # вместо
 $spec->main_section->exclude_body('^Requires: foo');
 # пишите
 $spec->main_section->exclude_body(qr'^Requires: foo');
продвинутые методы

Для пользователей, которые хорошо знакомы с perl, есть методы map_body и visit_body. Аргументом для них является ссылка на функцию, в которую метод построчно передает тело секции в специальной переменной perl $_.

  • Метод map_body -- функция пользователя возвращает измененную строку в той же специальной переменной perl $_, в которой было оригинальное значение.

Пример: реализация subst_body_if через map_body

 $section->map_body(sub {s/old text/new text/ if /anchor/});
  • Метод visit_body -- то же, что и map_body, но возвращаемые значения игнорируются, тело секции не меняется.

Пример: реализация match_body через visit_body

 my $match_found=0;
 $section->visit_body(sub {$match_found=1 if /pattern/});

работа с предупреждениями

Если операция не удалась, библиотека выдает предупреждение. В некоторых случаях это нежелательно. К примеру, пусть пакет не собрался с диагностикой

sisyphus_check: check-intersects ERROR: intersections with system packages /usr/share/icons/hicolor/48x48/apps

рассмотрим код, который удаляет из секций %files строки вида %dir %{_datadir}/icons/hicolor.*

foreach my $section ($spec->get_sections()) {
    next if $section->get_type() ne 'files';
    $section->exclude_body(qr'^\%dir\s+\%\{_datadir\}/icons/hicolor');
}

Если в пакете 6 подпакетов (соответственно, 6 секций %files), а %dir %{_datadir}/icons/hicolor.* найдена только в 2-х из них, то 4 команды exclude_body завершатся неудачей, о чем будут выданы предупреждения. Поскольку так и задумано, что часть команд exclude_body завершатся неудачей, предупреждения можно убрать. Для этого перед их выполнением нужно окружить вызовами applied_off/applied_on

$spec->applied_off();
foreach my $section ($spec->get_sections()) {
    next if $section->get_type() ne 'files';
    $section->exclude_body(qr'^\%dir\s+\%\{_datadir\}/icons/hicolor');
}
$spec->applied_on();

Однако полное отключение предупреждений неудобно тем, что в следующем релизе апстрим может совсем убрать %dir %{_datadir}/icons/hicolor из spec-файла, а с отключенными предупреждениями мы об этом не узнаем и будем хранить в hook бесполезный уже код. Поэтому в таких случаях лучше пользоваться вызовом applied_block (" строка предупреждения ", sub{ блок кода })

$spec->applied_block(
  "убрать %dir %{_datadir}/icons/hicolor из %files",
  sub {
    foreach my $section ($spec->get_sections()) {
        next if $section->get_type() ne 'files';
        $section->exclude_body(qr'^\%dir\s+\%\{_datadir\}/icons/hicolor');
    }
  }
);

Такой код будет молчать, если хоть одна операция применена успешно, но предупредит, если ни в одной секции операцию применить не удалось.

Добавляем патчи

Пусть у нас есть (Условно) для пакета trinity-tqt3, который мы хотим сопровождать роботом импорта, набор патчей от версии - предка, qt3, при чем всего патчей 9, а прикладываемых из них только 5. В спеке qt3 есть следуюшее:

# FC
Patch2: qt-3.0.5-nodebug.patch
Patch3: qt-3.3.8d-xim.patch

# MDK
Patch21: qt-3.0.5-fix-pyqt-config.patch
Patch22: qt3-opentype-aliasing.patch

# SuSE
Patch30: qt3-never-strip.diff
Patch31: shut-up.diff

# Qt-copy
Patch51: 0046-qiconview-no-useless-scrollbar.diff
Patch52: 0078-argb-visual-hack.patch

# Sergey A. Sukiyazov <sukiyazov@mail.ru>
Patch9000: 9000-qt-x11-free-3.3.3-menubar.patch

из них прикладываются только

%patch21 -p1
%patch22 -p1
%patch30 -p0
%patch51 -p2
%patch52 -p2

остальные хотелось бы не прикладывая, подержать под рукой для будущих разборок.

Создадим папку patches и скопируем туда все патчи.

Преобразуем Patch и %patch в команды add_patch. Опцию -pN заменим на STRIP=>N, если патч не прикладывается, добавим DISABLE=>1, комментарий сохраним в опции HEADER=>'# comment text'."\n"

Отметим, что можно сохранить и номер патча, в опции NUMBER => XXX. При этом библиотека проверит этот номер на конфликт с уже имеющимися патчами (вдруг апстрим добавил патч с тем же номером) и при необходимости увеличит номер, чтобы избежать конфликта.

Добавим полученные команды в файл hooks/trinity-tqt3.pl, в POSTHOOKS

    [...]
    $spec->add_patch('qt-3.0.5-nodebug.patch', STRIP=>1, DISABLE=>1, HEADER=>'# FC'."\n");
    $spec->add_patch('qt-3.3.8d-xim.patch', STRIP=>1, DISABLE=>1);

    $spec->add_patch('qt-3.0.5-fix-pyqt-config.patch', STRIP=>1, HEADER => '# MDK'."\n",);
    $spec->add_patch('qt3-opentype-aliasing.patch', STRIP=>1);

    $spec->add_patch('qt3-never-strip.diff', STRIP=>0, HEADER => '# SuSE'."\n",);
    $spec->add_patch('shut-up.diff', STRIP=>0, DISABLE=>1);

    $spec->add_patch('0046-qiconview-no-useless-scrollbar.diff', STRIP=>2, HEADER=>'# Qt-copy'."\n");
    $spec->add_patch('0078-argb-visual-hack.patch', STRIP=>2);

    $spec->add_patch('9000-qt-x11-free-3.3.3-menubar.patch', STRIP=>1, DISABLE=>1,
                     NUMBER => 9000,
		     HEADER => '# Sergey A. Sukiyazov <sukiyazov@mail.ru>'."\n",
		     );
    [...]

Если сбрасывать патчи от разных пакетов в одну папку patches кажется неэстетичным, то можно создать в patches подпапку patches/tqt3 и переложить патчи туда. Но тогда надо будет указать эту подпапку в команде add_patch: вместо

 $spec->add_patch('0078-argb-visual-hack.patch', STRIP=>2);

надо будет

 $spec->add_patch('tqt3/0078-argb-visual-hack.patch', STRIP=>2);

Работа с подпакетами

Подпакет - это набор секций с общим именем.

Пример: в спеке есть подпакет static (название не соответствует Alt Linux policy).

%package static
Group: Development/C
[...]
%description static
static library libfoo.a

%files static
[...]
  • rename_package(oldname, newname)

можно так

 $spec->rename_package('static','devel-static');

или так

 $spec->rename_package('-n foo-static','-n foo-devel-static');
  • disable_package(name)
 $spec->disable_package('static');

Пример: выделить подпакет.

Хочу из tqt3-apps-devel получить два пакета: libtqt3-apps и libtqt3-apps-devel:

%files -n tqt3-apps-devel
%{_libdir}/libtqtdesignercore.prl
%{_libdir}/libtqtdesignercore.so
%{_libdir}/libtqtdesignercore.so.1
%{_libdir}/libtqtdesignercore.so.1.0
%{_libdir}/libtqtdesignercore.so.1.0.0
%{_libdir}/libtqteditor.prl
%{_libdir}/libtqteditor.so
%{_libdir}/libtqteditor.so.1
%{_libdir}/libtqteditor.so.1.0
%{_libdir}/libtqteditor.so.1.0.0
%{_libdir}/libtqassistantclient.prl
%{_libdir}/libtqassistantclient.so
%{_libdir}/libtqassistantclient.so.1
%{_libdir}/libtqassistantclient.so.1.0
%{_libdir}/libtqassistantclient.so.1.0.0
%{_includedir}/tqt3/ntqassistantclient.h

Получить

%files -n libtqt3-apps
%{_libdir}/libtqtdesignercore.so.1
%{_libdir}/libtqtdesignercore.so.1.0
%{_libdir}/libtqtdesignercore.so.1.0.0
%{_libdir}/libtqteditor.so.1
%{_libdir}/libtqteditor.so.1.0
%{_libdir}/libtqteditor.so.1.0.0
%{_libdir}/libtqassistantclient.so.1
%{_libdir}/libtqassistantclient.so.1.0
%{_libdir}/libtqassistantclient.so.1.0.0

и

%files -n libtqt3-apps-devel:
%{_libdir}/libtqteditor.so
%{_libdir}/libtqtdesignercore.so
%{_libdir}/libtqtdesignercore.prl
%{_libdir}/libtqteditor.prl
%{_libdir}/libtqassistantclient.prl
%{_libdir}/libtqassistantclient.so
%{_includedir}/tqt3/ntqassistantclient.h

код преобразования:

# сюда будем складывать тело будущей секции %files -n libtqt3-apps
my @libtqt3_apps_body;
# пройдемся по %files -n libtqt3-apps-devel
$spec->get_section('files','-n libtqt3-apps-devel')->map_body(
  sub {
    if (m!.*_libdir./lib.*\.so.*!) { # если строка из libtqt3-apps
      # move the line to future libtqt3-apps body
      push @libtqt3_apps_body, $_;
      # kill the line in -devel
      $_='';
    }
  }
);

# подпакета libtqt3-apps в спеке нет, поэтому создадим его посекционно

# создадим %files -n libtqt3-apps с телом секции - отобранными ранее строками.
$spec->add_section('files','-n libtqt3-apps')->push_body(@libtqt3_apps_body);

# создадим секцию %package -n libtqt3-apps и добавим в нее текст
$spec->add_section('package','-n libtqt3-apps')->push_body(
'Summary: shared libraries for TQt3 Developer applications
Group: System/Libraries
');

# создадим секцию %description -n libtqt3-apps и добавим в нее текст
$spec->add_section('description','-n libtqt3-apps')->push_body(
'This package is intended for developers who want to develop applications
using the additional libraries that ship with the applications
included with TQt; the TQt Designer and the TQt Assistant.
It allows integrating additional enhancements into the TQt Designer
respectively faciliate the TQt Assistant from within your TQt application
to interactively call the Assistant for displaying online help that the
developer includes with his application.
');
# подпакет libtqt3-apps создан.

# переименуем tqt3-apps-devel -> libtqt3-apps-devel
$spec->rename_package('-n tqt3-apps-devel', '-n libtqt3-apps-devel');

debug

Волшебная опция для создания digest.diff файла

--SET RPM::Source::TransformContainer::PLAYER=RPM::Source::Transformation::DiffWritePlayer \

и усиленный ее вариант, если нужно найти конкретный фильтр:

--SET RPM::Source::TransformContainer::PLAYER=RPM::Source::Transformation::DiffWritePlayer \
--SET RPM::Source::Transformation::Factory::DependencyFilter::group_filters_by_transformation=0

To be continued ...

Это, конечно, не вся функциональность. API еще в разработке, и я упомянул только самую стабильную его часть. Но представление о возможностях и первое знакомство это введение дает.

Ссылки

Разработано при поддержке Фонда содействия развитию МП НТС в рамках НИОКР 01201066526 rigft