lift_indicator_suite/just/ci_workflow.md

14 KiB
Raw Permalink Blame History

Текущее состояние CI/CD workflow для tft_manufacture_test

Обзор

В репозитории настроен рабочий GitHub Actions pipeline, который успешно запускается на событиях push, pull_request и при ручном запуске через workflow_dispatch. Пайплайн специально привязан к реальному окружению разработки проекта: сборка и тесты выполняются внутри того же devcontainer-образа, который описан в .devcontainer/Dockerfile, а не в вручную собранной среде на ubuntu-latest.

Такой подход уже устранил основные проблемы, которые проявились при первичном поднятии CI: отсутствие ARM toolchain, отсутствие ninja, установка неправильного just и несовместимость прав доступа при работе с bind-mounted workspace внутри Docker.

Текущая архитектура

Workflow разделён на две jobы: build и test.[cite:156] Такое разделение делает пайплайн проще для сопровождения, позволяет отдельно анализировать результаты стадии сборки и создаёт хороший фундамент для следующих этапов — lint, coverage, release packaging и аппаратных проверок.

Среда выполнения строится из Dockerfile devcontainer-а проекта, в котором уже определены ARM GCC toolchain в /opt/arm-toolchain, обновлённый PATH, а также установлены cmake, ninja-build, clang-17, uv и just.[cite:125] Поскольку все ключевые зависимости уже зафиксированы именно там, использование этого же образа в CI делает поведение раннера максимально близким к локальной разработке.[cite:125]

Как работает workflow

Workflow реагирует на три типа событий: push, pull_request и workflow_dispatch. Это даёт удобный баланс между автоматической проверкой обычных коммитов и возможностью вручную перезапускать pipeline для отладки инфраструктурных или нестабильных падений без обязательного нового изменения в коде. Внутри job используется Docker Buildx и docker/build-push-action, а кэширование слоёв контейнера подключено через backend GitHub Actions cache с помощью cache-from: type=gha и cache-to: type=gha. За счёт этого повторные прогоны не пересобирают devcontainer с нуля, а переиспользуют уже собранные Docker-слои, что заметно ускоряет пайплайн после первого успешного заполнения кэша.

Что делает job build

Job build выполняет checkout репозитория, инициализирует Buildx, собирает devcontainer image с поддержкой кэша, проверяет версии инструментов внутри контейнера, синхронизирует Python tooling в tools/host через uv sync, а затем запускает just ci::build внутри контейнера. После успешной сборки workflow выгружает директорию build/ как GitHub artifact, чтобы результаты можно было сохранить и использовать на следующих стадиях.

Важная техническая деталь — команды внутри контейнера запускаются с --user root.Это требуется из-за того, что GITHUB_WORKSPACE подключается в контейнер как bind mount, а в GitHub Actions non-root пользователь внутри Docker часто не получает права на запись в такую директорию; ранее это как раз ломало создание .venv во время uv sync.

Что делает job test

Job test зависит от build, скачивает artifact с директорией build/, заново поднимает тот же devcontainer image с использованием cached layers, синхронизирует tools/host и запускает just ci::test внутри контейнера. На практике это означает, что host unit-тесты работают в той же программной среде, что и стадия сборки, но при этом выделены в отдельный CI-этап.

Так как GitHub-hosted runnerы эфемерны, сам Docker image не передаётся напрямую между jobами.[cite:143] Поэтому обмен между build и test организован двумя способами: ускорение повторной сборки образа идёт через Docker layer cache, а результаты проекта передаются через GitHub artifacts.

Почему эта схема хорошо подходит проекту

Этот репозиторий нельзя считать обычным desktop C-проектом: он завязан на фиксированное расположение embedded toolchain и на специально подготовленный devcontainer.[cite:125] Ранние попытки выполнять pipeline прямо на runnerе падали, потому что проект ожидал наличие /opt/arm-toolchain, установленный Ninja и современный бинарник just, который понимает атрибуты вроде [doc(...)].

Перенос CI внутрь devcontainer image устраняет этот класс расхождений и делает Dockerfile единым источником истины для окружения, версий и путей.[cite:125] Это упрощает дальнейшее сопровождение: при изменении инструментария достаточно обновить Dockerfile, и эти же изменения автоматически начнут действовать как локально, так и в CI.[cite:125]

Чего workflow пока не делает

Текущий pipeline пока не включает обязательную стадию lint и статический анализ, потому что в just/ci.just для lint пока ещё оставлена заглушка, а не полноценный вызов clang-format и clang-tidy. Он также пока не формирует release/HAB artifacts в CI, хотя в репозитории уже есть соответствующие рецепты just ci::release и связанные сборочные шаги.

Также pipeline пока не запускает HIL-сценарии. Это ожидаемо и правильно для текущего этапа: hardware-in-the-loop проверки требуют физического оборудования и в дальнейшем должны выполняться отдельно на self-hosted runner рядом с bench-стендом, а не на GitHub-hosted машинах.

Сильные стороны текущего решения

У текущей реализации уже есть несколько сильных сторон:

  • Она воспроизводима, потому что сборка и тесты выполняются в том же образе, что и локальная разработка.
  • Она ускоряется на повторных прогонах за счёт Docker layer caching через GitHub Actions cache backend.
  • Она модульна, потому что build и test вынесены в отдельные jobы, связанные артефактами.
  • Она удобна для отладки, потому что build outputs сохраняются как artifacts, а workflow можно запускать вручную через workflow_dispatch.
  • Она хорошо вписана в структуру проекта, потому что использует уже существующие just-точки входа, а не дублирует build-логику в YAML.

Текущие ограничения

Главное ограничение сейчас состоит в том, что pipeline проверяет собираемость и host unit-тесты, но ещё не закрывает style gate, static analysis, coverage и release packaging. Второе ограничение — Docker image пересобирается в каждой job, поэтому даже при наличии кэша остаётся неизбежный накладной расход по времени по сравнению с вариантом, где используется заранее опубликованный образ из registry.[cite:143]

Есть и архитектурное ограничение GitHub-hosted runnerов для аппаратной части.[cite:127] Прошивка через USB, pyOCD-сценарии и управление стендом должны в будущем быть вынесены в отдельную hardware lane на self-hosted runner.

Рекомендуемые следующие шаги

Шаг 1 — добавить lint job

Самое логичное следующее улучшение — реализовать полноценную стадию lint в just/ci.just и подключить отдельную job в workflow. В эту стадию стоит включить clang-format --dry-run --Werror, clang-tidy и необходимые исключения для generated-кода или vendor-зависимостей, чтобы избежать лишнего шума в CI.

Шаг 2 — добавить coverage

После стабилизации lint полезно подключить экспорт coverage для host-тестов. В ci.just уже существует закрытый рецепт _coverage, и его можно развить до генерации XML-отчёта, выгрузки артефактов и последующей интеграции с внешним coverage-сервисом, если это будет нужно.

Шаг 3 — выделить release workflow

Release packaging лучше оформлять отдельным workflow или отдельной gated job, запускаемой только по тегам, на main или вручную через workflow_dispatch. Это позволит не замедлять обычный PR-цикл, но при этом использовать just ci::release и публикацию HAB-артефактов тогда, когда это действительно нужно.

Шаг 4 — публиковать devcontainer image в GHCR

Следующий сильный шаг по оптимизации — публиковать devcontainer image в GHCR и затем запускать CI уже на базе заранее собранного образа, а не пересобирать его в каждой job.Это ещё сильнее сократит время старта pipeline и сделает масштабирование на lint, coverage и release заметно проще.

Шаг 5 — добавить self-hosted HIL lane

Финальное крупное направление развития — выделенный аппаратный workflow на self-hosted runner с доступом к MCU-Link, target board и M5StampPLC. Такую lane лучше запускать вручную, по расписанию или по label-триггеру, а не делать обязательной для каждого PR, поскольку аппаратные проверки медленнее, менее стабильны и по природе отличаются от быстрых software regression checks.

Целевое состояние

Зрелая версия этого CI/CD контура, вероятно, будет состоять из четырёх независимых линий: быстрый PR-pipeline (build, test, lint), optional coverage reporting, отдельный release workflow и отдельный self-hosted HIL pipeline.[cite:156] Такая структура сохранит короткий feedback loop для обычной разработки и одновременно покроет полный жизненный цикл embedded-проекта: от изменений в исходниках до production artifacts и аппаратной валидации на стенде.