Переход на модульную архитектуру just скриптов (см. docs/DEV_ARCH.md)
This commit is contained in:
Dmitry Akimov 2026-03-12 13:15:48 +03:00
parent e8a9826944
commit 6930479fdb
12 changed files with 673 additions and 566 deletions

0
.env.example Normal file
View file

2
.gitignore vendored
View file

@ -67,4 +67,6 @@ compile_commands.json
.venv/
__pycache__/
*.pyc
.env

28
.vscode/tasks.json vendored
View file

@ -13,9 +13,7 @@
"type": "shell",
"command": "just",
"args": [
"--justfile",
"scripts/build.just",
"build-${input:project}-${input:buildType}"
"build::build-${input:project}-${input:buildType}"
],
"options": {
"cwd": "${workspaceFolder}",
@ -41,9 +39,7 @@
"type": "shell",
"command": "just",
"args": [
"--justfile",
"scripts/build.just",
"test-host"
"build::test-host"
],
"options": {
"cwd": "${workspaceFolder}",
@ -61,9 +57,7 @@
"type": "shell",
"command": "just",
"args": [
"--justfile",
"scripts/build.just",
"test-host-release"
"build::test-host-release"
],
"options": {
"cwd": "${workspaceFolder}"
@ -79,9 +73,7 @@
"type": "shell",
"command": "just",
"args": [
"--justfile",
"scripts/build.just",
"hab-${input:habProject}-${input:buildType}"
"build::hab-${input:habProject}-${input:buildType}"
],
"options": {
"cwd": "${workspaceFolder}",
@ -99,9 +91,7 @@
"type": "shell",
"command": "just",
"args": [
"--justfile",
"scripts/build.just",
"hab-all-debug"
"build::hab-all-debug"
],
"options": {
"cwd": "${workspaceFolder}"
@ -114,9 +104,7 @@
"type": "shell",
"command": "just",
"args": [
"--justfile",
"scripts/build.just",
"hab-all-release"
"build::hab-all-release"
],
"options": {
"cwd": "${workspaceFolder}"
@ -132,9 +120,7 @@
"type": "shell",
"command": "just",
"args": [
"--justfile",
"scripts/build.just",
"clean"
"build::clean"
],
"options": {
"cwd": "${workspaceFolder}",

View file

@ -1,22 +0,0 @@
# Прошивка платы со стороны хоста
```bash
# Прошить во Flash
just flash firmware_test debug # разработка — итерации с отладчиком
just flash firmware_test release # проверить как будет на сервере
just flash bootloader debug
just flash bootloader release
just flash app debug
just flash app release
# TODO:Загрузить в RAM (без записи во Flash, плата стартует сразу)
just flash-ram firmware_test # default: debug
just flash-ram firmware_test debug
just flash-ram app release
# TODO:Псевдонимы
just flash-test-debug # = just flash firmware_test debug
just flash-test-release # = just flash firmware_test release
just flash-production # bootloader release + app release
```

370
Justfile
View file

@ -1,344 +1,64 @@
# =============================================================================
# Justfile — корень репозитория
# Выполняется на ХОСТ-МАШИНЕ разработчика (вне devcontainer).
#
# Отвечает только за:
# - инициализацию окружения (setup)
# - прошивку платы через USB (flash)
# - сквозные сценарии (pipeline)
# - вспомогательные утилиты (util)
#
# Сборка, тесты и HAB-образы — в just/build.just (внутри devcontainer).
# Корневой justfile — точка входа для всех команд
# Может выполняться на хосте или внутри devcontainer
#
# Быстрый старт:
# ./scripts/bootstrap.sh # первый запуск после git clone
# just # показать все рецепты
# just # показать все доступные команды
# just bootstrap # первая настройка после git clone (на хосте)
# just build ... # сборка (внутри devcontainer)
# just host::flash # прошивка (на хосте)
# =============================================================================
JUST_MIN := "1.27.0"
UV_MIN := "0.4.0"
DOCKER_MIN := "24.0.0"
# === Глобальные настройки ===
# Все модули наследуют эти настройки
TOOLS_DIR := "tools/host"
set shell := ["bash", "-euo", "pipefail", "-c"]
# экспортируем все just-переменные в окружение
set export
# автоматически загружать .env
set dotenv-load
# === Общие переменные (доступны во всех модулях через export) ===
BOARD := env('BOARD', 'MIMXRT1052')
BUILD_DIR := env('BUILD_DIR', 'build')
TOOLS_DIR := env('TOOLS_DIR', 'tools/host')
CACHE_DIR := ".cache"
# По умолчанию — показать список рецептов
_default:
@just --list --unsorted
# === Модули ===
# Каждый модуль — это namespace с изолированными рецептами
# host.just — операции на хост-машине
mod host 'just/host.just'
# build.just — сборка в DevContainer
mod build 'just/build.just'
# ci.just — CI/CD сценарии (опционально)
mod ci 'just/ci.just'
# =============================================================================
# ГРУППА: setup — инициализация рабочего окружения
# =============================================================================
# === Default рецепт ===
# Вызывается при `just` без аргументов
# Показывает полное дерево команд из всех модулей
# [1/3] Проверить наличие и версии инструментов на хост-машине
[group('setup')]
check-deps:
#!/usr/bin/env bash
set -euo pipefail
default:
@just --list --list-submodules
BOLD="\033[1m"; GREEN="\033[0;32m"; RED="\033[0;31m"; YELLOW="\033[1;33m"; RESET="\033[0m"
ERRORS=0; WARNINGS=0
# === Популярные алиасы (для удобства команды) ===
# Сокращают длинные вызовы модулей
ok() { echo -e " ${GREEN}${1}${RESET}"; }
fail() { echo -e " ${RED}${1}${RESET}"; ERRORS=$((ERRORS+1)); }
warn() { echo -e " ${YELLOW}⚠️ ${1}${RESET}"; WARNINGS=$((WARNINGS+1)); }
# Инициализация окружения (эквивалент host::bootstrap)
init:
@just host::bootstrap
_uname="$(uname -s)"
case "${_uname}" in
Linux*) PLATFORM="linux" ;;
Darwin*) PLATFORM="macos" ;;
MINGW*|MSYS*|CYGWIN*) PLATFORM="windows" ;;
*) PLATFORM="unknown" ;;
esac
# Быстрая прошивка тестового образа в Debug
flash:
@just host::flash-test-debug
# Сравнение semver без sort -V (недоступен в Git Bash)
semver_ge() {
local a="$1" b="$2"
local a1 a2 a3 b1 b2 b3
IFS='.' read -r a1 a2 a3 <<< "${a}"
IFS='.' read -r b1 b2 b3 <<< "${b}"
a1="${a1//[^0-9]/}"; a2="${a2//[^0-9]/}"; a3="${a3//[^0-9]/}"
b1="${b1//[^0-9]/}"; b2="${b2//[^0-9]/}"; b3="${b3//[^0-9]/}"
[[ "${a1:-0}" -gt "${b1:-0}" ]] && return 0
[[ "${a1:-0}" -lt "${b1:-0}" ]] && return 1
[[ "${a2:-0}" -gt "${b2:-0}" ]] && return 0
[[ "${a2:-0}" -lt "${b2:-0}" ]] && return 1
[[ "${a3:-0}" -ge "${b3:-0}" ]] && return 0
return 1
}
# Полная сборка всех проектов в Release (в контейнере)
build-all:
@just build::hab-all-release
install_hint() {
local tool=$1
case "${PLATFORM}" in
linux)
case "$tool" in
just) echo "curl -sSf https://just.systems/install.sh | bash -s -- --to ~/.local/bin" ;;
uv) echo "curl -LsSf https://astral.sh/uv/install.sh | sh" ;;
docker) echo "https://docs.docker.com/engine/install/ubuntu/" ;;
esac ;;
macos)
case "$tool" in
just) echo "brew install just" ;;
uv) echo "brew install uv" ;;
docker) echo "brew install --cask docker" ;;
esac ;;
windows)
case "$tool" in
just) echo "winget install --id Casey.Just (в PowerShell)" ;;
uv) echo 'powershell -c "irm https://astral.sh/uv/install.ps1 | iex"' ;;
docker) echo "winget install --id Docker.DockerDesktop (в PowerShell)" ;;
esac ;;
esac
}
# CI пайплайн
run-ci:
@just ci::pipeline
check_tool() {
local name=$1 cmd=$2 min_ver=$3
shift 3
local ver_args=("${@:---version}")
if ! command -v "$cmd" &>/dev/null; then
fail "$name not found -- $(install_hint "$name")"
return
fi
local actual
actual=$("$cmd" "${ver_args[@]}" 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
if [[ -z "$actual" ]]; then
warn "$name found but version could not be detected"
return
fi
if semver_ge "$actual" "$min_ver"; then
ok "$name $actual (>= $min_ver)"
else
fail "$name $actual -- need >= $min_ver -- $(install_hint "$name")"
fi
}
echo ""
echo -e "${BOLD} Platform: ${PLATFORM}${RESET}"
echo ""
echo -e "${BOLD} Checking host dependencies...${RESET}"
echo ""
check_tool "just" "just" "{{JUST_MIN}}"
check_tool "uv" "uv" "{{UV_MIN}}"
check_tool "docker" "docker" "{{DOCKER_MIN}}" version --format '{{{{.Client.Version}}}}'
if [[ "${PLATFORM}" == "linux" ]]; then
echo ""
echo -e "${BOLD} Checking udev rules (Linux only)...${RESET}"
echo ""
local rules_file="/etc/udev/rules.d/99-nxp-mimxrt.rules"
local udev_ok=true
grep -q "1fc9" "$rules_file" 2>/dev/null || { warn "udev: BootROM rule missing (VID 1fc9)"; udev_ok=false; }
grep -q "15a2" "$rules_file" 2>/dev/null || { warn "udev: Flashloader rule missing (VID 15a2)"; udev_ok=false; }
if ! groups 2>/dev/null | grep -q plugdev; then
warn "User '$(whoami)' not in 'plugdev' group"
udev_ok=false
fi
if [[ "$udev_ok" == "false" ]]; then
warn "USB flashing requires root without udev rules -- run: just setup-udev"
else
ok "udev rules (NXP USB) + plugdev group"
fi
fi
echo ""
if [[ $ERRORS -gt 0 ]]; then
echo -e " ${RED}${BOLD}$ERRORS error(s) found. Fix them before continuing.${RESET}"
echo ""
exit 1
elif [[ $WARNINGS -gt 0 ]]; then
echo -e " ${YELLOW}${BOLD}$WARNINGS warning(s). Some features may not work.${RESET}"
else
echo -e " ${GREEN}${BOLD}All dependencies satisfied.${RESET}"
fi
echo ""
# [2/3] Установить udev правила для USB-прошивки (только Linux, требует sudo)
[group('setup')]
setup-udev:
#!/usr/bin/env bash
set -euo pipefail
case "$(uname -s)" in
Linux*) ;;
*)
echo " udev rules are Linux-only, skipping on $(uname -s)."
exit 0
;;
esac
echo " Installing udev rules for NXP USB devices..."
printf '%s\n%s\n%s\n%s\n' \
'# NXP BootROM -- SDP mode (BOOT_MOD_1 = 3V3)' \
'SUBSYSTEM=="usb", ATTR{idVendor}=="1fc9", ATTR{idProduct}=="0130", MODE="0666", GROUP="plugdev"' \
'# NXP Flashloader -- after jump-address' \
'SUBSYSTEM=="usb", ATTR{idVendor}=="15a2", ATTR{idProduct}=="0073", MODE="0666", GROUP="plugdev"' \
| sudo tee /etc/udev/rules.d/99-nxp-mimxrt.rules > /dev/null
sudo udevadm control --reload-rules
sudo udevadm trigger
sudo usermod -a -G plugdev "$USER"
echo " ✅ udev rules installed"
echo " ⚠️ Re-login required for 'plugdev' group to take effect"
# [3/3] Синхронизировать зависимости tools/host (spsdk и др.)
# Идемпотентен: пропускает sync если uv.lock не изменился
[group('setup')]
setup-tools:
#!/usr/bin/env bash
set -euo pipefail
LOCK="{{TOOLS_DIR}}/uv.lock"
STAMP="{{CACHE_DIR}}/uv_lock.sha256"
mkdir -p "{{CACHE_DIR}}"
current=$(sha256sum "$LOCK" 2>/dev/null | cut -d' ' -f1 || echo "missing")
previous=$(cat "$STAMP" 2>/dev/null || echo "none")
if [[ "$current" == "$previous" ]]; then
echo " ✅ tools/host deps up to date (lockfile unchanged)"
else
echo " 📦 Syncing tools/host deps..."
cd "{{TOOLS_DIR}}" && uv sync
echo "$current" > "../../$STAMP"
echo " ✅ tools/host deps installed"
fi
# Полная инициализация — запустить один раз после git clone
[group('setup')]
bootstrap:
#!/usr/bin/env bash
set -euo pipefail
_uname="$(uname -s)"
case "${_uname}" in
Linux*) PLATFORM="linux" ;;
Darwin*) PLATFORM="macos" ;;
MINGW*|MSYS*|CYGWIN*) PLATFORM="windows" ;;
*) PLATFORM="unknown" ;;
esac
echo ""
echo " === Bootstrap: step 1/3 -- check-deps ==="
just check-deps
if [[ "${PLATFORM}" == "linux" ]]; then
echo " === Bootstrap: step 2/3 -- setup-udev ==="
just setup-udev
else
echo " step 2/3 -- setup-udev skipped (${PLATFORM})"
fi
echo " === Bootstrap: step 3/3 -- setup-tools ==="
just setup-tools
echo ""
echo " ✅ Bootstrap complete."
echo " Next: open project in VSCode -> 'Reopen in Container'"
echo ""
# =============================================================================
# ГРУППА: flash — прошивка платы через USB
# Требует: uv + spsdk (just setup-tools), плата в SDP-режиме
# HAB-образы должны быть собраны заранее (just hab-* внутри devcontainer)
#
# Матрица прошивки:
# just flash firmware_test debug
# just flash firmware_test release
# just flash bootloader debug
# just flash bootloader release
# just flash app debug
# just flash app release
# =============================================================================
# Прошить выбранный HAB-образ.
# Использование: just flash <project> <type>
# project: firmware_test | bootloader | app
# type: debug | release (default: release)
#
# Образ должен быть собран заранее в devcontainer:
# just --justfile just/build.just hab-<project>-<type>
[group('flash')]
flash project type="release":
#!/usr/bin/env bash
set -euo pipefail
case "{{type}}" in
debug) BUILD_TYPE="Debug" ;;
release) BUILD_TYPE="Release" ;;
*)
echo " ❌ Unknown type: {{type}}"
echo " Valid: debug, release"
exit 1 ;;
esac
cd "{{TOOLS_DIR}}" && uv run python3 flash_usb.py \
--firmware "{{project}}" --build-type "${BUILD_TYPE}"
# Загрузить образ в RAM без записи во Flash.
# Полезно для быстрой проверки — не изнашивает Flash, плата стартует сразу.
# Использование: just flash-ram <project> <type>
[group('flash')]
flash-ram project type="debug":
#!/usr/bin/env bash
set -euo pipefail
case "{{type}}" in
debug) BUILD_TYPE="Debug" ;;
release) BUILD_TYPE="Release" ;;
*)
echo " ❌ Unknown type: {{type}}"
echo " Valid: debug, release"
exit 1 ;;
esac
cd "{{TOOLS_DIR}}" && uv run python3 flash_usb.py \
--firmware "{{project}}" --build-type "${BUILD_TYPE}" --ram-only
# Псевдонимы для частых сценариев ────────────────────────────────────────────
# firmware_test Debug — самый частый при разработке
[group('flash')]
flash-test-debug:
@just flash firmware_test debug
# firmware_test Release — как будет на производстве
[group('flash')]
flash-test-release:
@just flash firmware_test release
# bootloader + app Release — финальная прошивка
[group('flash')]
flash-production:
@just flash bootloader release
@just flash app release
# =============================================================================
# ГРУППА: pipeline — производственные сценарии (для сервера)
# =============================================================================
# Входной контроль платы: тестовая прошивка (Release) -> HIL-тесты
[group('pipeline')]
incoming: flash-test-release
#!/usr/bin/env bash
set -euo pipefail
echo " ▶ Running HIL tests (CAN, UART, SDRAM, SPI Flash)..."
# cd tools/hil && uv run python3 run_hil.py <- раскомментить когда готово
echo " ⚠️ HIL tests not yet implemented"
# Финальная прошивка: загрузчик + основная прошивка (Release)
[group('pipeline')]
production: flash-production
@echo " ✅ Production firmware flashed (bootloader + app)"
# =============================================================================
# ГРУППА: util — вспомогательные инструменты
# =============================================================================
# Найти подключённые NXP USB-устройства
[group('util')]
scan:
cd "{{TOOLS_DIR}}" && uv run nxpdevscan
# Проверить связь с BootROM через SDP (плата в SDP-режиме)
[group('util')]
sdp-status:
sdphost -u 0x1FC9,0x0130 -- error-status
# Проверить что Flashloader отвечает (после jump-address)
[group('util')]
flashloader-status:
blhost -u 0x15A2,0x0073 -- get-property 1 0
# Обновить spsdk до новой версии
# Использование: just upgrade-tools 3.8.0
[group('util')]
upgrade-tools version:
cd "{{TOOLS_DIR}}" && uv add "spsdk=={{version}}" && uv sync
@echo " ✅ spsdk upgraded to {{version}}"
@echo " Run: git add tools/host/uv.lock tools/host/pyproject.toml"

View file

@ -45,11 +45,13 @@
│ └── host/ # Инструменты для работы с таргетом
│ ├── flash_usb.py # Прошивка через USB ROM (nxp-spsdk / blhost)
│ ├── hab/ # Утилиты и гайд по HAB (Secure Boot)
│ ├── dcd/ # Device Configuration Data
│ └── HOW_TO_FLASH.md → см. также /HOW_TO_FLASH.md
│ └── dcd/ # Device Configuration Data
├── just/ # Just-модули (автоматизация)
│ ├── build.just # Сборка, тесты, HAB-образы (devcontainer)
│ ├── host.just # Прошивка, bootstrap, HIL (хост)
│ └── ci.just # CI/CD пайплайны
├── scripts/
│ ├── bootstrap.sh # Первичная настройка окружения
│ └── build.just # Рецепты сборки (используется через Justfile)
│ └── bootstrap.sh # Первичная настройка окружения (уровень 0)
├── docs/ # Документация проекта
│ ├── DEV_ARCH.md # Архитектура окружения разработки
│ ├── CMAKE_HINTS.md # Шпаргалка по CMake в проекте
@ -58,7 +60,7 @@
│ └── manufacturing_user's_guide.pdf
├── CMakeLists.txt # Корневой CMake
├── CMakePresets.json # Пресеты сборки (Release/Debug/Host)
├── Justfile # Точка входа для команд сборки/тестирования/прошивки
├── Justfile # Точка входа для команд (модули: build, host, ci)
└── README.md
```
@ -89,8 +91,8 @@ Bare-metal прошивка для **входного контроля** пла
Стратегия тестирования двухуровневая:
| Уровень | Расположение | Инструменты | Запуск |
|---|---|---|---|
| **Host-тесты** (unit + интеграционные) | `tests/host/` | Unity + fff | `ctest` в devcontainer |
| -------------------------------------- | --------------- | --------------- | -------------------------------------- |
| **Host-тесты** (unit + интеграционные) | `tests/host/` | Unity + fff | `just build::test-host` в devcontainer |
| **Target-тесты** (аппаратные) | `tests/target/` | Unity на железе | Удалённый ПК-сервер через SSH |
Подробнее — [tests/HostTestingGuide.md](tests/HostTestingGuide.md) и [tests/README.md](tests/README.md).
@ -100,7 +102,7 @@ Bare-metal прошивка для **входного контроля** пла
## Управление зависимостями
| Зависимость | Подход | Причина |
|---|---|---|
| ------------------------------- | -------------------- | -------------------------------------------- |
| NXP MCUXpresso SDK | vendored | Стабильная версия, обновлений не планируется |
| FreeRTOS, FatFS, LittleFS и др. | vendored (через SDK) | Стабильные версии |
| Unity + fff | vendored | Маленькие, стабильные |
@ -113,7 +115,7 @@ Bare-metal прошивка для **входного контроля** пла
## Devcontainer — состав окружения
| Инструмент | Назначение |
|---|---|
| ---------------------- | -------------------------------------- |
| `arm-none-eabi-gcc` | Сборка firmware для таргета |
| `arm-none-eabi-gdb` | Отладка через GDB server (удалённая) |
| `gcc` (host) | Сборка и запуск host-тестов |
@ -122,8 +124,8 @@ Bare-metal прошивка для **входного контроля** пла
| `clangd` | Language server для VSCode |
| `clang-format` | Форматирование кода |
| `clang-tidy` | Статический анализ |
| `Python 3 + nxp-spsdk` | Прошивка (blhost, nxpimage), HAB, провизия |
| `just` | Запуск рецептов сборки/тестирования/прошивки |
| `Python 3 + nxp-spsdk` | HAB-образы (nxpimage) |
| `just` | Запуск рецептов через модули `build::` |
---
@ -139,12 +141,18 @@ Bare-metal прошивка для **входного контроля** пла
git clone <repo-url>
cd tft_manufacture_test
# Инициализация хоста (один раз)
sudo chmod +x bootstrap.sh
./bootstrap.sh
# Открыть в VSCode → Reopen in Container
# Затем внутри devcontainer:
just build-host # сборка host-тестов
just test # запуск host-тестов через CTest
just build-firmware # сборка firmware для таргета
just build::test-host # сборка и запуск host-тестов
just build::build-firmware-test-debug # сборка firmware для таргета
just build::hab-firmware-test-debug # подготовка HAB-образа
# На хосте (вне контейнера):
just flash # прошивка через USB ROM
```

View file

@ -1,8 +1,8 @@
#!/usr/bin/env bash
# =============================================================================
# scripts/bootstrap.sh
# bootstrap.sh
# Уровень 0: устанавливает just (если отсутствует), затем передаёт управление
# just bootstrap для полной инициализации окружения.
# just host::bootstrap для полной инициализации окружения.
#
# Поддерживаемые платформы:
# Linux — bash (native)
@ -10,7 +10,7 @@
# Windows — Git Bash (поставляется вместе с git)
#
# Требования: bash >= 4, curl (Linux/macOS) или winget (Windows)
# Запуск: ./scripts/bootstrap.sh
# Запуск: ./bootstrap.sh
# =============================================================================
set -euo pipefail
@ -29,12 +29,11 @@ warn() { echo -e " ${YELLOW}⚠️ ${*}${RESET}"; }
error() { echo -e " ${RED}${*}${RESET}"; }
echo ""
echo -e "${BOLD}=== TFT Firmware — Bootstrap ===${RESET}"
echo -e "${BOLD}=== Project Bootstrap ===${RESET}"
echo ""
# -----------------------------------------------------------------------------
# 1. Определить платформу
# uname на Git Bash: MINGW64_NT-10.0-19045, MSYS_NT-..., CYGWIN_NT-...
# -----------------------------------------------------------------------------
_uname="$(uname -s)"
case "${_uname}" in
@ -52,14 +51,12 @@ echo ""
# -----------------------------------------------------------------------------
# 2. Сравнение semver без sort -V (недоступен в Git Bash)
# Возвращает 0 если $1 >= $2
# -----------------------------------------------------------------------------
semver_ge() {
local a="$1" b="$2"
local a1 a2 a3 b1 b2 b3
IFS='.' read -r a1 a2 a3 <<< "${a}"
IFS='.' read -r b1 b2 b3 <<< "${b}"
# Убрать нечисловые суффиксы (1.36.0-beta -> обнулить суффикс)
a1="${a1//[^0-9]/}"; a2="${a2//[^0-9]/}"; a3="${a3//[^0-9]/}"
b1="${b1//[^0-9]/}"; b2="${b2//[^0-9]/}"; b3="${b3//[^0-9]/}"
@ -98,18 +95,18 @@ install_just_macos() {
}
install_just_windows() {
# Предпочитаем winget -- кладёт just.exe в системный PATH автоматически
if command -v winget &>/dev/null; then
info "Installing just via winget..."
winget install --id Casey.Just \
--accept-package-agreements --accept-source-agreements || {
error "winget install failed."
echo " Run manually in PowerShell: winget install --id Casey.Just"
echo " Then restart Git Bash and re-run: ./scripts/bootstrap.sh"
echo " Then restart Git Bash and re-run: ./bootstrap.sh"
exit 1
}
warn "Restart Git Bash so the new PATH from winget takes effect,"
warn "then re-run: ./scripts/bootstrap.sh"
warn "then re-run: ./bootstrap.sh"
exit 0
else
error "winget not found."
@ -150,8 +147,8 @@ if [[ "${JUST_OK}" == "false" ]]; then
fi
# -----------------------------------------------------------------------------
# 5. Передать управление just bootstrap
# 5. Передать управление just host::bootstrap для дальнейшей настройки рабочего окружения
# -----------------------------------------------------------------------------
info "Delegating to: just bootstrap"
info "Delegating to: just host::bootstrap"
echo ""
exec just bootstrap "$@"
exec just host::bootstrap "$@"

View file

@ -13,10 +13,10 @@
**Devcontainer** — всё что касается кода: сборка, статический анализ,
форматирование, host-тесты, подготовка HAB-образов. Разработчик проводит
здесь большую часть времени. Управляется через VSCode tasks и `scripts/build.just`.
здесь большую часть времени. Управляется через VSCode tasks и модуль `just build::`.
**Хост** — всё что касается железа: прошивка платы через USB, отладка
через JLink/probe-rs. Управляется через корневой `Justfile`.
через JLink/probe-rs. Управляется через модуль `just host::`.
Такое разделение решает несколько проблем: USB-устройства не требуют
проброса в контейнер; оба разработчика работают в идентичных условиях;
@ -30,10 +30,11 @@ CI использует те же команды что и локальная р
ПК разработчика
├── Хост (Linux / macOS / Windows + Git Bash)
│ ├── just ← запуск задач хостового уровня
│ ├── just ← запуск задач хостового уровня (just host::*)
│ ├── docker ← управление devcontainer
│ ├── git ← работа с репозиторием
│ ├── uv + spsdk ← прошивка платы (flash_usb.py, sdphost, blhost)
│ │ venv: tools/host/.venv-host
│ ├── JLinkGDBServer / probe-rs ← сервер отладки (USB → TCP :2331)
│ └── VSCode ← IDE (Dev Containers extension)
@ -45,8 +46,9 @@ CI использует те же команды что и локальная р
│ ├── clang-tidy-17 ← статический анализ
│ ├── clang-format-17 ← форматирование кода
│ ├── cmake-format ← форматирование CMakeLists
│ ├── just ← запуск задач внутри контейнера
│ ├── just ← запуск задач внутри контейнера (just build::*)
│ ├── uv + spsdk ← сборка HAB-образов (только nxpimage)
│ │ venv: tools/host/.venv-container
│ └── Unity + fff ← фреймворки host-тестов
└── Плата TFT (IMXRT1052) — на столе у разработчика
@ -60,7 +62,7 @@ CI использует те же команды что и локальная р
## 3. Что устанавливается и где
| Инструмент | Хост | Devcontainer | Сервер |
|---|---|---|---|
| ------------------ | --------- | ------------ | --------- |
| `just` | ✅ | ✅ Dockerfile | ✅ |
| `docker` | ✅ | — | — |
| `git` | ✅ | ✅ | ✅ |
@ -81,16 +83,21 @@ CI использует те же команды что и локальная р
Версия `spsdk` зафиксирована в `tools/host/uv.lock` — все три места
используют одну и ту же версию.
---
## 4. Структура репозитория (automation-часть)
```bash
/
├── Justfile ← хост: прошивка, setup, pipeline
├── Justfile ← корневой оркестратор; модули: build, host, ci
├── just/
│ ├── build.just ← devcontainer: сборка, тесты, HAB
│ ├── host.just ← хост: прошивка, bootstrap, HIL
│ └── ci.just ← CI/CD пайплайны
├── scripts/
│ └── bootstrap.sh ← уровень 0: just → just bootstrap
│ └── build.just ← devcontainer: сборка, тесты, HAB
│ └── bootstrap.sh ← уровень 0: just → just host::bootstrap
├── .devcontainer/
│ ├── Dockerfile
│ └── devcontainer.json
@ -110,7 +117,7 @@ CI использует те же команды что и локальная р
│ ├── pyproject.toml
│ └── uv.lock
├── .vscode/
│ └── tasks.json ← UI для build.just (внутри devcontainer)
│ └── tasks.json ← UI для just build::* (внутри devcontainer)
├── CMakePresets.json
├── cmake/
├── sdk/
@ -119,7 +126,7 @@ CI использует те же команды что и локальная р
├── firmware/
│ ├── test/
│ ├── bootloader/
│ └── app/
│ └── tft_app/
└── tests/ ← host-тесты (Unity + fff)
```
@ -130,7 +137,7 @@ CI использует те же команды что и локальная р
### 5.1 Предварительные требования
| Платформа | Что нужно до bootstrap |
|---|---|
| --------- | ----------------------------------------------------------- |
| Linux | `docker`, `git`, `curl` |
| macOS | Docker Desktop, `git` (Xcode CLT) |
| Windows | Docker Desktop, Git for Windows → **использовать Git Bash** |
@ -139,7 +146,7 @@ CI использует те же команды что и локальная р
```bash
git clone <repo-url> && cd <repo>
./scripts/bootstrap.sh
./bootstrap.sh
```
### 5.3 Что делает bootstrap
@ -151,15 +158,15 @@ bootstrap.sh (уровень 0)
│ uname: MINGW64_NT-... → windows, Linux → linux, Darwin → macos
├── проверить just (semver без sort -V — работает в Git Bash)
< 1.27.0 или отсутствует:
< 1.36.0 или отсутствует:
│ Linux/macOS → curl | bash → ~/.local/bin/just
│ Windows → winget install --id Casey.Just
│ (перезапустить Git Bash после установки)
└── exec just bootstrap
└── exec just host::bootstrap
├── [1/3] check-deps
│ just >= 1.27.0 · uv >= 0.4.0 · docker >= 24.0.0
│ just >= 1.36.0 · uv >= 0.4.0 · docker >= 24.0.0
│ платформо-зависимые подсказки при ошибках
├── [2/3] setup-udev (только Linux)
@ -170,7 +177,7 @@ bootstrap.sh (уровень 0)
│ требует re-login · на macOS/Windows пропускается
└── [3/3] setup-tools
uv sync в tools/host/
uv sync в tools/host/ (venv: .venv-host на хосте)
SHA-256 uv.lock кэшируется в .cache/
повторный вызов мгновенный если lockfile не изменился
```
@ -184,7 +191,7 @@ bootstrap.sh (уровень 0)
`postCreateCommand` выполняется автоматически при поднятии контейнера:
```bash
cd tools/host && uv sync &&
cd tools/host && uv sync && # venv: .venv-container
cd ../.. &&
cmake --preset host-debug &&
cmake --preset Debug
@ -200,20 +207,20 @@ cmake --preset Debug
### 6.1 Три подпроекта
| Прошивка | Boot-стратегия | DCD | Назначение |
|---|---|---|---|
| --------------- | -------------------- | ---- | ---------------------------------------- |
| `firmware_test` | XIP из Flash | ✅ | Входной контроль, тестирование периферии |
| `bootloader` | Копирование в ITCM | ❌ | Загрузчик, не использует SDRAM |
| `app` | XIP + буферы в SDRAM | ✅ | Основное приложение (FreeRTOS, LCDIF) |
| `tft_app` | XIP + буферы в SDRAM | ✅ | Основное приложение (FreeRTOS, LCDIF) |
### 6.2 Матрица сборки
Каждый проект собирается в двух режимах:
| | Debug | Release |
|---|---|---|
| --------------- | ------------------- | ------------------ |
| `firmware_test` | разработка, отладка | HAB для сервера |
| `bootloader` | отладка загрузчика | финальная прошивка |
| `app` | отладка приложения | финальная прошивка |
| `tft_app` | отладка приложения | финальная прошивка |
### 6.3 CMake пресеты
@ -235,7 +242,7 @@ buildPresets (host):
```bash
0x60000000 FCB — Flash Config Block 512 байт (пишет Flashloader)
0x60001000 IVT + BDT ← начало HAB-образа
0x60001040 DCD — инициализация SDRAM ~1088 байт (firmware_test, app)
0x60001040 DCD — инициализация SDRAM ~1088 байт (firmware_test, tft_app)
0x60003000 Код прошивки (.text, .data…)
```
@ -246,7 +253,7 @@ buildPresets (host):
### 7.1 Карта задач по контекстам
| Задача | Где |
|---|---|
| -------------------------------------- | -------------------------- |
| Написание кода, clangd, форматирование | devcontainer |
| Статический анализ (clang-tidy) | devcontainer |
| Host-тесты (Unity + fff) | devcontainer |
@ -265,7 +272,7 @@ buildPresets (host):
├── писать код
├── Ctrl+Shift+P → "Run Task" → 🧪 Host Tests (Debug)
│ или в терминале: just --justfile just/build.just test-host
│ или в терминале: just build::test-host
├── Ctrl+Shift+P → "Run Task" → 🔨 Build → firmware-test · debug
│ → build/Debug/firmware/test/firmware_test.elf
@ -277,7 +284,7 @@ buildPresets (host):
├── just flash firmware_test debug ← прошить отладочный образ
│ или
├── just flash-ram firmware_test debug ← загрузить в RAM (быстро, без износа Flash)
├── just host::flash-ram firmware_test debug ← загрузить в RAM (быстро, без износа Flash)
└── F5 в VSCode → отладка через JLink
```
@ -285,14 +292,14 @@ buildPresets (host):
### 7.3 VSCode Tasks (внутри devcontainer)
| Таск | Input 1 | Input 2 | Команда |
|---|---|---|---|
| 🔨 Build | project | debug/release | `just build-<project>-<type>` |
| 🧪 Host Tests (Debug) | — | — | `just test-host` |
| 🧪 Host Tests (Release) | — | — | `just test-host-release` |
| 📦 HAB Image | project | debug/release | `just hab-<project>-<type>` |
| 📦 HAB All (Debug) | — | — | `just hab-all-debug` |
| 📦 HAB All (Release) | — | — | `just hab-all-release` |
| 🗑️ Clean | — | — | `just clean` |
| ---------------------- | ------- | ------------- | ------------------------------------ |
| 🔨 Build | project | debug/release | `just build::build-<project>-<type>` |
| 🧪 Host Tests (Debug) | — | — | `just build::test-host` |
| 🧪 Host Tests (Release) | — | — | `just build::test-host-release` |
| 📦 HAB Image | project | debug/release | `just build::hab-<project>-<type>` |
| 📦 HAB All (Debug) | — | — | `just build::hab-all-debug` |
| 📦 HAB All (Release) | — | — | `just build::hab-all-release` |
| 🗑️ Clean | — | — | `just build::clean` |
Таски «Build» и «HAB Image» запрашивают два input последовательно:
сначала проект (`firmware-test / bootloader / app / all`),
@ -309,7 +316,7 @@ buildPresets (host):
2. Reset
3. Подключить USB к хосту
→ плата определяется как VID:PID = 1FC9:0130
4. Выполнить нужный just flash-* рецепт
4. Выполнить нужный just host::flash-* рецепт
5. После прошивки: BOOT_MOD_1 → GND, Reset
→ плата стартует из Flash
```
@ -318,23 +325,24 @@ buildPresets (host):
```bash
# Основной рецепт (project × type)
just flash firmware_test debug # разработка — итерации с отладчиком
just flash firmware_test release # проверить как будет на сервере
just flash bootloader debug
just flash bootloader release
just flash app debug
just flash app release
just host::flash firmware_test debug # разработка — итерации с отладчиком
just host::flash firmware_test release # проверить как будет на сервере
just host::flash bootloader debug
just host::flash bootloader release
just host::flash tft_app debug
just host::flash tft_app release
# TODO: Загрузка в RAM — без записи во Flash, мгновенный старт
# Удобно для частых итераций: не изнашивает Flash, не нужен BOOT_MOD переключатель
just flash-ram firmware_test # default: debug
just flash-ram firmware_test debug
just flash-ram app release
# Загрузка в RAM — без записи во Flash, мгновенный старт
# Удобно для частых итераций: не изнашивает Flash
just host::flash-ram firmware_test # default: debug
just host::flash-ram firmware_test debug
just host::flash-ram tft_app release
# TODO: Быстрые псевдонимы
just flash-test-debug # = just flash firmware_test debug
just flash-test-release # = just flash firmware_test release
just flash-production # bootloader release + app release
# Быстрые псевдонимы (из корневого Justfile)
just flash # = just host::flash-test-debug
just host::flash-test-debug
just host::flash-test-release
just host::flash-production # bootloader release + tft_app release
```
### 8.3 Что происходит внутри flash_usb.py
@ -403,7 +411,7 @@ Devcontainer
Фреймворк: Unity + fff
Пресеты: host-debug / host-release
Компилятор: системный clang-17 (не ARM GCC)
Запуск: just --justfile just/build.just test-host
Запуск: just build::test-host
Результат: JUnit XML → VSCode CTest Lab + GitLab CI
```
@ -439,17 +447,17 @@ Devcontainer
feature-ветка
├── код в devcontainer
🧪 Host Tests ← зелёные?
🔨 Build firmware-test debug ← компилируется?
just build::test-host ← зелёные?
just build::build-firmware-test-debug ← компилируется?
├── проверка на железе
📦 HAB Image → firmware-test · debug
│ just flash-test-debug ← прошить
just build::hab-firmware-test-debug
│ just flash ← прошить (алиас flash-test-debug)
│ target-тесты со стендом ← периферия работает?
├── подготовка к MR
📦 HAB All (Release) ← финальные образы
│ just flash firmware_test release ← убедиться что release работает
just build::hab-all-release ← финальные образы
│ just host::flash firmware_test release ← убедиться что release работает
└── Merge Request → GitLab
[CI pipeline — отдельная тема]
@ -457,29 +465,29 @@ feature-ветка
Производственный сервер
just incoming → firmware_test release → HIL
just production → bootloader + app release
just host::incoming → firmware_test release → HIL
just host::production → bootloader + tft_app release
```
---
## 12. Обновление зависимостей
### spsdk (на хосте разработчика)
### spsdk
```bash
just upgrade-tools 3.8.0
just host::upgrade-tools 3.8.0
git add tools/host/uv.lock tools/host/pyproject.toml
git commit -m "chore: upgrade spsdk to 3.8.0"
```
После этого у всех разработчиков и в контейнере обновится автоматически
при следующем `just setup-tools` / `uv sync`.
при следующем `just host::setup-tools` / `uv sync`.
### just в Dockerfile
```dockerfile
ARG JUST_VERSION=1.40.0 # .devcontainer/Dockerfile — единственное место
ARG JUST_VERSION=1.36.0 # .devcontainer/Dockerfile — единственное место
```
### ARM toolchain
@ -494,7 +502,7 @@ ARG TOOLCHAIN_VERSION=14.2.rel1 # .devcontainer/Dockerfile
### После git pull если изменился uv.lock
```bash
just setup-tools # автоматически обнаружит изменение и выполнит uv sync
just host::setup-tools # автоматически обнаружит изменение и выполнит uv sync
```
---
@ -509,12 +517,12 @@ just setup-tools # автоматически обнаружит измене
— только теггированные релизы, прошедшие CI
Сценарий входного контроля новой платы:
1. just incoming
1. just host::incoming
└── flash firmware_test release → HIL-тесты периферии
2. Тесты пройдены:
just production
just host::production
├── flash bootloader release
└── flash app release
└── flash tft_app release
Установлено: just · uv + spsdk · git
НЕ установлено: docker · cmake · компилятор · ARM toolchain
@ -527,8 +535,8 @@ just setup-tools # автоматически обнаружит измене
## Приложение А: минимальные версии
| Инструмент | Версия | Причина |
|---|---|---|
| `just` | 1.27.0 | поддержка `[group()]` |
| ------------ | --------- | ------------------------------------------------------------ |
| `just` | 1.36.0 | поддержка `mod` с кастомным путём (`mod host 'just/host.just'`) |
| `uv` | 0.4.0 | стабильный lockfile формат |
| `docker` | 24.0.0 | Compose v2, `--build-arg` |
| `spsdk` | 3.7.x | совместимость с HAB yaml-форматом |
@ -539,28 +547,29 @@ just setup-tools # автоматически обнаружит измене
```bash
# ── Первый запуск ──────────────────────────────────────────────
./scripts/bootstrap.sh # инициализация хоста
./bootstrap.sh # инициализация хоста
# VSCode → Reopen in Container
# ── Внутри devcontainer (терминал VSCode) ──────────────────────
just --justfile just/build.just test-host
just --justfile just/build.just build-firmware-test-debug
just --justfile just/build.just build-app-release
just --justfile just/build.just hab-firmware-test-debug
just --justfile just/build.just hab-all-release
just --justfile just/build.just clean
just build::test-host
just build::build-firmware-test-debug
just build::build-tft-app-release
just build::hab-firmware-test-debug
just build::hab-all-release
just build::clean
# ── Хостовый терминал — прошивка ───────────────────────────────
just flash firmware_test debug # во Flash
just flash firmware_test release
just flash bootloader release
just flash app release
just flash-ram firmware_test # в RAM (быстро, без износа Flash)
just flash-production # bootloader + app release
just flash # алиас: firmware_test debug
just host::flash firmware_test debug # во Flash
just host::flash firmware_test release
just host::flash bootloader release
just host::flash tft_app release
just host::flash-ram firmware_test # в RAM (быстро, без износа Flash)
just host::flash-production # bootloader + tft_app release
# ── Хостовый терминал — обслуживание ───────────────────────────
just scan # найти NXP USB-устройства
just sdp-status # проверить BootROM
just setup-tools # обновить spsdk после git pull
just check-deps # проверить версии инструментов
just host::scan # найти NXP USB-устройства
just host::sdp-status # проверить BootROM
just host::setup-tools # обновить spsdk после git pull
just host::check-deps # проверить версии инструментов
```

View file

@ -30,7 +30,7 @@ int main(void)
{
BOARD_Init();
GPIO_PinWrite(BOARD_INITPINS_UserLed1_PORT, BOARD_INITPINS_UserLed1_PIN, 0);
//GPIO_PinWrite(BOARD_INITPINS_UserLed2_PORT, BOARD_INITPINS_UserLed2_PIN, 0);
GPIO_PinWrite(BOARD_INITPINS_UserLed2_PORT, BOARD_INITPINS_UserLed2_PIN, 0);
while (1)
{
}

View file

@ -5,10 +5,11 @@
# Рабочая директория — корень репозитория (не scripts/).
# =============================================================================
set working-directory := ".."
# === Импорт общих переменных ===
TOOLS_DIR := env('TOOLS_DIR', justfile_directory() / 'tools/host')
BUILD_DIR := env('BUILD_DIR', justfile_directory() / 'build')
TOOLS_DIR := "tools/host"
BUILD_DIR := "build"
set working-directory := '..'
_default:
@just --list --justfile scripts/build.just --unsorted
@ -22,7 +23,7 @@ _default:
# app × debug / release
# all × debug / release (все проекты сразу, для CI)
# =============================================================================
# Приватные рецепты для configure (не показываются в --list)
[private]
_configure-debug:
cmake --preset Debug
@ -68,7 +69,7 @@ build-all-release: _configure-release
cmake --build --preset all-release
# ── утилиты ───────────────────────────────────────────────────────────────────
[group('build')]
[group('build'), confirm("Delete build/ directory?")]
clean:
rm -rf build
@ -112,7 +113,7 @@ test-host-release: _configure-host-release
# =============================================================================
# ── firmware_test ─────────────────────────────────────────────────────────────
[group('flash')]
[group('hab_image_gen')]
hab-firmware-test-debug: build-firmware-test-debug
#!/usr/bin/env bash
set -euo pipefail
@ -122,7 +123,7 @@ hab-firmware-test-debug: build-firmware-test-debug
-o "../../../{{BUILD_DIR}}/Debug/firmware_test_hab.bin"
echo " ✅ firmware_test_hab.bin (Debug)"
[group('flash')]
[group('hab_image_gen')]
hab-firmware-test-release: build-firmware-test-release
#!/usr/bin/env bash
set -euo pipefail
@ -133,7 +134,7 @@ hab-firmware-test-release: build-firmware-test-release
echo " ✅ firmware_test_hab.bin (Release)"
# ── bootloader ───────────────────────────────────────────────────────────────
[group('flash')]
[group('hab_image_gen')]
hab-bootloader-debug: build-bootloader-debug
#!/usr/bin/env bash
set -euo pipefail
@ -143,7 +144,7 @@ hab-bootloader-debug: build-bootloader-debug
-o "../../../{{BUILD_DIR}}/Debug/bootloader_hab.bin"
echo " ✅ bootloader_hab.bin (Debug)"
[group('flash')]
[group('hab_image_gen')]
hab-bootloader-release: build-bootloader-release
#!/usr/bin/env bash
set -euo pipefail
@ -154,7 +155,7 @@ hab-bootloader-release: build-bootloader-release
echo " ✅ bootloader_hab.bin (Release)"
# ── app ───────────────────────────────────────────────────────────────────────
[group('flash')]
[group('hab_image_gen')]
hab-app-debug: build-app-debug
#!/usr/bin/env bash
set -euo pipefail
@ -164,7 +165,7 @@ hab-app-debug: build-app-debug
-o "../../../{{BUILD_DIR}}/Debug/app_hab.bin"
echo " ✅ app_hab.bin (Debug)"
[group('flash')]
[group('hab_image_gen')]
hab-app-release: build-app-release
#!/usr/bin/env bash
set -euo pipefail
@ -175,27 +176,28 @@ hab-app-release: build-app-release
echo " ✅ app_hab.bin (Release)"
# ── все образы сразу ─────────────────────────────────────────────────────────
[group('flash')]
[group('hab_image_gen')]
hab-all-debug: hab-firmware-test-debug hab-bootloader-debug hab-app-debug
@echo " ✅ All HAB images (Debug) ready"
[group('flash')]
[group('hab_image_gen')]
hab-all-release: hab-firmware-test-release hab-bootloader-release hab-app-release
@echo " ✅ All HAB images (Release) ready"
# ── верификация образа (для отладки) ─────────────────────────────────────────
# Использование: just --justfile just/build.just hab-verify firmware_test debug
[group('flash')]
[group('hab_image_gen')]
hab-verify project="firmware_test" type="release":
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(pwd)"
case "{{project}}-{{type}}" in
firmware_test-debug) BIN="{{BUILD_DIR}}/Debug/firmware_test_hab.bin" ;;
firmware_test-release) BIN="{{BUILD_DIR}}/Release/firmware_test_hab.bin" ;;
bootloader-debug) BIN="{{BUILD_DIR}}/Debug/bootloader_hab.bin" ;;
bootloader-release) BIN="{{BUILD_DIR}}/Release/bootloader_hab.bin" ;;
app-debug) BIN="{{BUILD_DIR}}/Debug/app_hab.bin" ;;
app-release) BIN="{{BUILD_DIR}}/Release/app_hab.bin" ;;
firmware_test-debug) BIN="${ROOT}/{{BUILD_DIR}}/Debug/firmware_test_hab.bin" ;;
firmware_test-release) BIN="${ROOT}/{{BUILD_DIR}}/Release/firmware_test_hab.bin" ;;
bootloader-debug) BIN="${ROOT}/{{BUILD_DIR}}/Debug/bootloader_hab.bin" ;;
bootloader-release) BIN="${ROOT}/{{BUILD_DIR}}/Release/bootloader_hab.bin" ;;
app-debug) BIN="${ROOT}/{{BUILD_DIR}}/Debug/app_hab.bin" ;;
app-release) BIN="${ROOT}/{{BUILD_DIR}}/Release/app_hab.bin" ;;
*)
echo " ❌ Unknown: {{project}}-{{type}}"
echo " Projects: firmware_test, bootloader, app"
@ -203,6 +205,20 @@ hab-verify project="firmware_test" type="release":
exit 1 ;;
esac
OUT="/tmp/hab_parse_{{project}}_{{type}}.yaml"
uv run nxpimage hab parse -b "${BIN}" -o "${OUT}"
cd "{{TOOLS_DIR}}" && uv run nxpimage hab parse -b "${BIN}" -o "${OUT}"
echo " ✅ ${OUT}"
grep -E "(entry|csf|tag)" "${OUT}" || true
# =============================================================================
# ГРУППА: quality — линтеры, форматтеры
# =============================================================================
# TODO: проверять bsp,firmware, modules
[group('quality')]
lint:
echo "TODO!"
# clang-tidy -p {{BUILD_DIR}}/Debug src/**/*.c
[group('quality')]
format:
echo "TODO!"
# clang-format -i src/**/*.{c,h}

53
just/ci.just Normal file
View file

@ -0,0 +1,53 @@
# =============================================================================
# ci.just — CI/CD пайплайны
# Выполняется в CI-окружении (GitHub Actions, GitLab CI, Jenkins)
#
# Предполагается что CI работает внутри devcontainer.
# =============================================================================
# === Импорт общих переменных ===
BUILD_DIR := env('BUILD_DIR', justfile_directory() / 'build')
set working-directory := '..'
# =============================================================================
# ГРУППА: ci — полные пайплайны
# =============================================================================
# Полный CI pipeline: сборка + тесты + линтеры
[group('ci')]
pipeline: build test lint
@echo " ✅ CI pipeline complete"
# Сборка всех проектов в Release
[group('ci')]
build:
just build::build-all-release
# Unit-тесты
[group('ci')]
test:
just build::test-host-release
# TODO: Линтеры + статический анализ
[group('ci')]
lint:
echo "TODO"
#just build::lint
#just build::check
# Генерация HAB-образов для релиза
[group('ci')]
release: build
just build::hab-all-release
@echo " ✅ Release artifacts ready in {{BUILD_DIR}}/Release/"
# Генерация coverage отчёта (если gcov настроен)
[group('ci'), private]
_coverage:
#!/usr/bin/env bash
set -euo pipefail
if command -v gcovr &>/dev/null; then
echo " 📊 Generating coverage report..."
gcovr --xml -o {{BUILD_DIR}}/coverage.xml
else
echo " ⚠️ gcovr not found, skipping coverage"
fi

338
just/host.just Normal file
View file

@ -0,0 +1,338 @@
# =============================================================================
# host.just — операции на хост-машине (вне devcontainer)
#
# Ответственность:
# - bootstrap окружения (just, uv, docker, udev)
# - USB-прошивка через spsdk
# - HIL-тесты на реальном железе
# - производственные сценарии (incoming, production)
#
# Все рецепты должны работать БЕЗ devcontainer.
# =============================================================================
# === Импорт общих переменных из корневого justfile ===
# Благодаря `set export` в корне, эти переменные доступны
TOOLS_DIR := env('TOOLS_DIR', justfile_directory() / 'tools/host')
CACHE_DIR := env('CACHE_DIR', justfile_directory() / '.cache')
# === Минимальные версии зависимостей ===
JUST_MIN := "1.36.0"
UV_MIN := "0.4.0"
DOCKER_MIN := "24.0.0"
set working-directory := '..'
# =============================================================================
# ГРУППА: setup — инициализация рабочего окружения
# =============================================================================
# [1/3] Проверить наличие и версии инструментов на хост-машине
[group('setup')]
check-deps:
#!/usr/bin/env bash
set -euo pipefail
BOLD="\033[1m"; GREEN="\033[0;32m"; RED="\033[0;31m"; YELLOW="\033[1;33m"; RESET="\033[0m"
ERRORS=0; WARNINGS=0
ok() { echo -e " ${GREEN}✅ ${1}${RESET}"; }
fail() { echo -e " ${RED}❌ ${1}${RESET}"; ERRORS=$((ERRORS+1)); }
warn() { echo -e " ${YELLOW}⚠️ ${1}${RESET}"; WARNINGS=$((WARNINGS+1)); }
_uname="$(uname -s)"
case "${_uname}" in
Linux*) PLATFORM="linux" ;;
Darwin*) PLATFORM="macos" ;;
MINGW*|MSYS*|CYGWIN*) PLATFORM="windows" ;;
*) PLATFORM="unknown" ;;
esac
# Сравнение semver без sort -V (недоступен в Git Bash)
semver_ge() {
local a="$1" b="$2"
local a1 a2 a3 b1 b2 b3
IFS='.' read -r a1 a2 a3 <<< "${a}"
IFS='.' read -r b1 b2 b3 <<< "${b}"
a1="${a1//[^0-9]/}"; a2="${a2//[^0-9]/}"; a3="${a3//[^0-9]/}"
b1="${b1//[^0-9]/}"; b2="${b2//[^0-9]/}"; b3="${b3//[^0-9]/}"
[[ "${a1:-0}" -gt "${b1:-0}" ]] && return 0
[[ "${a1:-0}" -lt "${b1:-0}" ]] && return 1
[[ "${a2:-0}" -gt "${b2:-0}" ]] && return 0
[[ "${a2:-0}" -lt "${b2:-0}" ]] && return 1
[[ "${a3:-0}" -ge "${b3:-0}" ]] && return 0
return 1
}
install_hint() {
local tool=$1
case "${PLATFORM}" in
linux)
case "$tool" in
just) echo "curl -sSf https://just.systems/install.sh | bash -s -- --to ~/.local/bin" ;;
uv) echo "curl -LsSf https://astral.sh/uv/install.sh | sh" ;;
docker) echo "https://docs.docker.com/engine/install/ubuntu/" ;;
esac ;;
macos)
case "$tool" in
just) echo "brew install just" ;;
uv) echo "brew install uv" ;;
docker) echo "brew install --cask docker" ;;
esac ;;
windows)
case "$tool" in
just) echo "winget install --id Casey.Just (в PowerShell)" ;;
uv) echo 'powershell -c "irm https://astral.sh/uv/install.ps1 | iex"' ;;
docker) echo "winget install --id Docker.DockerDesktop (в PowerShell)" ;;
esac ;;
esac
}
check_tool() {
local name=$1 cmd=$2 min_ver=$3
shift 3
local ver_args=("${@:---version}")
if ! command -v "$cmd" &>/dev/null; then
fail "$name not found -- $(install_hint "$name")"
return
fi
local actual
actual=$("$cmd" "${ver_args[@]}" 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
if [[ -z "$actual" ]]; then
warn "$name found but version could not be detected"
return
fi
if semver_ge "$actual" "$min_ver"; then
ok "$name $actual (>= $min_ver)"
else
fail "$name $actual -- need >= $min_ver -- $(install_hint "$name")"
fi
}
echo ""
echo -e "${BOLD} Platform: ${PLATFORM}${RESET}"
echo ""
echo -e "${BOLD} Checking host dependencies...${RESET}"
echo ""
check_tool "just" "just" "{{JUST_MIN}}"
check_tool "uv" "uv" "{{UV_MIN}}"
check_tool "docker" "docker" "{{DOCKER_MIN}}" version --format '{{{{.Client.Version}}}}'
if [[ "${PLATFORM}" == "linux" ]]; then
echo ""
echo -e "${BOLD} Checking udev rules (Linux only)...${RESET}"
echo ""
rules_file="/etc/udev/rules.d/99-nxp-mimxrt.rules"
udev_ok=true
grep -q "1fc9" "$rules_file" 2>/dev/null || { warn "udev: BootROM rule missing (VID 1fc9)"; udev_ok=false; }
grep -q "15a2" "$rules_file" 2>/dev/null || { warn "udev: Flashloader rule missing (VID 15a2)"; udev_ok=false; }
if ! groups 2>/dev/null | grep -q plugdev; then
warn "User '$(whoami)' not in 'plugdev' group"
udev_ok=false
fi
if [[ "$udev_ok" == "false" ]]; then
warn "USB flashing requires root without udev rules -- run: just setup-udev"
else
ok "udev rules (NXP USB) + plugdev group"
fi
fi
echo ""
if [[ $ERRORS -gt 0 ]]; then
echo -e " ${RED}${BOLD}$ERRORS error(s) found. Fix them before continuing.${RESET}"
echo ""
exit 1
elif [[ $WARNINGS -gt 0 ]]; then
echo -e " ${YELLOW}${BOLD}$WARNINGS warning(s). Some features may not work.${RESET}"
else
echo -e " ${GREEN}${BOLD}All dependencies satisfied.${RESET}"
fi
echo ""
# [2/3] Установить udev правила для USB-прошивки (только Linux, требует sudo)
[group('setup')]
setup-udev:
#!/usr/bin/env bash
set -euo pipefail
case "$(uname -s)" in
Linux*) ;;
*)
echo " udev rules are Linux-only, skipping on $(uname -s)."
exit 0
;;
esac
echo " Installing udev rules for NXP USB devices..."
printf '%s\n%s\n%s\n%s\n' \
'# NXP BootROM -- SDP mode (BOOT_MOD_1 = 3V3)' \
'SUBSYSTEM=="usb", ATTR{idVendor}=="1fc9", ATTR{idProduct}=="0130", MODE="0666", GROUP="plugdev"' \
'# NXP Flashloader -- after jump-address' \
'SUBSYSTEM=="usb", ATTR{idVendor}=="15a2", ATTR{idProduct}=="0073", MODE="0666", GROUP="plugdev"' \
| sudo tee /etc/udev/rules.d/99-nxp-mimxrt.rules > /dev/null
sudo udevadm control --reload-rules
sudo udevadm trigger
sudo usermod -a -G plugdev "$USER"
echo " ✅ udev rules installed"
echo " ⚠️ Re-login required for 'plugdev' group to take effect"
# [3/3] Синхронизировать зависимости tools/host (spsdk и др.)
# Идемпотентен: пропускает sync если uv.lock не изменился
[group('setup')]
setup-tools:
#!/usr/bin/env bash
set -euo pipefail
LOCK="{{TOOLS_DIR}}/uv.lock"
STAMP="{{CACHE_DIR}}/uv_lock.sha256"
mkdir -p "{{CACHE_DIR}}"
current=$(sha256sum "$LOCK" 2>/dev/null | cut -d' ' -f1 || echo "missing")
previous=$(cat "$STAMP" 2>/dev/null || echo "none")
if [[ "$current" == "$previous" ]]; then
echo " ✅ tools/host deps up to date (lockfile unchanged)"
else
echo " 📦 Syncing tools/host deps..."
cd "{{TOOLS_DIR}}" && uv sync
echo "$current" > "$STAMP"
echo " ✅ tools/host deps installed"
fi
# Полная инициализация — запустить один раз после git clone
[group('setup')]
bootstrap:
#!/usr/bin/env bash
set -euo pipefail
_uname="$(uname -s)"
case "${_uname}" in
Linux*) PLATFORM="linux" ;;
Darwin*) PLATFORM="macos" ;;
MINGW*|MSYS*|CYGWIN*) PLATFORM="windows" ;;
*) PLATFORM="unknown" ;;
esac
echo ""
echo " === Bootstrap: step 1/3 -- check-deps ==="
just host::check-deps
if [[ "${PLATFORM}" == "linux" ]]; then
echo " === Bootstrap: step 2/3 -- setup-udev ==="
just host::setup-udev
else
echo " step 2/3 -- setup-udev skipped (${PLATFORM})"
fi
echo " === Bootstrap: step 3/3 -- setup-tools ==="
just host::setup-tools
echo ""
echo " ✅ Bootstrap complete."
echo " Next: open project in VSCode -> 'Reopen in Container'"
echo ""
# =============================================================================
# ГРУППА: flash — прошивка платы через USB
# Требует: uv + spsdk (just setup-tools), плата в SDP-режиме
# HAB-образы должны быть собраны заранее (just hab-* внутри devcontainer)
#
# Матрица прошивки:
# just flash firmware_test debug
# just flash firmware_test release
# just flash bootloader debug
# just flash bootloader release
# just flash app debug
# just flash app release
# =============================================================================
# Прошить выбранный HAB-образ.
# Использование: just flash <project> <type>
# project: firmware_test | bootloader | app
# type: debug | release (default: release)
#
# Образ должен быть собран заранее в devcontainer:
# just --justfile just/build.just hab-<project>-<type>
[group('flash')]
flash project type="release":
#!/usr/bin/env bash
set -euo pipefail
case "{{type}}" in
debug) BUILD_TYPE="Debug" ;;
release) BUILD_TYPE="Release" ;;
*)
echo " ❌ Unknown type: {{type}}"
echo " Valid: debug, release"
exit 1 ;;
esac
cd "{{TOOLS_DIR}}" && uv run python3 flash_usb.py \
--firmware "{{project}}" --build-type "${BUILD_TYPE}"
# Загрузить образ в RAM без записи во Flash.
# Полезно для быстрой проверки — не изнашивает Flash, плата стартует сразу.
# Использование: just flash-ram <project> <type>
[group('flash')]
flash-ram project type="debug":
#!/usr/bin/env bash
set -euo pipefail
case "{{type}}" in
debug) BUILD_TYPE="Debug" ;;
release) BUILD_TYPE="Release" ;;
*)
echo " ❌ Unknown type: {{type}}"
echo " Valid: debug, release"
exit 1 ;;
esac
cd "{{TOOLS_DIR}}" && uv run python3 flash_usb.py \
--firmware "{{project}}" --build-type "${BUILD_TYPE}" --ram-only
# Псевдонимы для частых сценариев ────────────────────────────────────────────
# firmware_test Debug — самый частый при разработке
[group('flash')]
flash-test-debug:
@just host::flash firmware_test debug
# firmware_test Release — как будет на производстве
[group('flash')]
flash-test-release:
@just host::flash firmware_test release
# TODO: bootloader + app Release — финальная прошивка
[group('flash'), confirm("Flash bootloader + app (Release)?")]
flash-production:
@just host::flash bootloader release
@just host::flash app release
# =============================================================================
# ГРУППА: pipeline — производственные сценарии (для сервера)
# =============================================================================
# Входной контроль платы: тестовая прошивка (Release) -> HIL-тесты
[group('pipeline')]
incoming: flash-test-release
#!/usr/bin/env bash
set -euo pipefail
echo " ▶ Running HIL tests (CAN, UART, SDRAM, SPI Flash)..."
# cd tools/hil && uv run python3 run_hil.py <- раскомментить когда готово
echo " ⚠️ HIL tests not yet implemented"
# Финальная прошивка: загрузчик + основная прошивка (Release)
[group('pipeline')]
production: flash-production
@echo " ✅ Production firmware flashed (bootloader + app)"
# =============================================================================
# ГРУППА: util — вспомогательные инструменты
# =============================================================================
# Найти подключённые NXP USB-устройства
[group('util')]
scan:
cd "{{TOOLS_DIR}}" && uv run nxpdevscan
# Проверить связь с BootROM через SDP (плата в SDP-режиме)
[group('util'), no-cd]
sdp-status:
cd "{{TOOLS_DIR}}" && uv run sdphost -u 0x1FC9,0x0130 -- error-status
# Проверить что Flashloader отвечает (после jump-address)
[group('util')]
flashloader-status:
cd "{{TOOLS_DIR}}" && uv run blhost -u 0x15A2,0x0073 -- get-property 1 0
# Обновить spsdk до новой версии
# Использование: just upgrade-tools 3.8.0
[group('util')]
upgrade-tools version:
cd "{{TOOLS_DIR}}" && uv add "spsdk=={{version}}" && uv sync
@echo " ✅ spsdk upgraded to {{version}}"
@echo " Run: git add tools/host/uv.lock tools/host/pyproject.toml"