Сборка Debian пакетов для расширений PHP

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

Ещё немножко: тут опущены довольно важные моменты деятельности Debian Maintainer, такие, как: правильно указывать релизы, как пройти линтиан, подписать код, и вообще, стать молодцом и выложить пакет в паблик по строгим гайдлайнам Debian. Это внутренняя сборка. Но всё в ваших руках. Если вы бородатый линукс‑мэйнтэйнер, то для вас этот текст может быть крайне наивным, пожалуйста не надорвитесь от смеха.

Мы в своих проектах активно используем PHP, плохо ли это или хорошо, модно ли или не модно, оставим за скобками, оно работает, каши не просит и успешно выполняет свои функции. А ещё у нас всё это крутится в stateful‑контейнерах под окружением Debian‑like операционной системы (в нашем случае Ubuntu 22.04 LTS, но это не сильно принципиально).

Профессор Фортран плохого не посоветует

Профессор Фортран плохого не посоветует

Проблематика: исторически сложилось, что мы используем некоторые не очень популярные расширения PHP, написанные и кое как (почти никак) поддерживаемые своими авторами, и этих расширений нет в типовых источниках deb-пакетов. У нас постулируется принцип «нормально делай — нормально будет», поэтому у нас не приветствуется slackware-style установка бинарников в систему вне пакетных менеджеров. Поэтому будем собирать полноценные deb-пакеты для расширений PHP, не ломая совместимость с существующим окружением.

Собственно, сабжевые расширения PHP, с чего всё началось: Blitz за авторством Алексея Рыбака и php-rdkafka имени Arnaud Le Blanc. Проблема у них схожая, хотя у Blitz более серьёзная: пакетов нет в стандартных репозиториях, последний релиз был давно и с момента тэга релиза автор и комьюнити накидали немало полезных патчей, некоторые из которых ещё и фиксируют проблемы совместимости с актуальными версиями PHP, которых не существовало на момент выпуска последних релизов. Тезисно: пакетов нет, релизы были давно, есть в HEAD’е полезные патчи.

Ну что, пошли получать ачивку Debian Package Maintainer домашнего разлива.

Hint: некоторые файлы окружения можно сгенерировать автоматическими средствами, либо выстродать вручную, но не всегда это удобно. Иногда проще скопипастить с готового решения, чем не стоит пренебрегать.

Как выглядит типичная инструкция сборки расширения PHP?

  • установить нужную версию PHP и сборочный пакет для неё (phpM.N-dev);

  • поставить требуемые зависимые либы и их dev-пакеты (например librdkafka и librdkafka-dev для php-rdkafka);

  • склонировать репозиторий или скачать и распаковать архив исходного кода расширения;

  • выполнить в директории phpize;

  • выполнить ./configure;

  • выполнить make;

  • выполнить make install;

Далее нужно найти требуемый файл разделяемой библиотеки и добавить в php.ini, чтобы оно подхватилось интерпретатором. Наверное, большинство так и делает, особенно в минималистических и stateless-окружениях типа Docker. Но мы будем собирать пакеты.

Вообще, есть целый хэндбук как собирать пакеты под Debian, там очень много полезной информации, крайне рекомендуется к прочтению, чтобы не тыкаться вслепую и не удивляться, откуда какая чёрная магия происходит. По тексту я буду давать полезные ссылки на этот трактат, но в целом, предполагается, что читатель ознакомится хоть как-то с базовыми понятиями.

Структура пакета

Типичный deb-пакет — это архив с бинарниками и иной payload + немного meta-информации и управляющих скриптов для установщика (dpkg, apt, etc.). Его можно только поставить, это конечный продукт сборки софта под конкретную архитектуру и с конкретными зависимостями. Разбирать его довольно бессмысленно, там нет исходников. Справедливости ради, исходники тоже могут храниться в deb-пакетах, но это отдельная категория пакетов, так называемые deb-src, в нашем случае их нет и начинать придётся с чистого листа.

Итак, подготовим директорию для будущего пакета. Это должна быть пустая директория без хлама внутри. Всю работу будем делать внутри этой директории и файловая структура будет считаться именно от этой директории как от рутовой. Назовём будущую директорию пакета php-что-то-там, как это общепринято, в нашем случай пусть это будет php-rdkafka.

Вся debian-магия требует набора стандартизированных файлов во вложенной директории debian, там на самом деле ничего сложного. Тем более нам будет помогать хелпер для deb-хелпера под названием dh-php. Зачем он нужен? Вообще сборка deb-пакета относительно стандартная пошаговая процедура вне зависимости что собирается, пакет документации или офисный пакет с кучей зависимости. Почти всё можно собрать без хелперов, но это будет титанический ручной труд, поэтому nix-мэйнтэйнеры понавводили огромное количество хелперов на любые случаи жизни. Да, Makefile для пакетов мало кто пишет сейчас вручную. Внутри сборочного процесса для deb-пакетов есть довольно большой набор шагов и каждый из них может быть переопределён и кастомизирован, чтобы собрать что нужно как нужно. Эти переопределения находятся в файле debian/rules. По формату этот файл является Makefile и, как правило, пустой (не переопределять ничего). Добавляем в него требуемый хелпер dh-php через include, теперь сборку будет контролировать dh-php, который очень много чего уже переопределяет, чтобы php-пакеты собрались «каноничными»:

debian/rules:

#!/usr/bin/make -f
include /usr/share/dh-php/pkg-pecl.mk

Далее нужен контрольный файл, место которого в debian/control. В нём задаётся мета-информация будущего пакета. Он имеет строгий синтаксис, который описан в хэндбуке, проще всего взять существующий от иного пакета и допилить до требуемого состояния. От содержимого этого файла зависит:

  • названия пакетов и указание типа софта

  • описание, ссылки, имена мэйнтейнеров

  • перечень зависимостей для сборки или установки пакета

Коли мы начали препарировать php-rdkafka, будем разбирать файл от этого пакета:

debian/control:

Source: php-rdkafka
Section: php
Priority: optional
Maintainer: Habr Team 
Uploaders: Vadim Rybalko 
Build-Depends: debhelper (>= 10~),
               dh-php (>= 4~),
               liblz4-dev,
               libzstd-dev,
               librdkafka-dev (>= 0.11~),
               php-all-dev
Standards-Version: 4.5.1
Homepage: https://pecl.php.net/package/rdkafka

Package: php-rdkafka
Priority: optional
Section: php
Architecture: amd64
Pre-Depends: php-common (>= 2:69~)
Depends: ${misc:Depends},
         ${pecl:Depends},
         ${php:Depends},
         ${shlibs:Depends}
Breaks: ${pecl:Breaks}
Replaces: ${pecl:Replaces}
Suggests: ${pecl:Suggests}
Provides: ${pecl:Provides},
          ${php:Provides}
Description: PHP-rdkafka is a stable Kafka client for PHP based on librdkafka
 .

Секция Source описывает сборку, Package — бинарный пакет. Мета-информация очевидная, в зависимостях прописаны базовые + специфичные для конкретного пакета. Есть маленькая хитрость: этот файл будет пересоздаваться при сборке, но без него на ряде этапов будут ошибки. Поэтому этот файл поместим под именем debian/control.in и продублируем под традиционным debian/control. Файл debian/control.in будет использоваться как шаблон для создания debian/control. Почему? Современный стиль PHP-пакетов для Debian подразумевает наличие номера версии PHP в названии пакета, к счастью dh-php пересоздаст debian/control с длинной портянкой секций пакетов под каждую из версий PHP, используя debian/control.in как шаблон.

На этом этапе у нас три файла

  • debian/control

  • debian/control.in

  • debian/rules

Далее нужен файл совместимости с путём debian/compat, он совсем простой и содержит только версию совместимости debhelper, например, десятой:

debian/compat:

10

Дальше есть опциональный файл debian/gbp.conf для серии утилит gbp-buildpackage, gbp-dch, etc., мы их не используем, но файл всё равно лежит на всякий случай, в том числе чтобы автоматически билдить из гита использую тэги. Честно говоря, утилиты не были осилены, может как-нибудь в следующий раз. Но в гите, тем не менее, всё держим строго согласно описанной структуре бранчей и тэгов, на будущее:

debian/gbp.conf:

[DEFAULT]
debian-branch = debian/main
debian-tag = debian/%(version)s
upstream-branch = upstream
upstream-tag = upstream/%(version)s
pristine-tar = True

[dch]
meta = 1

[import-orig]
filter = ['.gitignore','debian']

Здесь описано, что основная ветка называется debian/main, каждый релиз имеет тэг debian/%(version)s, ветка для внешних исходников upstream, каждый набор исходников помечен тэгом upstream/%(version)s. Как это всё реализуется на практике будет описано ниже, пока гита в директории всё равно нет.

Контролочка: на этом этапе у нас пять файлов

  • debian/compat

  • debian/control

  • debian/control.in

  • debian/gbp.conf

  • debian/rules

Далее два специфичных dh-php файла, которые используются для создания ini-файла для автоматического include при установке конечного пакета в систему. Первый debian/php-rdkafka.php содержит ссылку на второй:

debian/php-rdkafka.php:

mod debian/rdkafka.ini

Второй debian/rdkafka.ini не сильно навороченнее:

debian/rdkafka.ini:

extension=rdkafka.so

Сразу стоит добавить определение формата патчей, так как нам придётся их накладывать. Тут выбор невелик и в 95 прцентов случаев это quilt, так и запишем в файле debian/source/format:

3.0 (quilt)

Если вас нужда заставила использовать lintian, например, вы хотите выложить свою сборку на Launchpad, то придётся освоить override-файлы, так как подавляющее большинство php-исходников сгенерируют ворнинги на ровном месте. Пока можем просто создать файл-плейсхолдер, на будущее, это будет файл debian/source.lintian-overrides с нехитрым содержимым:

# Override invalid PHP license problem for PHP extensions

Контролочка: на этом этапе у нас девять файлов

Почти закончили с директорией debian. Осталось разобрать две сущности: файл изменений debian/changelog и директорию патчей, но про них будет в отдельной секции. Итак, файл debian/changelog содержит в себе номера версий и список изменений. Это не бесполезный файл, предназначенный исключительно для кожанного мешка, какими обычно бывают release notes. При сборке пакета номер версии берётся именно отсюда. Вообще, этот файл можно генерировать из гита, но я не смог это осилить, поэтому правлю руками либо через dch. Он имеет свой строгий синтаксис, фрагмент его выглядит вот так:

debian/changelog:

php-rdkafka (6.0.3+4.1.2-2) stable; urgency=medium

  * Release for version 6.0.3
   - Ability to provide custom `librdkafka` path during pecl install (#526, @Wirone)
  * Unreleased patches for 6.0.3
   - Automation at Sat Jul 2 15:10:53 2022 +0200
   - Update release notes at Sat Jul 2 15:15:04 2022 +0200
   - Add private constructor on Metadata classes (#531) at Tue Jul 26 20:44:00 2022 +0200
   - Test against PHP 8.3 and librdkafka 1.9, 2.3 (#545) at Mon Dec 4 15:01:53 2023 +0100
   - feat: implement oauthbearer token refresh cb setter (#546) at Fri Jan 5 14:11:25 2024 -0500
   - Update README.md at Sat Jun 1 13:35:04 2024 +0200
  * Release for version 4.1.2
   - Enabled features on windows build: headers, purge, murmur (#410, @nick-zh, @cmb69)

 -- Vadim Rybalko   Mon, 28 Jul 2024 01:08:0230 +0100

php-rdkafka (6.0.3+4.1.2-1) stable; urgency=medium

  * Release for version 6.0.3
   - Ability to provide custom `librdkafka` path during pecl install (#526, @Wirone)
  * Release for version 4.1.2
   - Enabled features on windows build: headers, purge, murmur (#410, @nick-zh, @cmb69)

 -- Vadim Rybalko   Fri, 25 Nov 2022 19:30:45 +0200

php-rdkafka (6.0.2+4.1.2-1) stable; urgency=medium

  * Release for version 6.0.2
   - Fixed signature of RdKafka\KafkaConsumer::getMetadata() (#521, @arnaud-lb)
  * Release for version 4.1.2
   - Enabled features on windows build: headers, purge, murmur (#410, @nick-zh, @cmb69)

 -- Vadim Rybalko   Fri, 25 Nov 2022 19:19:50 +0200

...

На что тут нужно обратить внимание? Каждая секция состоит из названия пакета, версии или версий (про это чуть позже), приоритета, описательной части и подписи. Номер версии имеет свой формат. Если мы взяли релиз из апстрима и захотели его добавить в ченжлог, то просто указываем версию в нотации мэйнтэйнера исходного кода. Если мы модифицируем (а мы как минимум добавляем debian-оснастку), то указываем номер модификации через дефис. В примере выше используется конструкция с плюсом, так можно выразить пакеты под несколько версий для обратной совместимости со старыми версиями PHP, разработка под которые остановлена мэйнтейнером ранее. Чуть далее будет понятно как эта магия работает. Если мы добавили патчей относительно старого релиза исходного кода, то итерируем версию модификации после дефиса, это понадобится при добавлении патчей из мердж-реквестов в исходном гит-репозитории.

Пока у нас нет исходного кода, файл debian/changelog можно создать пустым и напихать в него позже.

Итак, почти всё готово, пора вытаскивать исходники и закукоживать это всё воедино. Смотрим в репозиторий исходного кода, что там вообще с релизами, ищем свежий, чекаутим его и сохраняем в сабдиректорию с номером версии. Как альтернатива, можно скачать архив и распаковать его:

Мне не нравится такой подход, а вытаскиваю из гитовой репки из соседней директории, тем не менее так проще всего.

Мне не нравится такой подход, а вытаскиваю из гитовой репки из соседней директории, тем не менее так проще всего.

Получится такая иерархия файлов:

Где три точки — это файлы исходного кода. Теперь пошаримся среди них и найдём файл package.xml. Это довольно важный файл и там есть необходимая контрольная информация для dh-php. Например, указание на коридор совместимых версий PHP:

...
 
  
   
    7.0.0
    8.99.99
   
   
    1.4.8
   
  
 
...

Как видно, с версией PHP 5.6 эта версия php-rdkafka не совместима. Нам не очень это нужно, но бывают более жёсткие варианты, у того же Blitz новый потолок версий для каждого из мажорных релизов PHP (а местами проблемы совместимости и минорных). Поэтому потренируемся на rdkafka и включим возможность собрать пакет и под PHP 5.6 тоже. Путём изучения истории изменений устанавливаем, что последняя совместимая с 5.6 версия — это 4.1.2. Скачиваем её, распаковываем рядом. Теперь мы видим две директории с исходниками: rdkafka-6.0.3 и rdkafka-4.1.2. В каждой есть свой package.xml. Далее скопируем эти XML-файлы в корень директории. Актуальный под своим именем package.xml, а от 4.1.2 под именем package-5.xml. Формат именования важен, dh-php прямо ищет xml-файлы под нужным именем и версией мажор или мажор.минор через дефис:

$ cp rdkafka-6.0.3/package.xml package.xml
$ cp rdkafka-4.1.2/package.xml package-5.xml

Ещё раз контрольно пройдёмся по иерархии файлов в директории:

NB! Файла package.xml в исходниках может не быть. Строго говоря, это управляющий файл PECL и прямого отношения к исходникам расширения не имеет. Если его нет, придётся собрать его руками, либо специальной утилитой, толку от которой немного (дольше разбираться). Файл небольшой, довольно декларативный. Смело можно брать от иного пакета и быстро переписать содержимое: метаинформацию, ченжлог и список файлов (который не обязателен, кстати).

Вот сейчас самое время проинициализировать гит на всякий случай. Делаем git init, следом создаём ветку upstream и добавляем туда файлы исходного кода:

(our_repo) $ git init
(our_repo) $ git checkout -b upstream
(our_repo) $ git add rdkafka-4.1.2 rdkafka-6.0.3

Коммитим с типовым текстом (я его где-то подсмотрел в репозиториях автора dh-php Ondrej Sury) и вешаем тэг:

(our_repo) $ git commit -m "New upstream version 6.0.3+4.1.2"
(our_repo) $ git tag upstream/6.0.3+4.1.2

Теперь создаём ветку debian/main, переходим в неё и мержимся из соответствующеготэга апстрима:

(our_repo) $ git checkout -b debian/main
(our_repo) $ git merge upstream/6.0.3+4.1.2

Настало время напихать в debian/changelog, как планировалось ранее. Вытаскиваем вехи ченжлога из package.xml или релизной страницы исходного кода и заполняем новую секцию в файле. Теперь почти всё готово для сборки релиза.

Закинем в гит что получилось. Добавляем всё, что осталось, это XML-файлы и директория debian, коммитим в ветку debian/main и вешаем тэг:

(our_repo) $ git add *.xml debian
(our_repo) $ git commit -m "Release for 6.0.3+4.1.2-1"
(our_repo) $ git tag debian/6.0.3+4.1.2-1

У нас теперь две ветки и два тэга:

(our_repo) $ git branch
* debian/main
  upstream
(our_repo) $ git tag
debian/6.0.3+4.1.2-1
upstream/6.0.3+4.1.2

Сборка

NB! Скорее всего у нас сразу не получится собрать и что-то пойдёт не так. Но попытка не пытка.

Ставим build-essential (странно, что у вас он ещё не стоит) и пакеты, перечисленные в Build-Depends в файле debian/control. Далее ставим dev-пакеты всех версий PHP, под которые мы хотим собрать пакет. У нас будет фулл-хауз:

# apt install php5.6-dev php7.0-dev php7.1-dev \
php7.2-dev php7.3-dev php7.4-dev php8.0-dev \
php8.1-dev php8.2-dev php8.3-dev

У меня они уже стоят:

php5.6-dev is already the newest version (5.6.40-77+ubuntu22.04.1+deb.sury.org+1).
php7.0-dev is already the newest version (7.0.33-75+ubuntu22.04.1+deb.sury.org+1).
php7.1-dev is already the newest version (7.1.33-63+ubuntu22.04.1+deb.sury.org+1).
php7.2-dev is already the newest version (7.2.34-50+ubuntu22.04.1+deb.sury.org+1).
php7.3-dev is already the newest version (7.3.33-19+ubuntu22.04.1+deb.sury.org+1).
php7.4-dev is already the newest version (1:7.4.33-13+ubuntu22.04.1+deb.sury.org+1).
php8.0-dev is already the newest version (1:8.0.30-7+ubuntu22.04.1+deb.sury.org+1).
php8.1-dev is already the newest version (8.1.29-1+ubuntu22.04.1+deb.sury.org+1).
php8.2-dev is already the newest version (8.2.21-1+ubuntu22.04.1+deb.sury.org+1).
php8.3-dev is already the newest version (8.3.9-1+ubuntu22.04.1+deb.sury.org+1).

Вот и славно. Можно собирать. Будем делать это в сабдиректории, чтобы не пачкать репозиторий результатами сборки:

(our_repo) $ mkdir -p _build/src
(our_repo) $ cp -r rdkafka-* _build/src/
(our_repo) $ cp -r debian _build/src/
(our_repo) $ cp -r package*.xml _build/src/
(our_repo) $ cd _build/src && dpkg-buildpackage -b -rfakeroot -us -uc

Можно всё это хозяйство сразу оформить в примитивный Makefile

SHELL := /bin/sh
.DEFAULT_GOAL := build

build:
	mkdir -p _build/src
	rm -rf _build/src/*
	cp -r rdkafka-* _build/src/
	cp -r debian _build/src/
	cp -r package*.xml _build/src/
	cd _build/src && dpkg-buildpackage -b -rfakeroot -us -uc
	cd ../../

clean:
	test -d _build || rm -rf _build

Будет обильный вывод, основные моменты которого попробуем осмыслить:

  • сначала будет вывод мета-информации, мы её уже видели и даже сами писали. Далее скрипт зачистит всё и создаст под каждую версию PHP свою сабдиректорию сборки;

  • в сабдиректории сборки будет скопирован исходный код согласно правилам совместимости из XML-файлов, будет выполнен phpize, ./configure и make под каждую из версий PHP;

  • далее пойдут автотесты, важно чтобы они прошли без проблем. Если тесты валятся и есть уверенность, что это фича, а не бага (ивестно из issues в репозитории исходного кода), то можно отменить этот этап целиком с помощью переменной окружения DEB_BUILD_OPTIONS="nocheck", вставленной перед dpkg-buildpackage;

  • далее будет произведена контролируемая установка и полученные бинарники будут скомпонованы с etc-обвязкой PHP-пакетов для автоматической установки и линковки с ini-файлами;

  • выскачет невообразимое количество ворнингов, которые мы пропустим мимо внимания и выполнение закончится.

Все файлы будут лежать на в директории _build, примерно вот так:

(our_repo) $ ll _build
total 2168
drwxrwxr-x  3 vadim vadim   4096 Jul 28 00:59 ./
drwxr-xr-x  8 vadim vadim   4096 Jul 28 00:54 ../
-rw-r--r--  1 vadim vadim   1634 Jul 28 00:59 php-rdkafka-all-dev_6.0.3+4.1.2-1_amd64.deb
-rw-rw-r--  1 vadim vadim  19702 Jul 28 00:59 php-rdkafka_6.0.3+4.1.2-1_amd64.buildinfo
-rw-rw-r--  1 vadim vadim   8780 Jul 28 00:59 php-rdkafka_6.0.3+4.1.2-1_amd64.changes
-rw-r--r--  1 vadim vadim   1580 Jul 28 00:59 php-rdkafka_6.0.3+4.1.2-1_amd64.deb
-rw-r--r--  1 vadim vadim 148470 Jul 28 00:59 php5.6-rdkafka-dbgsym_6.0.3+4.1.2-1_amd64.ddeb
-rw-r--r--  1 vadim vadim  33582 Jul 28 00:59 php5.6-rdkafka_6.0.3+4.1.2-1_amd64.deb
-rw-r--r--  1 vadim vadim 171860 Jul 28 00:59 php7.0-rdkafka-dbgsym_6.0.3+4.1.2-1_amd64.ddeb
-rw-r--r--  1 vadim vadim  37500 Jul 28 00:59 php7.0-rdkafka_6.0.3+4.1.2-1_amd64.deb
-rw-r--r--  1 vadim vadim 175604 Jul 28 00:59 php7.1-rdkafka-dbgsym_6.0.3+4.1.2-1_amd64.ddeb
-rw-r--r--  1 vadim vadim  37358 Jul 28 00:59 php7.1-rdkafka_6.0.3+4.1.2-1_amd64.deb
-rw-r--r--  1 vadim vadim 171798 Jul 28 00:59 php7.2-rdkafka-dbgsym_6.0.3+4.1.2-1_amd64.ddeb
-rw-r--r--  1 vadim vadim  37250 Jul 28 00:59 php7.2-rdkafka_6.0.3+4.1.2-1_amd64.deb
-rw-r--r--  1 vadim vadim 174434 Jul 28 00:59 php7.3-rdkafka-dbgsym_6.0.3+4.1.2-1_amd64.ddeb
-rw-r--r--  1 vadim vadim  36804 Jul 28 00:59 php7.3-rdkafka_6.0.3+4.1.2-1_amd64.deb
-rw-r--r--  1 vadim vadim 176770 Jul 28 00:59 php7.4-rdkafka-dbgsym_6.0.3+4.1.2-1_amd64.ddeb
-rw-r--r--  1 vadim vadim  36780 Jul 28 00:59 php7.4-rdkafka_6.0.3+4.1.2-1_amd64.deb
-rw-r--r--  1 vadim vadim 179426 Jul 28 00:59 php8.0-rdkafka-dbgsym_6.0.3+4.1.2-1_amd64.ddeb
-rw-r--r--  1 vadim vadim  37282 Jul 28 00:59 php8.0-rdkafka_6.0.3+4.1.2-1_amd64.deb
-rw-r--r--  1 vadim vadim 182130 Jul 28 00:59 php8.1-rdkafka-dbgsym_6.0.3+4.1.2-1_amd64.ddeb
-rw-r--r--  1 vadim vadim  37518 Jul 28 00:59 php8.1-rdkafka_6.0.3+4.1.2-1_amd64.deb
-rw-r--r--  1 vadim vadim 186640 Jul 28 00:59 php8.2-rdkafka-dbgsym_6.0.3+4.1.2-1_amd64.ddeb
-rw-r--r--  1 vadim vadim  37508 Jul 28 00:59 php8.2-rdkafka_6.0.3+4.1.2-1_amd64.deb
-rw-r--r--  1 vadim vadim 186568 Jul 28 00:59 php8.3-rdkafka-dbgsym_6.0.3+4.1.2-1_amd64.ddeb
-rw-r--r--  1 vadim vadim  37688 Jul 28 00:59 php8.3-rdkafka_6.0.3+4.1.2-1_amd64.deb
drwxrwxr-x 15 vadim vadim   4096 Jul 28 00:59 src/

Собственно, вот пакеты, хоть репку свою поднимай, хоть ручками ставь — as you wish.

New level unlocked

Думали конец? А вот и нет. Потому что есть вероятность, что оно не соберётся и где-то вывалится. Скорее всего, после релиза двулетней давности, сборка в принципе поломалась для актуальных версий PHP, и нужно накладывать патчи, которые мэйнтэйнер не потрудился собрать в новый релиз. А может и писать патчи самостоятельно. Начнём с изучения репозитория исходного кода с помощью git log:

(original_repo) $ git log
commit 9cafbba8808963a373374524b0030f63d1b4c471 (HEAD -> 6.x, origin/HEAD, origin/6.x)
Author: Arnaud Le Blanc 
Date:   Sat Jun 1 13:35:04 2024 +0200

    Update README.md

commit bcd5004f461d1d3a5f879bb21280bdde6f6800c2
Author: cb-freddysart <115113665+cb-freddysart@users.noreply.github.com>
Date:   Fri Jan 5 14:11:25 2024 -0500

    feat: implement oauthbearer token refresh cb setter (#546)

commit b21a905832202e9051d0627036a96135f9c34da5
Author: Arnaud Le Blanc 
Date:   Mon Dec 4 15:01:53 2023 +0100

    Test against PHP 8.3 and librdkafka 1.9, 2.3 (#545)
    
    
    
    Co-authored-by: Dirk Adler 

commit 0aee7cf70a287d3e901787be144b7ff5a1441701
Author: Arnaud Le Blanc 
Date:   Tue Jul 26 20:44:00 2022 +0200

    Add private constructor on Metadata classes (#531)

commit 428a552c5219120ca456b462fd67cedd53b55325
Author: Arnaud Le Blanc 
Date:   Sat Jul 2 15:15:04 2022 +0200

    Update release notes

commit 413f7cce13d9456bd9bbc30402da86143574cc10
Author: Arnaud Le Blanc 
Date:   Sat Jul 2 15:10:53 2022 +0200

    Automation

commit 9fca57149805f0d07c0cad9a8fd0155da455f2ae (tag: 6.0.3)
Author: Arnaud Le Blanc 
Date:   Sat Jul 2 15:09:30 2022 +0200

    release/6.0.3 (#528)

Ага, вот эти ребята. Оказывается с релиза было принято 6 патчей, два из который довольно важны. Надо бы их забрать себе. В своё время мы чекаутили тэг релиза исходного кода в upstream-ветку своей репки. Можно плюнуть и отчекаутить там чужой HEAD, но это не true way. Будем накладывать патчи.

Как патчить чужой код для совместимости довольно неплохо расписано в том же хэндбуке для Debian Package Maintainers. По нему и пойдём. Нам надо собрать алиас-команду dquilt, которой будем накладывать живительные патчи. Добавляем в свой ~/.bashrc (или схожий файл оболочки) алиас примерно следующее (любители zsh традиционно переделывают под себя сами):

alias dquilt="quilt --quiltrc=${HOME}/.quiltrc-dpkg"
. /usr/share/bash-completion/completions/quilt
complete -F _quilt_completion -o filenames dquilt

Далее надо создать файл ~/.quiltrc-dpkg с содержимым:

d=. ; while [ ! -d $d/debian -a $(readlink -e $d) != / ]; do d=$d/..; done
if [ -d $d/debian ] && [ -z $QUILT_PATCHES ]; then
    # if in Debian packaging tree with unset $QUILT_PATCHES
    QUILT_PATCHES="debian/patches"
    QUILT_PATCH_OPTS="--reject-format=unified"
    QUILT_DIFF_ARGS="-p ab --no-timestamps --no-index --color=auto"
    QUILT_REFRESH_ARGS="-p ab --no-timestamps --no-index"
    QUILT_COLORS="diff_hdr=1;32:diff_add=1;34:diff_rem=1;31:diff_hunk=1;33:diff_ctx=35:diff_cctx=33"
    if ! [ -d $d/debian/patches ]; then mkdir $d/debian/patches; fi
fi

Далее в нашу репку стоит добавить .gitignore, чтобы нам не мешались промежуточные результаты патчинга или сборки:

.pc/
_build/

В директории .pc будет происходить весь патчинг, после чего созданные патчи уйдут в поддиректорию debian/patches, которой у нас пока нет, но там всё будет делать наш dquilt самостоятельно.

Почти всё готово. Для удобства склонируем репку исходного года где-то рядом, чтобы было удобно мержить/копировать. Сначала определим политику создания патчей. Я предпочитаю делать отдельный патч на каждый коммит в репозитории исходного кода, но ничего не мешает сделать общий патч на интервал коммитов. Я смотрю, что с коммита последнего релиза было 6 коммитов по логу. Вытаскиваю список затронутых каждым коммитом файлов:

(original_repo) $ git diff --name-only 9fca57149805f0d07c0cad9a8fd0155da455f2ae 413f7cce13d9456bd9bbc30402da86143574cc10
.github/workflows/release.yml
tools/extract-release-notes.php
tools/prepare-release.sh
(original_repo) $ git diff --name-only 413f7cce13d9456bd9bbc30402da86143574cc10 428a552c5219120ca456b462fd67cedd53b55325
package.xml
(original_repo) $ git diff --name-only 428a552c5219120ca456b462fd67cedd53b55325 0aee7cf70a287d3e901787be144b7ff5a1441701
metadata.c
metadata.stub.php
metadata_arginfo.h
metadata_broker.c
metadata_broker.stub.php
metadata_broker_arginfo.h
metadata_broker_legacy_arginfo.h
metadata_collection.c
metadata_collection.stub.php
metadata_collection_arginfo.h
metadata_collection_legacy_arginfo.h
metadata_legacy_arginfo.h
metadata_partition.c
metadata_partition.stub.php
metadata_partition_arginfo.h
metadata_partition_legacy_arginfo.h
metadata_topic.c
metadata_topic.stub.php
metadata_topic_arginfo.h
metadata_topic_legacy_arginfo.h
package.xml
php_rdkafka_priv.h
tests/metadata_001.phpt
tests/metadata_broker_001.phpt
tests/metadata_collection_001.phpt
tests/metadata_partition_001.phpt
tests/metadata_topic_001.phpt
tests/topic_partition_001.phpt
tests/topic_partition_002.phpt
topic_partition.c
(original_repo) $ git diff --name-only 0aee7cf70a287d3e901787be144b7ff5a1441701 b21a905832202e9051d0627036a96135f9c34da5
.github/workflows/test.yml
(original_repo) $ git diff --name-only b21a905832202e9051d0627036a96135f9c34da5 bcd5004f461d1d3a5f879bb21280bdde6f6800c2
.github/workflows/test/build-librdkafka.sh
conf.c
conf.h
conf.stub.php
conf_arginfo.h
conf_legacy_arginfo.h
config.m4
package.xml
tests/conf_callbacks.phpt
tests/conf_callbacks_rdkafka11.phpt
(original_repo) $ git diff --name-only bcd5004f461d1d3a5f879bb21280bdde6f6800c2 9cafbba8808963a373374524b0030f63d1b4c471
README.md

Тэг 9fca57149805f0d07c0cad9a8fd0155da455f2ae был релизным, со следующего за ним мы и пойдём.

В нашей репке открываем режим патчинга и создаём первый патч, следом указываем какие файлы будут меняться:

(our_repo) $ dquilt new v6_0_3_413f7cce13d9456bd9bbc30402da86143574cc10.patch
(our_repo) $ dquilt add rdkafka-6.0.3/.github/workflows/release.yml \
rdkafka-6.0.3/tools/extract-release-notes.php \
rdkafka-6.0.3/tools/prepare-release.sh 

Название файла патча абсолютно вариативно, но стоит накидать в него что-то осмысленное: либо текстовое саммари из трёх слов, либо хэш коммита.

Обратите внимание, что надо добавить в качестве префикса пути название директории с файлами. Теперь изменения в этих файлах будут отслеживаться. Чекаутимся в этот коммит в репке исходного кода и рсинкаем исходный код в директорию нужной версии в нашей репке:

(original_repo) $ git checkout 413f7cce13d9456bd9bbc30402da86143574cc10
(original_repo) $ rsync -av --delete ./ ../our_repo/rdkafka-6.0.3/

Если мы нигде не ошиблись, то контрольный git status покажет, что поменялось три файла. Делаем из них патч и зададим описание (для него отлично подойдёт вывод из git log):

(our_repo) $ dquilt refresh
(our_repo) $ dquilt header -e

После этого приедут два новых файла в директорию debian:

  • debian/patches/series — тут перечислены все патчи в порядке очерёдности применения;

  • debian/patches/v6_0_3_413f7cce13d9456bd9bbc30402da86143574cc10.patch — файл патча

Контролочка: если мы сравним листинг файла патча и вывод команды git diff между двумя коммитами, то мы увидим, что их объём эквивалентен. То есть да, патчи можно напрямую вытаскивать из гита и правильно укладывать. Но мне удобнее через dquilt.

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

...
(our_repo) $ dquilt add package.xml rdkafka-6.0.3/package.xml
...
копирование в rdkafka-6.0.3/ из внешнего источника
...
(our_repo) $ cp rdkafka-6.0.3/package.xml package.xml
...

Когда мы сделаем все патчи, у нас в debian/patches будет несколько новых файлов. Делаем новый релиз: добавляем новую секцию в debian/changelog и итерируем число версии после дефиса относительно предыдущего. Добавляем их в git:

(our_repo) $ git add debian/patches
(our_repo) $ git add debian/changelog
(our_repo) $ git commit -m "Release for 6.0.3+4.1.2-2"
(our_repo) $ git tag debian/6.0.3+4.1.2-2

Всё здорово, но в репке бардак. У нас из-за патчинга есть незакоммиченные правки в исходники релиза апстрима. Они нам не нужны, так как учтены в патчах, вычищаем их доступными средствами (git checkout, rm -r).

Неожиданности

Скорее всего их будет много, мы работаем с чужим кодом, зачастую заброшенным.

Например, есть особенность dh-php в том, что уже не получится в debian/rules добавить что-то, что нужно, так как оверрайд-таргеты перехватываются dh-php. В моём случае я хотел добавить в псевдотаргет override_dh_auto_test условие, чтобы тесты проходили, но при падении игнорировались (обычно это легко делается). Но мои правки усиленно игнорились, из чего я сделал вывод, что это именно из-за dh-php и был вынужден целиком отключить этап тестирования для пакета php-blitz прибегнув к более жёсткой конструкции DEB_BUILD_OPTIONS="nocheck".

Ещё в одном репозитории автор заигнорил у себя в .gitignore ряд файлов,  тем не менее, которые уже были в его репозитории. В итоге у меня они остались только в рабочей копии, а в мой репозиторий не попали из-за вложенного .gitignore, что поломало сборку.

Некоторые патчи из апстрима ломали сборку под предыдущими версиями PHP, тут либо сплитить релизы по версиям PHP, либо самостоятельно накладывать пачт, если очевидно как.

Что в итоге

Какие вольности мы можем позволить себе по результату?

  • Можно взять нужный нам deb-файл из числа собранных и установить его через dpkg на конечной системе.

  • Можно подписать релиз и загнать в собственный репозиторий.

  • Можно допилить всю обвязку до состояния полного соответствия гайдлайнам Debian и отправить в один из публичных репозиториев пакетов, например на Launchpad.

Мы же у себя просто собираем пакеты через CI и храним результат в артифактах Гитлаба, всё равно у нас это всё устанавливается ансиблом. Но правильным будет оформить в репку, дабы это не сложно.

© Habrahabr.ru