Как стать автором
Обновить

Подобие CI/CD c микроконтроллерами. Jenkins + GitLab + HeadlessBuild

Уровень сложностиПростой
Время на прочтение7 мин
Количество просмотров3.6K

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

Вот несколько ссылок, где есть некоторая информация по этому вопросу:

Не забываем, что мне совершенно неизвестно как делать правильно. Универсального рецепта не нашлось, поэтому собираю своего Кракена на win-сервере(local).

Прежде всего, я отказался от слоя абстракции связанной с контейнеризацией. Прошу прощения, любители Docker, мне он пока не зашел. Отличная идея использовать контейнеры, но духоты предвижу и так с головой – лучше не усложнять “архитектуру” еще одним слоем. Оставим на лучшее время. Трезво оценив возможности своих познаний я понял, что слои из Groovy pipeline-скриптов, batch-скриптов предоставляют уже немаленький конвейерный ад.

Классическая разработка под MCU:

  1. Синхронизация с GitLab(GitHub, SVN…)

  2. Внесение изменений в прошивку

  3. Сборка прошивки (необходимо подготовленное рабочее окружение)

  4. Подготовка артефактов сборки к публикации

  5. Публикация прошивок на сервере

  6. Ручное добавление прошивок на новых релизах при поставке их в составе комплекса ПО

Как я это вижу с CI/CD:

  1. Синхронизация с Git

  2. Внесение изменений в прошивку

  3. Добавление тега текущей версии

  4. Пуш в Git

Автоматизированная сборка и публикация:

Вижу тут некоторые плюсы:

  • Автоматизация задач:

    • Сборка прошивок

    • Прогон тестов

    • Подготовка прошивок к публикации (подпись, правка, изменение имени)

    • Публикация на сервер

  • Использование единого набора инструментов для сборки

  • Снижение сложности входа для новых сотрудников (спорно, но все же в перспективе)

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

Да, я вполне понимаю, что подобная система даст наибольшую отдачу при использовании Make/CMake и контейнеров (для изолирования среды и работы с уже подготовленными средами). Но имеем, что имеем – переписывать весь ворох проектов ради своего интереса в нерабочее время, это выше моих сил.

Дальнейшее описание будет для системы контроля версий GitLab.

У них есть отличная документация по интеграции GitLab с Jenkins (https://docs.gitlab.com/ee/integration/jenkins.html), лучше делать по ней, а сюда лишь посматривать – вдруг чего будет интересного

Про Jenkins


Суть его – лишь скрипт-раннер и место хранения
Дженкинс может подхватывать веб-хуки с гитлаба и сам начинать выполнять скрипты
Лучше всего, опять же, почитать документацию – здесь будет лишь очень поверхностная информация

В Jenkins прописывается:

  • git ссылка до проекта с кодом и/или определенной веткой

  • Откуда брать файл со скриптом конфигурации дженкинса. В этом скрипте прописываются шаги и какие батники выполнить – достаточно просто.

То есть, практически вся работа у Jenkins будет стянуть сборку, собрать и запустить пару батников.

Есть два основных варианта использования Jenkins. Со свободной конфигурацией и Pipeline

В первую очередь, нажимаем создать Item (Item – это один проект для сборки)

Тут нужно вписать название задачи и выбрать тип

Далее нужно вписать ссылку на проект и данные авторизации

Триггеры сборки определяют по какому из событий будет выполнена задача

Свободная конфигурация позволяет настроить все шаги сборки через веб-GUI. Вот пример

Pipeline – более гибкая в настройке система, которая позволяет исполнять скрипт сборки при наступлении события-триггера. Шаги сборки определяет скрипт на языке Groovy. Триггеры сборки и git-авторизация остаются теми же для обеих систем.

Pipeline Script позволяет записать скрипт в окне в веб-GUI. Pipeline Script from SCM – скачивает и исполняет Groovy-скрипт(Jenkinsfile) напрямую из репозитория с проектом.

Обратите внимание, что имя файла-скрипта должно быть указано в Script Path

Шаги для настройки своего проекта

Скачал Jenkins с официального сайта. Запустил, установил, ничего сложного. Установил GitLab, Pipeline: Stage View, Pipeline, Folders, Credentials, CppCheck и еще пару плагинов - тут уж на ваш вкус. Перезапустил Jenkins.

Еще в Jenkins прописал глобальные переменные с особыми путями (IDE, cppcheck, папка с bat-файлами и т.д.)

В конфигурации системы Jenkins в разделе GitLab выставил ссылку на ip-адрес GitLab и добавил авторизацию по токену GitLab API.

Токен генерируется в GitLab и для него выставил доступ к API

В настройках GitLab-проекта для сборки в settings->integrations включил Jenkins CL

Если нет Jenkins CL, то во вкладке settings->webhooks можно очень похожим образом настроить все тоже самое, с небольшой разницей в степени интеграции Jenkins и GitLab.

Jenkins URL: http://MY_PC_IP:8080/

Project Name: TEST_BUILDER (Должно совпадать с именем подготовленного задания на Jenkins)

Username и пароль – от моего Jenkins (логин: admin и пароль, который находится, примерно, тут C:\ProgramData\Jenkins.jenkins\secrets\initialAdminPassword)

На Jenkins создал Item Pipeline с настройкой Pipeline Script from SCM.

Теперь можно запускать Jenkinsfile из проекта на git.

Вот и все. При пуше в репозиторий с тегом, будет проведена сборка проекта согласно скрипту Jenkinsfile. Артефакты сборки можно будет закинуть на файловый сервер/другой репозиторий с помощью .bat файлов.

Вот так выглядит пайплайн сборки проекта:

Справа на графике видно, что есть замечания от cppcheck – смотрим:

Предупреждения кликабельны – можно посмотреть какой участок кода вызвал проблему прям в вебе:

Ну, и артефакты сборки пушатся на файловый сервер (Samba, smb-протокол) и на git, т.к. весят достаточно мало

Немного примеров и батников

Дженкинс по дефолту идет как локальная служба – нужно сделать его админом, иначе он не сможет вылезать в сетку

Для того, чтобы собрать проект через консоль, нужно использовать фичу, которая называется HeadlessBuild. Т.к. CubeIDE основана на Eclipse, то и команды очень похожие. Сама команда есть в примере файла build_stm_prj.bat. Как именно запускать stm32cubeide через терминал и какие параметры она принимает – можно узнать, выполнив такую команду:

\stm32cubeidec.exe -nosplash  --launcher.suppressErrors -application org.eclipse.cdt.managedbuilder.core.headlessbuild  -help

Пример команды сборки проекта для Atmel Studio:

if exist "build_log.txt" del "build_log.txt"
del /q AVR_PROJ\Release\

"E:\Program Files (x86)\Atmel\Atmel Studio 6.2\atmelstudio.exe" AVR_PROJ.atsln /rebuild Release /out build_log.txt
type build_log.txt

Пример содержания Jenkinsfile. Глобальные переменные (${FILE_SERVER_ADDR} ${CPP_CHECK} ${BAT_TOOLS}) задаются в настройках системы у Jenkins

def RESULT_FILE_NAME
def TAGS_VERSION

pipeline{

    agent any

    environment{
        PROJ_NAME = "test_prj"
        PROJ_DIR = "test_prj"
        PROJ_ARTIFACTS_TYPE = "srec"
        PROJ_SOURCES = "${PROJ_DIR}\\Core\\Src"
        PROJ_ARTIFACTS_DIR = "${PROJ_DIR}\\Release"
        PROJ_ARTIFACTS_TYPE = "srec"
        SERVER_ARTIFACTS_DIR = "${FILE_SERVER_ADDR}\\FW_Folder"
        GITLAB_ARTIFACTS_DIR = "test_prj1"
        GITLAB_TARGET_NAME = "test_fw.${PROJ_ARTIFACTS_TYPE}"
    }

    stages{
        stage('Check CPP'){
            steps{
                bat "${CPP_CHECK} ${PROJ_SOURCES} 2> cppcheck-result.xml"
                publishCppcheck pattern:'cppcheck-result.xml'
            }
        }
        stage('Unit tests'){
            steps{
                updateGitlabCommitStatus name: 'test', state: 'pending'
                dir("${PROJ_DIR}"){
                    bat "${CEEDLING}"
                }
                updateGitlabCommitStatus name: 'test', state: 'success'
            }
        }
        stage('Building'){
            steps{
                updateGitlabCommitStatus name: 'build', state: 'pending'
                bat "${BAT_TOOLS}\\build_stm_prj.bat ${WORKSPACE} ${PROJ_NAME}"
                updateGitlabCommitStatus name: 'build', state: 'success'
            }
        }
        stage('Publish Artifact'){
            steps{
                updateGitlabCommitStatus name: 'Artifact publication', state: 'pending'
                script {
                    TAGS_VERSION = getCommandOutput("${BAT_TOOLS}\\get_tag.bat ${WORKSPACE}")
                    echo "Tags is ${TAGS_VERSION}"
                }
                bat "${BAT_TOOLS}\\publish_file_to_gitlab.bat ${WORKSPACE} ${PROJ_ARTIFACTS_DIR} ${PROJ_NAME} ${GITLAB_ARTIFACTS_DIR} ${GITLAB_TARGET_NAME} \"${GITLAB_ARTIFACTS_DIR} project updated to version ${TAGS_VERSION}\""
                bat "${BAT_TOOLS}\\publish_file_to_server.bat ${WORKSPACE} ${PROJ_ARTIFACTS_DIR} ${PROJ_NAME} ${SERVER_ARTIFACTS_DIR}"
                updateGitlabCommitStatus name: 'Artifact publication', state: 'success'
            }
        }
    }

    post{
        
        success{
            echo "Build succes"
        }
        unsuccessful{
            updateGitlabCommitStatus name: 'build', state: 'failed'
            updateGitlabCommitStatus name: 'Artifact publication', state: 'failed'
            echo "ouhh..."
        }
    }
}

def getCommandOutput(cmd) {
    if (isUnix()){
         return sh(returnStdout:true , script: '#!/bin/sh -e\n' + cmd).trim()
     } else{
       stdout = bat(returnStdout:true , script: cmd).trim()
       result = stdout.readLines().drop(1).join(" ")       
       return result
    } 
}

Содержание файла get_tag.bat:

@echo off
set work_dir=%1
cd /d %work_dir%
setlocal enableextensions
for /F "tokens=1-2 delims=." %%I in (' git describe --tags ^| grep -Eo "[0-9]+\.[0-9]+"') do (
    set tag_main=%%I
    set tag_minor=%%J
    )
echo %tag_main% %tag_minor%

Содержание файла build_stm_prj.bat:

@echo off
set proj_dir=%1
set project=%2

echo %proj_dir%
echo %project%

cd /d "%proj_dir%"
if exist "C:\STM32CubeIDE_headlessBuilds" rd /q /s "C:\STM32CubeIDE_headlessBuilds"
C:\ST\STM32CubeIDE_1.3.0\STM32CubeIDE\stm32cubeidec.exe --launcher.suppressErrors -nosplash -application org.eclipse.cdt.managedbuilder.core.headlessbuild -data "C:\STM32CubeIDE_headlessBuilds" -import %project% -cleanBuild all

Содержание файла для публикации артефакта на gitlab - publish_file_to_gitlab.bat:

SetLocal EnableExtensions

set work_dir=%1
set firm_file_dir=%2
set firm_file_name=%3
set project_git_name=%4
set target_firm_file_name=%5
set commit_str=%6

cd /d %work_dir%
cd /d %firm_file_dir%

::forced delete folder 
rd /q /s gitlab_fw_dir 2>nul

::change the ip to yours
git clone git@0.0.0.0:FW/gitlab_fw_dir.git

::create project folder if not exist
if not exist "gitlab_fw_dir\%project_git_name%\" mkdir "gitlab_fw_dir\%project_git_name%"


::delete prev fw if exist
if exist "gitlab_fw_dir\%project_git_name%\%target_firm_file_name%" del "gitlab_fw_dir\%project_git_name%\%target_firm_file_name%"

xcopy %firm_file_name% gitlab_fw_dir\%project_git_name%\%target_firm_file_name%* >NUL

cd /d gitlab_fw_dir

git add -A
git commit -a -m %commit_str% >nul
git push >nul

cd /d ..
::forced delete folder 
rd /q /s gitlab_dir 2>nul

exit 0

Содержание файла publish_file_to_server.bat (добавлен человекочитаемый вывод лога git на сервер в виде файла Changelog.txt):

SetLocal EnableExtensions

set work_dir=%1
set firm_file_dir=%2
set firm_file_name=%3
set project_git_name=%4
set target_firm_file_name=%5
set commit_str=%6

cd /d %work_dir%
cd /d %firm_file_dir%

::forced delete folder 
rd /q /s gitlab_fw_dir 2>nul

git clone git@0.0.0.0:FW/gitlab_fw_dir.git

::create project folder if not exist
if not exist "gitlab_fw_dir\%project_git_name%\" mkdir "gitlab_fw_dir\%project_git_name%"


::delete prev fw if exist
if exist "gitlab_fw_dir\%project_git_name%\%target_firm_file_name%" del "gitlab_fw_dir\%project_git_name%\%target_firm_file_name%"

xcopy %firm_file_name% gitlab_fw_dir\%project_git_name%\%target_firm_file_name%* >NUL

cd /d gitlab_fw_dir

git add -A
git commit -a -m %commit_str% >nul
git push >nul

cd /d ..
::forced delete folder 
rd /q /s gitlab_dir 2>nul

exit 0

Весь ворох скриптов и сборка сильно упрощается, если у вас проекты сделаны на самописных Make/Cmake - файлах, а система сборки запускается на Linux-сервере. Тогда, уместным является использование Docker-контейнеров с настроенным окружением под каждый проект. Локальная разработка будет логично продолжена сборкой, тестированием и публикацией артефактов.

Про это на Конвеерум рассказывал Дмитрий Лисин (Третий Пин):

На этом завершаем небольшое знакомство с процессом непрерывной интеграции программного обеспечения для микроконтроллеров. Успешных разработок!

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Что чаще всего вы используете при разработке ПО на микроконтроллеры?
45.83% IDE от производителя чипа11
33.33% IDE универсальное (Keil, Iar)8
41.67% Clang/LLVM/GCC + самописные Makefile/Cmake + Блокнот/VSCode10
Проголосовали 24 пользователя. Воздержались 4 пользователя.
Теги:
Хабы:
Всего голосов 12: ↑12 и ↓0+12
Комментарии9

Публикации

Истории

Работа

DevOps инженер
43 вакансии

Ближайшие события

One day offer от ВСК
Дата16 – 17 мая
Время09:00 – 18:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область