Разговор о сборке собственной системы для устройства рано или поздно выходит на второй уровень: если Buildroot собирает образ из исходников напрямую, Yocto Project поднимается выше и предлагает построить собственный дистрибутив с пакетным управлением, слоями рецептов и производственным конвейером. Разница в подходе определяет, для какого проекта что выбирать, и в статье разобраны и технология сборки, и команда, которой придётся жить с выбором несколько лет.

Что продают под именем Yocto и почему это не дистрибутив

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

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

Архитектура такого решения опирается на три элемента. Движок bitbake читает рецепты, описывающие загрузку исходников, их сборку, упаковку и зависимости. Конфигурация целевой машины отделяется от состава дистрибутива: MACHINE отвечает за железо, DISTRO за набор правил сборки, и обе настройки меняются независимо. Третий элемент это слои: чужие и свои каталоги с рецептами и классами, которые подключаются буквально в одной строке файла слоёв. Модульность и есть то, за что крупные вендоры оборудования любят Yocto: каждый может издавать свой уровень поддержки железа и не заботиться о дереве целиком.

Первая сборка от клона до образа

Первый контакт приятно неожиданен по объёму подготовки: клонируется проект poky, из его каталога скрипт инициализирует окружение сборки, после чего в каталоге сборки появляются два конфигурационных файла, отвечающих почти за всё. В local.conf указывается целевая машина и дистрибутив, в bblayers.conf подключаются слои. Минимальный старт выглядит так:

git clone -b scarthgap git://git.yoctoproject.org/poky
source poky/oe-init-build-env build
bitbake core-image-minimal

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

Для железа из реального мира шаги такие же: берётся слой вендора платы, задаётся его машина в local.conf, и сборка повторяется. Слой вендора содержит описание загрузчика, ядра и дерева устройств конкретной платы, поэтому сама процедура смены платформы редко выходит за пределы конфигурационных файлов. Меняются только значения переменных, а рецепты и классы остаются общими.

Слои, рецепты и собственные наработки команды

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

SUMMARY = "demo service"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=xxxxxxxxxxxx"
SRC_URI = "git://git.example.com/demo.git;branch=main"
SRCREV = "${AUTOREV}"
S = "${WORKDIR}/git"

inherit cmake

Рецепт в примере задаёт три вещи: метаданные пакета, источник исходников и тип системы сборки, которая соберёт приложение. Класс cmake берётся из стандартных слоёв и знает, как собирать проект, а вызов bitbake demo приводит пакет в целевую систему уже собранным. Именно такой уровень абстракции, когда логика сборки выносится в классы и рецепты, отличает Yocto от ручных сценариев: переход на другой компилятор или целевую платформу выполняется правками конфигурации, а не рецептов.

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

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

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

Производственная сборка, релизный конвейер и обновления изделий

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

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

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

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

Кэш состояний и скорость повторных сборок

Самая болезненная тема Yocto это время сборки, и здесь спасает sstate: каждое задание рецепта сохраняет свой результат в общем хранилище состояний, и повторная сборка с той же конфигурацией почти мгновенна. Хранилище располагается локально или на сетевом ресурсе по выбору, и команда, у которой несколько машин строят один образ, объединяет кэши в одно общее пространство. Разница по времени впечатляет: первая сборка занимает часы, а сборка после незначительных правок нередко укладывается в минуты.

Сюда же входит каталог загрузок, куда раз и навсегда складируются архивы исходников. При работе без сети или в закрытом контуре этот каталог переносится заранее, и сборка не зависит от доступности внешних серверов. Самый рабочий приём эксплуатации это утверждённое зеркало источников: конфигурация указывает локальное зеркало в качестве главного источника, движок первым делом проверяет его, и обрыв связи с внешним хранилищем не останавливает работу. Если хочется собрать образ ни от чего не зависящий, к этому добавляют правило BB_NO_NETWORK: после первичной загрузки исходников сеть в сборке отключается полностью, и каждое обращение рецепта за скачиванием становится ошибкой с явным указанием пакета. Такая дисциплина ловит скрытые обращения за данными и гарантирует, что образ воспроизводим без интернета, что особенно ценно для встраиваемых продуктов, где жизненный цикл больше, чем доступность чужих серверов.

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

Yocto против Buildroot для команд и изделий

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

  1. для одиночной платы с устоявшимся набором служб Buildroot дешевле и быстрее: инструментарий из одного make с меню выучивается за неделю;
  2. для проекта с несколькими изделиями, где каждому нужно своё ядро и дерево устройств, лучше Yocto, потому что он отделяет машину от дистрибутива и позволяет собирать их в разных комбинациях;
  3. для изделия в продаже с требованиями лицензионной проверки и обновления пакетов на боевом устройстве пакетные сборки Yocto с подписанными выпусками снимают ручную работу;
  4. для команды до двух инженеров Buildroot выигрывает простотой эксплуатации, а для контрактного производства и вендорской модели Yocto предоставляет слои и управляемый конвейер;
  5. с запасом на полгода обучения команды Yocto оправдан, а при необходимости видеть результат завтра Buildroot покрывает задачу скромнее, но быстрее.

Решение не замораживается навсегда: первые образы часто делают на Buildroot для прототипа, а переход на Yocto выполняют, когда проект становится изделием и требует управления жизненным циклом. Главное чтобы переход был описан в документации проекта с самого старта, иначе им станут заниматься тогда, когда прототип превратился в продукт и сроки стоят дороже всего.

Поддержка изделия на Yocto в течение лет

Для долгосрочных продуктов Yocto имеет понятный цикл поддержки: за релизом следуют стабилизационные выпуски, а весенние выпуски выделенных серий получают статус долгосрочной поддержки на сроки, измеряемые годами. Для изделия со сроком жизни в несколько лет это политический фактор: выбор ветки это выбор даты окончания бесплатной поддержки, и планирование сервисного обслуживания ведётся от неё.

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