… или использование TeamCity для сборки *.deb-пакетов и не только.
Написать статью меня побудило знакомство с модулем tcDebRepository. Я наивно полагал, что "вот сейчас я его подключу, и всё волшебным образом заработает". Как водится, не заработало, и в конце концов был накоплен некий опыт, который захотелось систематизировать.
Статья ни в коей мере не является введением в основы TeamCity и предполагает, что читатель уже знаком и собственно с TeamCity, и с инфраструктурой Debian GNU/Linux. Если вы уже представляете, что такое continuous integration, но ещё ни разу не держали в руках TeamCity — вам, наверное, сюда. О сборке пакетов в Debian можно почитать в Debian New Maintainers' Guide.
Для игр (на случай, если кто-то захочет воспроизвести результаты) использовался сервер TeamCity 10 и 3 агента под управлением Debian 8.0 (Jessie). 3 агента — это лимит в случае TeamCity Professional. Всё ниженаписанное, думаю, без проблем переносится на любой другой дистрибутив на основе Debian GNU/Linux, напр., Astra Linux.
Планы
Достаточно произвольным образом я выбрал для экспериментов 4 пакета:
С учётом ограничения лицензии типа Professional на количество конфигураций сборки можно было "набрать" до 20 пакетов.
Подготовка
TeamCity загружается с официального сайта. Кроме собственно TeamCity, на каждую из агентских машин нам потребуется установить пакет build-essential, равно как и необходимые для сборки зависимости для всех четырёх пакетов (из категорий
build-depends и
build-depends-indep). Это позволит минимизировать (но совсем не обязательно устранить) проблемы с зависимостями при сборке.
Виды пакетов в Debian GNU/Linux
Пакеты, помимо прочих особенностей, делятся на "родные" (native) и внешние (non-native) (подробнее). "Родные" пакеты (autotools-dev, debhelper, dpkg) обычно разрабатываются в рамках проекта Debian, и исходный код уже содержит необходимую для сборки метаинформацию (каталог debian/ в корне дерева исходного кода).
Отличие внешних пакетов (bash) в том, что исходный код никоим образом не завязан на Debian, и инженерам сопровождения (в русскоязычной документации это называется "разработчик Debian", в англоязычной — просто "maintainer") приходится поддерживать параллельное дерево исходного кода с метаинформацией и патчами (это то самое содержимое каталога debian/).
Общие настройки
Бинарные пакеты, которые мы будем собирать — это, в терминологии TeamCity, "артефакты". Соответственно, нужно указать, что мы ожидаем иметь в сухом остатке по окончании очередной сборки, указав artifact paths:

Для "родных" пакетов артефакты pkgname.orig.tar.{gz,bz2,xz} и pkgname.debian.tar.{gz,bz2,xz} не создаются.
Подключение исходного кода к TeamCity
Чаще всего как раз с этим шагом нет ничего сложного: просто идём в настройки конфигурации сборки (build configuration) и добавляем новый корень системы контроля версий (VCS root). Для "родных" пакетов эту операцию нужно выполнить однократно, для внешних — как правило, дважды (но возможны исключения, когда и разработчики (вне проекта Debian), и инженеры сопровождения используют одну и ту же DVCS (Git, Bazaar), и изменения в коде постоянно "кочуют" из одного репозитория в другой, в то же время не вызывая merge-конфликтов для метаинформации и патчей).
Единственная особенность состоит в том, что артефакты в нашем случае будут собираться вне дерева исходного кода (на один каталог выше), так что нам нужно настроить checkout rules таким образом, чтобы, скажем, исходный код пакета dpkg выгружался не в текущий рабочий каталог, а в одноимённый пакету подкаталог, т. е. dpkg/. Это достигается добавлением в checkout rules одной строчки:
+:.=>dpkgи в конечном счёте выглядит так:

Теперь можно добавить VCS-триггер и к настройке контроля версий уже не возвращаться:

Интеграция с Bazaar
"Но ведь для сборки bash требуется интеграция с Bazaar, а штатная поставка TeamCity не поддерживает эту систему!" — скажет внимательный читатель, и будет прав. Кроме того, TeamCity, увы, не позволит нам добавить и Git URL вида bzr::http://bazaar.launchpad.net/~doko/+junk/pkg-bash-debian — у JGit слишком много ограничений.
Существует внешний модуль для поддержки Bazaar, но у него есть по меньшей мере два серьёзных недостатка:
- checkout на стороне агента не поддерживается, и
- штатной поставки Bazaar недостаточно, т. к. модуль опирается на функциональность bzr xmlls и bzr xmllog, что выясняется только в процессе сборки:

Поскольку сервер TeamCity у меня работал на Windows, я отказался от весёлого приключения в виде установки Bazaar на стороне сервера, а вместо этого в случае пакета bash просто добавил ещё один шаг сборки (Bazaar-интеграцию для бедных), используя Command Line Runner и следующий сценарий оболочки:
#!/bin/bash
#
# vim:ft=sh:
#
export LANG=C
export LC_ALL=C
set -e
rm -rf bash/debian
bzr branch http://bazaar.launchpad.net/~doko/+junk/pkg-bash-debian bash/debian
major_minor=$(head -n1 bash/debian/changelog | awk '{print $2}' | tr -d '[()]' | cut -d- -f1)
echo "Package version from debian/changelog: ${major_minor}"
tar_archive=bash_${major_minor}.orig.tar
rm -f ${tar_archive} ${tar_archive}.bz2
# +:.=>bash checkout rule should be set for the main VCS root in TeamCity
tar cf ${tar_archive} bash
tar --delete -f ${tar_archive} bash/debian
# Required by dpkg-buildpackage
bzip2 -9 ${tar_archive}Такой подход не позволит нам "видеть" изменения в одном дереве исходного кода (из двух) и автоматически запускать сборку при их (изменений) появлении, но для первого опыта вполне достаточен.
N.B.! Поскольку Command Line Runner не умеет подсвечивать синтаксис кода сценария, для пользователей браузеров Mozilla Firefox и SeaMonkey я бы рекомендовал расширение It's All Text!, позволяющее редактировать содержимое текстовых полей во внешнем редакторе. Можно подключить Vim или Emacs и насладиться подсветкой синтаксиса, автодополнением, шахматами и поэтессами.
Сборка: настройка
Для сборки нам достаточно использовать уже знакомый нам Command Line Runner, вызывающий dpkg-buildpackage. Ключи -uc и -us означают, что мы не хотим создавать цифровых подписей для наших пакетов. Если всё-таки хотим — придётся загрузить соответствующую пару GnuPG-ключей на каждый из агентов.
Также обратите внимание, что dpkg-buildpackage должен исполняться не в текущем рабочем каталоге, а в одноимённом пакету подкаталоге (куда будет выгружено дерево исходного кода). Если настройка контроля версий выполнена, поле "Working directory" можно заполнить в один щелчок мыши, не вводя имя каталога вручную:

Сборка: разрешение проблем
Качество кода
Как ни странно, но качество кода (или, точнее, стиль разработки) может являться серьёзной проблемой на пути внедрения continuous integration. Опытным путём выяснилось, что, в случае bash, версии в двух деревьях кода рассинхронизированы: последние коммиты в основном дереве соответствуют версии 4.4, хотя файл debian/changelog уже без малого два года назад остановился на версии 4.3, и код одной версии с метаинформацией другой версии вместе не собираются. Хорошо, значит, мне нужна ветка bash-4.3 в основном дереве.

- Вот идёт ветка
bash-4.3-testingс тэгамиbash-4.3-rc2и (ниже, не видно)bash-4.3-rc1— и потом она внезапно обрывается. Если верить истории версий, то релизbash4.3 так и не состоялся. - В то же время, спустя несколько дней на ветке
masterпоявляется коммит с тэгомbash-4.3, которому не предшествует ни одна операция типа merge или cherry-pick. - Беглый взгляд на историю и содержание коммитов приводит к ощущению, что вся разработка ведётся в локальной ветке одного человека, а
git pushна savannah.gnu.org происходит через равные промежутки времени, причём черезgit merge --squash -s ours(у каждого коммита невероятно длинный и трудно читаемыйdiff). - Коммиты "
Bash-4.3 patch XY" (всего 46 патчей для версии 4.3) кладутся вmaster(наbash-4.3-testingих нет), а через 3 недели на веткеmasterпоявляется меткаbash-4.4-beta2. Это означает, что последнее стабильное состояние "bash4.3 плюс патчи" взять, увы, неоткуда. Слава богу, TeamCity позволяет выполнять сборку по тэгу (флаг "Enable to use tags in the branch specification"), что и было в конце концов сделано.
Резюме:
- То, что я увидел, не похоже ни на традиционную схему создания веток, ни на git-flow.
- Да, я в курсе дела, что сборка по тэгу сводит к нулю весь смысл continuous integration, но общаться с разработчиком
bashмы будем в другой раз.
Зависимости
При запуске первой же сборки мы увидим, что dpkg-buildpackage завершил работу с кодом возврата 3:

В результате просмотра протокола сборки выяснится, что какие-то зависимости всё-таки отсутствуют:

Но вот мы установили всё, что требовалось (на всех агентах), а dpkg-buildpackage завершается с тем же кодом. В чём же дело? Здесь есть несколько нюансов.
- Скорее всего, вы будете собирать ПО из Debian unstable или Debain experimental. В таком случае, для удовлетворения необходимых для сборки зависимостей агенты TeamCity тоже должны работать под управлением Debian unstable или Debian experimental (иначе
dpkg-buildpackageбудет "ругаться", что ваши стабильные версии зависимостей "устарели"). Для подавления ошибки иногда достаточно добавить ключ -d:
dpkg-buildpackage -uc -us -d - Частным случаем устаревших зависимостей является сценарий
configure, созданный более новой версией GNU Autotools, чем в настоящее время установлены в системе.dpkg-buildpackageне в состоянии диагностировать такую ситуацию — вместо этого в протоколе сборки мы наблюдаем загадочные сообщения об отсутствующих макросахm4. Решением является повторное создание сценарияconfigureс помощью текущей версии GNU Autotools. Просто добавьте первым шагом сборки следующую команду:
autoreconf -i
"Сломанные" unit-тесты
Если мы всё-таки хотим себя обмануть и таки собрать наш пакет, достаточно будет запустить dpkg-buildpackage в изменённом окружении:
DEB_BUILD_OPTIONS=nocheck dpkg-buildpackage -uc -usО других способах самообмана можно почитать здесь.
Финишная прямая
После того, как все круги ада пройдены, мы увидим, что очередная сборка завершилась созданием артефактов:

Если вы не хотите, чтобы с каждым инкрементом версии пакета количество артефактов единичной сборки увеличивалось (в конечном счёте предлагая широкий ассортимент из dpkg_1.18.16_i386.deb, dpkg_1.18.17_i386.deb и dpkg_1.18.18_i386.deb), содержимое рабочего каталога стоит выборочно очищать перед каждой сборкой. Это можно было бы выполнить вручную, непосредственно перед dpkg-buildpackage вызвав rm -rf c пресловутыми artifact paths в качестве аргументов, но есть способ лучше — штатный модуль TeamCity с ласковым названием "Швабра". Вот так выглядят его настройки (ключевое здесь — "Before next build start"):

А вот таким образом будет выглядеть соответствующий фрагмент протокола сборки, если модуль Swabra правильно настроен:

Теперь самое время настроить наш Debian-репозиторий. Это достигается добавлением артефакт-фильтров в настройках модуля tcDebRepository. Некоторое неудобство состоит в том, что для каждой конфигурации (читай: программного пакета) приходится добавлять новый фильтр, фактически идентичный предыдущему:

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


При добавлении репозитория в /etc/apt/sources.list можно наблюдать все те же пакеты уже со стороны клиента:


N.B.! Если вы собираете под несколько архитектур (i386, x32, amd64, arm), стоит либо иметь несколько отдельных конфигураций сборки, соответствующих одному пакету и различающихся требованиями к агентам, либо, в дополнение к VCS Trigger, добавить Schedule Trigger с флагом "Trigger build on all enabled and compatible agents":

Через какое-то время вы увидите, что проект dpkg активно развивается, а вот остальные участники, похоже, курят бамбук.

Комментарии (12)

rudenkovk
19.12.2016 11:48+1Беда таких систем сборки конечных пакетов в следующем (мой опыт): нет пересборки по древу зависимостей. (сделать можно, но дикое усложнение и деньги, в случае TC/Bamboo). Сие ведет, в частности, к тому, что тяжело контролировать версии (когда их нужно много). Если пакетов мало, то с OBS нет никакого смысла заморачиваться. В противном случае, проще отправлять таском из CI в OBS.

Singaporian
19.12.2016 14:23+1Статья обалденная. Но в ней, кроме сисек, не раскрыт еще один важный момент: debian/changelog. Как он меняется? Что происходит в случае Team Upload (dch --team)? Что в случае QA Upload(dch --qa)? NMU? Как автоматически отображать релиз в ченджлоге (dch -r)?

unix_junkie
19.12.2016 20:29Спасибо за оценку.
На вопрос, думаю, смогу ответить спустя какое-то время, когда наберётся достаточная статистика по
dpkg(пока что не было ни одного NMU или QA Upload).
Пока что вижу, что
- не всякий коммит сопровождается записью в
debian/changelog, что странно - имена авторов изменения в VCS и в
debian/changelogизредка различаются, что тоже странно
Думаю, надо внимательно курить специфичную для Debian литературу (Policy Guide и т. д.).
Могу порекомендовать Вам поднять сборку
dpkg, используя статью в кач-ве инструкции, и убедиться во всём воочию.
Singaporian
19.12.2016 21:55+1А вот в этом то и проблема. Запись в ченджлоге должна быть не с каждым коммитом (может там всего лишь identetion поправили), а одна запись на одну story или один bugfix. То есть законченная задача, связанная не столько с системой контроля версий, сколько с issue-трэкером — значит он тоже должен быть в связке.

unix_junkie
19.12.2016 23:06Похоже, пора переводить статью на английский и писать в
debian-devel@lists.d.oс просьбой прокомментировать.

unix_junkie
21.12.2016 16:56Андрей Рахматуллин из проекта Debian комментирует:
If you use dpkg-buildpackage then the specifics of the last changelog entry don't matter.

Singaporian
21.12.2016 17:10+1Doesn't matter потому что он говорит про «entry». Но версию то менять надо. На ее основе будет собран пакет с этой версией и отправлен в репозиторий. Если версию не поменять автоматически, то он перетрет старый пакет в репо. В результате все машины, настроенные на этот репозиторий выкинут ошибку о неконсистентной чексумме. А даже если им всем сделать apt-get clean/update, то все равно апдейтиться на эту версию они не станут, так как у них уже эта версия стоит (хотя по факту она предыдущая).

unix_junkie
21.12.2016 17:36Я понял.
Т. е. вопрос скорее в контексте не continuous integration, а continuous delivery. Тогда это не вопрос ко мне или к разработчикам Debian, а задача для автора tcDebRepository, причём имеющая смысл исключительно для сборки пакетов из основной поставки Debian GNU/Linux.
Если я собираю свой пакет на своём сервере, я не вижу проблем в том, чтобы просто сделать инкремент версии в случае, эквивалентном, скажем, binNMU.
Или я что-то упустил?

Singaporian
21.12.2016 17:56+1Да, это откровенно continuous delivery (даже если это деливери в QA). Но .deb — это вообще continuous delivery всегда по определению :)
По поводу своего пакета — тогда врядли актуально. Я тоже собираю свои пакетики для инфраструктуры. Changelog меняю руками. Вполне хватает рук, автоматика не нужна. Все вышесказанное актуально для пакетов основного приложения, которое тестируют, деплоят и обновляют. Где важен конвейер.
- не всякий коммит сопровождается записью в
gtbear
1. Можете объяснить в чем смысл самостоятельно собирать пакеты типа bash?
2. Смотрели ли вы в сторону http://openbuildservice.org/ который как раз таки заточен под сборку пакетов и интеграцию с репозиториями?
unix_junkie
bashприведён исключительно для примера. Смысл собирать в том, чтоxfs, X11 Font Server) ранее присутствовали в дистрибутиве, а затем были исключены из него, но по-прежнему представляют интерес для пользователей.