Прежде всего – хотелось избавить себя от рутинных операций, которые необходимо прокручивать в каждый релиз артефакта каждого проекта. Второе – понять, нужен ли мне вообще такой подход у разработке, сколько от него профита. Третье – узнать новое.
Вот несколько ссылок, где есть некоторая информация по этому вопросу:
- https://community.st.com/s/question/0D53W00000ITwGRSA1/how-to-use-headless-build-for-continuous-integration – вопрос на сайте ST. Обсуждение и интересные мысли
- https://www.youtube.com/playlist?list=PL4cGeWgaBTe1uwiqIAc6fwPzPpvgPZI2J – отличнейший материал в виде видео на английском с примером установки того же самого на локальной машине для одного разработчика.
- https://embeddedartistry.com/blog/2018/10/18/embedded-systems-testing-resources – больше рассказывают про TDD, но есть отдельные статьи про CI/CD
Не забываем, что мне совершенно неизвестно как делать правильно. Универсального рецепта не нашлось, поэтому собираю своего Кракена на win-сервере(local).
Прежде всего, я отказался от слоя абстракции связанной с контейнеризацией. Сорян, любители Docker, мне он пока не зашел. Отличная идея использовать контейнеры, но духоты предвижу и так с головой – лучше не усложнять “архитектуру” еще одним слоем. Оставим на лучшее время. Трезво оценив возможности своих познаний я понял, что слои из Groovy pipeline-скриптов, batch-скриптов предоставляют уже немаленький интерфейсный ад.
Классическая разработка под MCU:
- Синхронизация с GitLab(GitHub, SVN…)
- Внесение изменений в прошивку
- Сборка прошивки (необходимо подготовленное рабочее окружение)
- Подготовка артефактов сборки к публикации
- Публикация прошивок на сервере
- Ручное добавление прошивок на новых релизах при поставке их в составе комплекса ПО
Как я это вижу с CI/CD:
- Синхронизация с Git
- Внесение изменений в прошивку
- Добавление тега текущей версии
- Пуш в Git
Автоматизированная сборка и публикация:
Вижу тут некоторые плюсы:
- Автоматизация задач:
- Сборка прошивок
- Прогон тестов
- Подготовка прошивок к публикации (подпись, правка, изменение имени)
- Публикация на сервер
- Использование единого набора инструментов для сборки
- Снижение сложности входа для новых сотрудников (спорно, но все же в перспективе)
- Возможность быстро получить новую версию прошивки без необходимости настраивать рабочее окружение (тоже спорно, но иногда очень бывает нужно для хотфикса)
Да, я вполне понимаю, что подобная система даст наибольшую отдачу при использовании Make/CMake и контейнеров (для изолирования среды и работы с уже подготовленными средами). Но имеем, что имеем – переписывать весь ворох проектов ради своего интереса в нерабочее время, это выше моих сил.
Дальнейшее описание будет для системы контроля версий GitLab.
У них есть отличная документация по интеграции GitLab с Jenkins (https://docs.gitlab.com/ee/integration/jenkins.html), лучше делать по ней, а сюда лишь посматривать – вдруг чего будет интересного
Про Jenkins
Суть его – лишь скрипт-раннер и место хранения
Дженкинс может подхватывать веб-хуки с гитлаба и сам начинать выполнять скрипты
Лучше всего, опять же, почитать документацию(https://www.jenkins.io/doc/book/using/) – здесь будет лишь очень поверхностная информация
В Jenkins прописывается:
- git ссылочка до проекта с кодом и бранчем
- Откуда брать файл со скриптом конфигурации дженкинса (В этом скрипте прописываются шаги и какие батнички выполнить – достаточно просто)
То есть, практически вся работа у Jenkins будет стянуть сборку, сбилдить и запустить пару батников.
Есть два варианта использования:
Нажимаем создать Item (Item – это один проект для сборки)
Тут нужно вписать название задачи и выбрать тип. Со свободной конфигурацией и Pipeline.
Далее нужно вписать ссылку на проект и данные авторизации
Триггеры сборки определяют по какому из событий будет выполнена задача
Свободная конфигурация позволяет настроить все шаги сборки через веб-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 в разделе GitLab выставил ссылку на ip GitLab и добавил авторизацию по токену GitLab API.
Токен генерируется в GitLab и для него выставил доступ к API.
В настройках GitLab-проекта для сборки в settings->integrations включил 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 из проекта на гите
Так же в Jenkins прописал глобальные переменные с особыми путями (IDE, cppcheck, папка с bat-файлами и т.д.)
Вот и все. При пуше в репозиторий с тегом, будет проведена сборка проекта согласно скрипту Jenkinsfile. Артефакты сборки можно будет закинуть на файловый сервер/другой репозиторий с помощью bat файлов.
Вот так выглядит пайплайн проекта:
Видно, что есть замечания от cppcheck – смотрим:
Предупреждения кликабельны – можно посмотреть какой участок кода вызвал проблему прям в вебе:
Ну, и артефакты сборки пушаться на файловый сервер с smb-протоколом и на git, т.к. весят достаточно мало.
Немного примеров и хавту
Дженкинс по дефолту идет как локальная служба – нужно сделать его админом, иначе он не сможет вылезать в сетку
Для того, чтобы собрать проект через консоль, нужно использовать фичу ide, которая называется 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
Содержание файла 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 git clone [email protected]: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 chcp 65001 set work_dir=%1 set firm_file_dir=%2 set firm_file_name=%3 set dest_folder=%4 cd /d %work_dir% cd /d %firm_file_dir% net.exe use %dest_folder% /persistent:no >NUL if exist %dest_folder%\%firm_file_name% ( echo FILE ALREADY EXIST net.exe use %dest_folder% /delete >NUL exit /b 1 ) else ( xcopy /s /i /c %firm_file_name% %dest_folder%\%firm_file_name%* >NUL if exist %dest_folder%\Changelog.txt ( del %dest_folder%\Changelog.txt ) cd /d %work_dir% git log --pretty=format:"%%ah, %%an внес следующие изменения:%%n%%B %%n%%n***********************************%%n" > %dest_folder%\Changelog.txt ) if exist "%firm_file_name%" del "%firm_file_name%" net.exe use %dest_folder% /delete >NUL
Comment