О билд-системах и собственном репозитории
Зачем оно вообще нужно? В процессе использования любого дистрибутива накапливается некий список улучшений, который ты воссоздаёшь каждый раз на новых машинах и виртуалках. Это могут быть:
- конфиги
- пакеты, которых нет в дистрибутиве
- пакеты, сборка которых не устраивает по разным причинам
- собственные проекты
Для меня актуальны последние три пункта. В четвёртом, дополнительно хочется ставить их как белый человек, а не методом слаквари.
Теория
Примерное движение пакетов и исходников показано на схеме. Обозначения:
- .deb -- бинарный пакет
- .dsc -- исходники ([d]ebian [s]our[c]es), комплект из 3х файлов, оригинальный тарбол, тарбол со служебными файлами из
debian/
и собственно .dsc -- манифест с описанием, чексуммами 2х предыдущих архивов, gpg-подписями и т.д. (итого: .dsc, .debian.tar.gz, .orig.tar.gz) - .changes -- то же самое что и .dsc, но включает в себя ещё и итоговый .deb-пакет
Программы:
- dput, dupload -- загрузка скомпиленнных пакетов на другой сервер по списку в .changes
- reprepro -- управление репозиторием (метаданные, их подпись, файлы в пуле, acl'ы)
- rebuildd, wanna-build -- софт для поддержания базы данных, в которой отслеживается состояние пакетов. Также управляет заданиями на сборку.
- pbuilder, sbuild -- непосредственно сборка пакетов (для билдсервера)
- debuild, dpkg-buildpackage -- ручная сборка пакетов (для dev-машины)
- cowbuilder, cowdancer -- вспомогательные утилиты, ускоряют создание сборочной среды
- gpg -- управление цифровой подписью
- build-essential -- виртуальный пакет с минимально необходимыми зависимостями для сборки
- devscripts -- дополнительные утилиты для сборки
- mini-dinstall -- автоматическое обновление репозитория при загрузке
Реализация
Выглядит это так:
- мы сидим на dev-машине, пишем код
- закончили с кодом - пробуем собрать то что получилось через debuild/<...>
- не получилось - см п. 1,
- делаем
dch -i
, пишем список изменений, при необходимости - заново собираем .dsc черезdebuild -S
- загружаем .dsc через dput/<...> на сервер, в
incoming/
репозитория - включаем .dsc в репозиторий через
reprepro includedsc <release> incoming/<package>_<ver>.dsc
- добавляем задание на сборку или руками через sql-клиент или через
rebuildd-job add
на билд-машине
Пункты 6-7 можно автоматизировать через mini-dinstall. TODO: Однако, тут "есть ньюансы".
Затем в дело вступает наша хитрая машинерия:
- rebuildd лезет в базу за новыми заданиями, и видит там что-то новое и лочит его
- затем, обычным apt-get source пытается получить всё необходимое для сборки
- вот здесь-то нас и поджидает первая свинья - если пакет был залит 5 минут назад, apt хост-системы про него ничего не знает
всё падает с ошибкой SOURCE_FAILED
после успешной выкачки source, rebuildd пытается построить chroot для сборки, поставив туда необходимые зависимости
- здесь нас поджидает вторая свинья - одинаковый набор репозиториев+ключей должен присутствовать как на хосте, так и в чруте
иначе всё падает на dpkg-satisfy-depends
запускается непосредственно сборка пакета, в результате получится .changes
- вот этот самый .changes при помощи dput/<...> переправляется на сервер с репозиторием, в упомянутый выше
incoming/
- третья свинья: в .changes входит deb и dsc. Внимание, вопрос: что будет, если 2 бид-сервера соберут пакет для разных архитектур и зальют его на один и тот же сервер? deb - разный, а вот dsc - один и тот же. Тут есть 2 варианта - или всё обломается на стадии загрузки: "не могу переписать уже залитый файл", или reprepro выдаст ошибку на этапе импорта: "у меня уже есть source с этим именем и версией, чего вы мне тут пытаетесь втулить?".
и да, кстати, насчёт "одинакового dsc/deb". Нифига он не одинаковый. В первом случае - меняется время перепакованных файлов, во втором - gcc-build-id. В результате, чексуммы разные -> кровь, кишки, распидорасило.
через
reprepro includedeb <release> incoming/<package>_<ver>.deb
в репу добавляется уже бинарный пакет
Недостатки есть и существенные. Первый и главный - использование средств хост-системы для разрешения зависимостей. Грабли пунктов 2-3 можно обойти, но этом используется изначально неправильный подход. Чруты должны быть полностью независимы от хоста на котором всё собирается.
А причина - в том, что многие забывают, что означает первая буква в pbuilder, который используется в подавляющем большинстве мануалов "как настроить свою репу". Я не говорю уже о том, что 90% из подобных мануалов - это инструкция вида
- соберите пакеты
- свалите всё в кучу
- натравите reprepro
- ...
- профит
Второй главный недостаток, неустраняемый впринципе -- это многовековые слои отложений легаси в формате пакетов и сборочных средствах. Проще надо быть, блжад. Пусть даже ценой частичной потери совместимости. rpm-based, арч, гента, даже бсд как-то обходятся максимум парой файлов. Здесь их минимум 6. МИНИМУМ.
Третий недостаток, теоретически решаем, но с очередной порцией костылей: используется свой собственный формат в пакетах. Похожий на yaml, но другой. Это ограничивает возможности работы с метаданными до официально поддерживаемых утилит и порождает десятки велосипедов для его разбора и ренерации.
Посмотрите, например, в исходники debmirror'а. Или на мой велосипед в debian-repo-listing/, написать который заново оказалось быстрее по времени, чем продраться через лапшу в debmirror'е, который я честно смотрел перед этим.
Ну и ещё на закуску: для создания chroot'ов используется старый, древний и проверенный debootstrap. У меня была мысль понастроить lxc-контейнеров, заюзать снапшоты и исключить раз и навсегда debootstrap/cowdancer из процесса сборки. Зачем? Управлять проще, поддерживать проще, и заодно можно ресурсы резать. Потом посмотрел, сколько придётся перепахать инструментов (а ведь потом ещё это поддерживать) - и не стал, время можно потратить и с большей пользой.
В довершение скажу, что я не один с такими мыслями: вот здесь один из DD высказывает похожую мыслю в ключе "похороните уже это говно мамонта".