Сейчас трудно представить разработку программного обеспечения без автоматизированных сборок проекта и тестирования. Для минимизации временных затрат на интеграцию изменений разработчиков в проект, существуют разные готовые решения. В данной статье я расскажу о замене сервера непрерывной интеграции CruiseControl.NET на Jenkins в команде разработчиков PVS-Studio. А также о том, что нас к этому побудило, какие цели мы преследовали и с какими проблемами столкнулись.
Непрерывная интеграция (англ. Continuous Integration, далее CI) - автоматизированный процесс сборки, развёртывания и тестирования разрабатываемого программного обеспечения. Эта практика разработки популярна как в больших коллективах, так и у индивидуальных разработчиков. Для подобной практики есть много готовых решений. В данной статье пойдёт речь о бесплатных проектах с открытым исходным кодом CruiseControl.NET и Jenkins.
CruiseControl.NET ( CCNet ) — инструмент для непрерывной интеграции программного обеспечения, реализованный на .NET Framework. Также существуют варианты инструмента на Java (CruiseControl) и версия для Ruby-сред (CruiseControl.rb). Управление и просмотр информации о сборках осуществляется через веб-интерфейс или desktop утилиту. Интегрируется с различными системами управления версиями. Является проектом с открытым исходным кодом и, к сожалению, не развивается примерно с 2013 года.
Jenkins — инструмент для непрерывной интеграции с открытым исходным кодом, написанный на Java. Был ответвлён от проекта Hudson после спора с Oracle. Обеспечивая функции непрерывной интеграции, позволяет автоматизировать часть процесса разработки программного обеспечения, в котором не обязательно участие человека. Возможности Jenkins можно расширять с помощью плагинов. В данный момент проект активно развивается и поддерживается, и разработчиками, и сообществом.
Хоть статья немного и похожа на обзор в стиле "CCNet Vs. Jenkins", акцент сразу будет сделан на выборе в пользу сервера с Jenkins. Главной причиной смены инструмента непрерывной интеграции для нас является отсутствие развития у проекта CruiseControl.NET. В статье будут описаны и другие моменты работы с CCNet, с которыми возникали трудности.
Недавно у проекта PVS-Studio был юбилей 10 лет, о котором можно прочесть в статье "Как 10 лет назад начинался проект PVS-Studio". Больше половины времени существования продукта мы использовали CCNet. Его интерфейс, настройки и функции стали настолько привычными, что Jenkins казался крайне неудобным. Впервые мы стали его использовать, когда появился PVS-Studio для Linux. Переходу на Jenkins предшествовало длительное изучение этого инструмента. Часть времени мы потратили на поиск аналога привычных нам функций из CCNet. Далее будут описаны интересные моменты из проделанной работы.
Настройки проектов CCNet (конфигурация сервера) хранились в одном xml файле, а различные пароли в другом. Хоть файл настроек и достиг размера ~4500 строк, пользоваться им было довольно удобно. Лёгким нажатием Alt+2 в Notepad++ список всех проектов можно свернуть и редактировать нужный (рисунок 1).
Рисунок 1 - Редактирование CCNet настроек в Notepad++
Хоть файл и содержал дублирующийся код, при поддержке сервера особых затруднений не возникало.
Вот так заполнялся блок SCM:
<svn>
<username>&SVN_USERNAME;</username>
<password>&SVN_PASSWORD;</password>
<trunkUrl>&SVN_ROOT;...</trunkUrl>
<workingDirectory>&PROJECT_ROOT;...</workingDirectory>
<executable>&SVN_FOLDER;</executable>
<deleteObstructions>true</deleteObstructions>
<cleanUp>true</cleanUp>
<revert>true</revert>
<timeout units="minutes">30</timeout>
</svn>
Так заполнялся блок MSBuild:
<msbuild>
<description>PVS-Studio 2015</description>
<workingDirectory>&PROJECT_ROOT;...</workingDirectory>
<projectFile>...\PVS-Studio-vs2015.sln</projectFile>
<buildArgs>/p:Configuration=Release</buildArgs>
<targets>Build</targets>
<timeout>600</timeout>
<executable>&MSBUILD14_PATH;</executable>
</msbuild>
А так заполнялся блок для общих задач:
<exec>
<description>PVS-Studio 2015 sign</description>
<executable>&PROJECT_ROOT;...\SignToolWrapper.exe</executable>
<baseDirectory>&PROJECT_ROOT;...</baseDirectory>
<buildArgs>"&SIGNTOOL;" ... \PVS-Studio-vs2015.dll"</buildArgs>
<buildTimeoutSeconds>600</buildTimeoutSeconds>
</exec>
На основе такого проектного файла CCNet потом удобно отображает все выполняемые шаги (правда, только в десктопной tray утилите. Web интерфейс это почему-то не поддерживал). В Jenkins с "высокоуровневым" отображением этапов прохождения интеграционного проекта пришлось повозиться, но об будет рассказано позже.
Для Jenkins необходимо хранить достаточно много файлов настроек: конфиг сервера, файлы настроек некоторых плагинов, каждый проект имеет свой собственный файл конфигурации. Хоть все эти файлы заданы в формате xml, для просмотра и редактирования они не очень удобны (по крайней мере по сравнению с CCNet), т.к. все команды внутри тэгов прописаны сплошным текстом. Правда, в большей степени это связано с идеологией использования инструмента. В CCNet конфиг пишется вручную, и поэтому, может быть "красиво" отформатирован. Jenkins же предполагает редактирование настроек проекта через свой web интерфейс, а конфиги генерирует автоматически.
Примерно так выглядят команды в конфигах Jenkins'а:
<hudson.tasks.BatchFile>
<command>CD "%BUILD_FOLDERS%\Builder"
PVS-Studio_setup.exe /VERYSILENT /SUPPRESSMSGBOXES ...
Publisher_setup.exe /VERYSILENT /SUPPRESSMSGBOXES</command>
</hudson.tasks.BatchFile>
И это ещё очень маленький пример.
Как я писал ранее, в CCNet проекты заполняются Task блоками. Вот так выглядит успешно выполненная задача с отображением шагов (рисунок 2).
Рисунок 2 - Просмотр статуса задачи в CCTray (desktop клиент для CCNet)
Ошибку в любом из блоков хорошо видно в иерархии подзадач. Это очень удобная и наглядная визуализация интеграционного процесса. Почти никогда не было необходимости искать логи, по описанию задачи было сразу понятно, что стоит проверить на локальном компьютере. Прямо в таком виде в Jenkins ничего не нашлось, поэтому пришлось подробно изучить этот момент перед переездом на новый сервер.
Можно провести такую аналогию между CCNet и Jenkins: в CCNet есть проект (Project), шагом в этом проекте является Task (как видно на рисунке выше). В Jenkins также есть проект (Job), а его шагами являются Step'ы (рисунок 3).
Рисунок 3 - Соответствие именований проектов в CCNet и Jenkins
К сожалению, web интерфейс Jenkins'а не умеет визуализировать работу отдельных шагов - у Job'а есть только полный консольный лог всех шагов вместе. Большим неудобством здесь является то, что из Jenkins невозможно посмотреть, какой из шагов завершился с ошибкой - нужно смотреть полный лог сборки Job'а. И так как к хорошему быстро привыкаешь, от старого сценария использования отказываться не хотелось. Тогда на помощь к нам пришёл Multijob Plugin.
Этот плагин позволил нам внести следующие нововведения:
1. Использование Job'ов в качестве Step'ов в других Job'ах. Таким образом появились универсальные Job'ы, которые позволили отделить лог конкретных подзадач и, самое главное, отдельно просматривать статусы конкретных подзадач. Web интерфейс Jenkins умеет хорошо визуализировать выполнение отдельных Job'ов в рамках Multijob'а - как раз то, что мы искали. На рисунке 4 показан пример выполненного Multijob'а.
Рисунок 4 - Просмотр выполненного Multijob'а
2. С использованием универсальных Job'ов удалось избавиться от дублирующего кода. Например, есть компиляция какой-нибудь утилиты: для дистрибутива, для запуска тестов и для запуска анализа кода. В CCNet это были одинаковые Task блоки в 3-х разных проектах. В Jenkins для компиляции этой утилиты сделан Job, который используют несколько Multijob'ов.
3. При создании проектов в Jenkins используется следующая идеология. Все Job'ы мы разделяем на Multijob "проекты" и универсальные "шаги". Имена универсальных Job'ов имеют префикс "job_" и не подразумевают использование как самостоятельного проекта. Также они не содержат загрузку исходного кода из репозитория. Имена Multijob'ов имеют префикс "proj_" и включают в себя загрузку исходного кода, и запуск только других Job'ов. (Step'ы мы стараемся избегать, т.к. они не визуализируются).
Универсальные Job'ы запускаются со следующим параметром:
WORKSPACE=$WORKSPACE
Это означает, что Job будет запущен в рабочем каталоге Multijob'а.
Таким образом удаётся получить отдельно лог обновления исходных файлов и логи всех этапов сборки по отдельности. Следовать этой идеологии для всех проектов сложно и бессмысленно. Так сделано только для нескольких самых больших и важных проектов, которые необходимо подробно изучать при возникновении проблем.
4. В Multijob'е можно настроить условные и параллельные запуски Job'ов. Multijob'ы умеют запускать Multijob'ы по тем же правилам. Так можно объединять запуски проектов: например, запустить сборку всех инсталляторов или все тесты.
В CCNet просматривать логи сборки проектов было крайне неудобно, т.к. они перемешивались с выводом сервера, и имели особую разметку. В Jenkins такой проблемы нет, и дополнительно появилась возможность разделять логи для подзадач в некоторых проектах.
В Jenkins для каждой добавленной ссылки на SVN репозиторий определяется своя версия ревизии. Т.е. если добавить несколько каталогов, то номера могут сильно отличаться, а нужен один максимальный.
Согласно документации, работать с этим надо следующим образом:
If you have multiple modules checked out, use the svnversion command. If you have multiple modules checked out, you can use the svnversion command to get the revision information, or you can use the SVN_REVISION_<n> environment variables, where <n> is a 1-based index matching the locations configured.
Так и сделали: из всех выставленных значений SVN_REVISION_<n> берётся максимальное и добавляется в собранные программы.
Расширение возможностей с помощью плагинов позволяет максимально гибко настроить сервер. Плагины для запуска тестов в Visual Studio, пожалуй, единственные, которые мы отказались использовать. Они имели дополнительные обязательные параметры запуска, которые мы не использовали, поэтому проще было сделать универсальный Job, просто запускающий тесты из командной строки. К сожалению, "из коробки" Jenkins не умел многое из того, к чему мы привыкли в CCNet. Однако, с помощью плагинов удалось "вернуть" всю требуемую нам функциональность.
Далее приведен список плагинов с небольшим описанием, которыми нам было удобно воспользоваться:
Для получения уведомлений от CCNet мы пользовались клиентом для Windows - CCTray.
Вот какие варианты теперь есть для работы с Jenkins:
1. CCTray — эту программу можно использовать и для сервера с Jenkins. Выглядеть проекты будут примерно, как и раньше (рисунок 5).
Рисунок 5 - Скриншот CCTray
Описание как клиента для Jenkins:
2. CatLight (рисунок 6)
Рисунок 6 - Скриншот CatLight
Описание клиента:
3. Kato (рисунок 7)
Рисунок 7 - Скриншот Kato
Описание клиента:
4. CCMenu — клиент только для Mac с открытым исходным кодом. Для нас не актуален, но может кому-нибудь пригодится.
Использование CI полезно в любом проекте. Для этого есть замечательный бесплатный инструмент Jenkins, который был рассмотрен в статье, а также много других бесплатных и платных CI. Приятно пользоваться развивающимся проектом: для Jenkins и плагинов периодически выходит множество обновлений. Появляются новые решения, как, например, сейчас на главной странице приводится новый проект Blue Ocean, который ещё находится на стадии Beta.
Клиенты для мониторинга проектов Jenkins не очень меня порадовали. Отсутствует много напрашивающихся функций. Возможно, десктопные клиенты не особо востребованы и правильнее пользоваться только web-интерфейсом.
При переезде на новый сервер не получилось использовать Jenkins как службу Windows, т.к. в этом режиме не выполняются UI тесты. Вышли из положения, настроив запуск сервера как консольного приложения со скрытым окном.
Если у вас есть дополнения к изложенному материалу или интересные решения приведённых проблем, то мы будем рады, если вы оставите комментарий к статье или напишите нам через форму обратной связи.