Переход с CruiseControl.NET на Jenkins в команде разработчиков PVS-Studio
Сейчас трудно представить разработку программного обеспечения без автоматизированных сборок проекта и тестирования. Для минимизации временных затрат на интеграцию изменений разработчиков в проект, существуют разные готовые решения. В данной статье я расскажу о замене сервера непрерывной интеграции 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
- CCNet больше не развивается. Им можно до сих пор пользоваться, но расширять функционал и исправлять существующие/потенциальные ошибки придётся собственными силами.
- Нестабильно работает режим опроса SCM (Source Code Management) об изменениях, а именно для автоматического запуска при наличии изменений в системе контроля версий. При проблемах с сетью, в этом режиме проект получает статус «Failed», даже если не запускался. На практике проблема возникает так часто (к сожалению, в нашем офисе не самый стабильный доступ в интернет), что этим режимом становится невозможно пользоваться.
- При опросе SCM об изменениях, в случае наличия ошибки системы контроля версий (например, если из репозитория был удалён какой-нибудь каталог, прописанный в настройках и возник tree conflict), выполнение проекта сразу прерывается, сохраняя статус «Success» — проект перестаёт работать, но его статус в web интерфейсе и десктоп утилите остаётся «зелёным». В таком режиме запуск, например, тестов, может не выполняться неделями, и есть риск, что никто не обратит на это внимание, думая, что тесты успешно работают.
- Общий лог работы сервера слишком вербозный и неструктурированный: сложно понять, какой шаг сборки отвалился и найти лог именно для этого шага. При работе нескольких проектов параллельно, лог сборки «смешивается». XML лог сборки отдельного проекта доступен в web интерфейсе, но он наоборот зачастую недостаточно подробен и не содержит всех запускаемых команд.
- Неэффективное распараллеливание подзадач внутри проекта. Подзадачи запускаются параллельно группами по количеству ядер процессора. Если в группу попадают длительные и быстрые задачи, то новые задачи запускаться не будут, пока не завершатся все задачи из предыдущего запуска.
Сравнение сценариев использования
Настройки сервера
Настройки проектов CCNet (конфигурация сервера) хранились в одном xml файле, а различные пароли в другом. Хоть файл настроек и достиг размера ~4500 строк, пользоваться им было довольно удобно. Лёгким нажатием Alt+2 в Notepad++ список всех проектов можно свернуть и редактировать нужный (рисунок 1).
Рисунок 1 — Редактирование CCNet настроек в Notepad++
Хоть файл и содержал дублирующийся код, при поддержке сервера особых затруднений не возникало.
Вот так заполнялся блок SCM:
&SVN_USERNAME;
&SVN_PASSWORD;
&SVN_ROOT;...
&PROJECT_ROOT;...
&SVN_FOLDER;
true
true
true
30
Так заполнялся блок MSBuild:
PVS-Studio 2015
&PROJECT_ROOT;...
...\PVS-Studio-vs2015.sln
/p:Configuration=Release
Build
600
&MSBUILD14_PATH;
А так заполнялся блок для общих задач:
PVS-Studio 2015 sign
&PROJECT_ROOT;...\SignToolWrapper.exe
&PROJECT_ROOT;...
"&SIGNTOOL;" ... \PVS-Studio-vs2015.dll"
600
На основе такого проектного файла CCNet потом удобно отображает все выполняемые шаги (правда, только в десктопной tray утилите. Web интерфейс это почему-то не поддерживал). В Jenkins с «высокоуровневым» отображением этапов прохождения интеграционного проекта пришлось повозиться, но об будет рассказано позже.
Для Jenkins необходимо хранить достаточно много файлов настроек: конфиг сервера, файлы настроек некоторых плагинов, каждый проект имеет свой собственный файл конфигурации. Хоть все эти файлы заданы в формате xml, для просмотра и редактирования они не очень удобны (по крайней мере по сравнению с CCNet), т.к. все команды внутри тэгов прописаны сплошным текстом. Правда, в большей степени это связано с идеологией использования инструмента. В CCNet конфиг пишется вручную, и поэтому, может быть «красиво» отформатирован. Jenkins же предполагает редактирование настроек проекта через свой web интерфейс, а конфиги генерирует автоматически.
Примерно так выглядят команды в конфигах Jenkins’а:
CD "%BUILD_FOLDERS%\Builder"
PVS-Studio_setup.exe /VERYSILENT /SUPPRESSMSGBOXES ...
Publisher_setup.exe /VERYSILENT /SUPPRESSMSGBOXES
И это ещё очень маленький пример.
Просмотр статусов задач
Как я писал ранее, в 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_
Так и сделали: из всех выставленных значений SVN_REVISION_
Полезные плагины для Jenkins
Расширение возможностей с помощью плагинов позволяет максимально гибко настроить сервер. Плагины для запуска тестов в Visual Studio, пожалуй, единственные, которые мы отказались использовать. Они имели дополнительные обязательные параметры запуска, которые мы не использовали, поэтому проще было сделать универсальный Job, просто запускающий тесты из командной строки. К сожалению, «из коробки» Jenkins не умел многое из того, к чему мы привыкли в CCNet. Однако, с помощью плагинов удалось «вернуть» всю требуемую нам функциональность.
Далее приведен список плагинов с небольшим описанием, которыми нам было удобно воспользоваться:
- Multijob plugin — позволяет использовать в качестве этапов сборки другие Job’ы с возможностью последовательного и параллельного выполнения.
- Environment Injector Plugin — с помощью этого плагина можно задавать глобальные пароли. Используются они как переменные окружения, при этом плагин скрывает значения такой переменной в логе.
- Pre SCM BuildStep Plugin — добавление дополнительных шагов перед выполнением команд системы контроля версий.
- MSBuild Plugin — удобный плагин для сборки проектов с помощью MSBuild. В настройках один раз указываются пути к разным версиям MSBuild. Далее в проекте можно легко добавлять шаги сборки.
- Parameterized Trigger plugin — добавляет параметры запуска проектов. Можно, например, сделать выбор trunk/stable ветки для сборки дистрибутива.
- Post-Build Script Plug-in — выполнение дополнительных этапов после сборки.
- Throttle Concurrent Builds Plug-in — данный плагин позволяет регулировать количество параллельно запущенных сборок проекта глобально или в рамках заданной категории. Он позволяет получить в Jenkins функциональность, подобную очередям в CCNet — возможность выполнять параллельно несколько проектов из разных категорий (очередей), при этом обеспечивая последовательное выполнение проектов в рамках одной очереди. Например, у нас есть очереди Installers (дистрибутивы) и Tests (тесты). Мы хотим иметь возможность при работе тестов параллельно собирать какой-нибудь дистрибутив, но при этом тесты параллельно работать не должны — не хватит «ядер» на сервере.
- Build Name Setter Plugin — позволил задать имя сборки в нужном нам формате: Major.Minor.Revision.Build.
- Dashboard View — позволяет добавить своё отображение Job’ов в браузере. Т.к. у нас есть универсальные Job’ы, которые нет смысла запускать вручную, то мы создали список без них с помощью этого плагина.
- ZenTimestamp Plugin — удобный плагин, добавляющий временные метки в логах сборки.
Обзор десктопных клиентов
Для получения уведомлений от CCNet мы пользовались клиентом для Windows — CCTray.
Вот какие варианты теперь есть для работы с Jenkins:
1. CCTray — эту программу можно использовать и для сервера с Jenkins. Выглядеть проекты будут примерно, как и раньше (рисунок 5).
Рисунок 5 — Скриншот CCTray
Описание как клиента для Jenkins:
- Не развивается, как и CCNet;
- Не умеет показывать подзадачи (работает только для CCNet);
- Не умеет запускать проекты;
- По нажатию на название можно перейти на страницу проекта;
- Настраивается вид отображения проектов (Icons, List, Details);
- С открытым исходным кодом.
2. CatLight (рисунок 6)
Рисунок 6 — Скриншот CatLight
Описание клиента:
- На данный момент Beta версия, финальная версия станет платной;
- Ещё есть падения при установке, работе и глюки в интерфейсе;
- При выводе компьютера из гибернации статус проектов на dashboard’е не обновляется автоматически;
- Не умеет показывать подзадачи для Multijob’ов;
- Не умеет запускать проекты;
- Не настраивается вид отображения проектов (единственный возможный вид — рисунок 6);
- По нажатию на название можно перейти на страницу проекта;
- Можно видеть статус последних 5-и запусков и переходить к ним;
- Можно видеть прогресс запущенного проекта;
- При добавлении нескольких серверов они удобно отделены чертой;
- Есть для Windows, Linux и Mac.
3. Kato (рисунок 7)
Рисунок 7 — Скриншот Kato
Описание клиента:
- Не умеет показывать подзадачи для Multijob’ов;
- Умеет запускать проекты. К сожалению, не поддерживает проекты с параметризованным запуском — утилита «падает» при попытке запустить такой проект;
- Проекты с разных серверов отображаются в одном списке и неразличимы (не всегда удобно);
- Настраивается вид отображения проектов (List, Grid);
- По нажатию на название можно перейти на страницу проекта;
- Можно просматривать последний лог прямо в клиенте, но из-за отсутствия моноширинного текста это не очень удобно;
- С открытым исходным кодом;
- Только для Windows.
4. CCMenu — клиент только для Mac с открытым исходным кодом. Для нас не актуален, но может кому-нибудь пригодится.
Заключение
Использование CI полезно в любом проекте. Для этого есть замечательный бесплатный инструмент Jenkins, который был рассмотрен в статье, а также много других бесплатных и платных CI. Приятно пользоваться развивающимся проектом: для Jenkins и плагинов периодически выходит множество обновлений. Появляются новые решения, как, например, сейчас на главной странице приводится новый проект Blue Ocean, который ещё находится на стадии Beta.
Клиенты для мониторинга проектов Jenkins не очень меня порадовали. Отсутствует много напрашивающихся функций. Возможно, десктопные клиенты не особо востребованы и правильнее пользоваться только web-интерфейсом.
При переезде на новый сервер не получилось использовать Jenkins как службу Windows, т.к. в этом режиме не выполняются UI тесты. Вышли из положения, настроив запуск сервера как консольного приложения со скрытым окном.
Если у вас есть дополнения к изложенному материалу или интересные решения приведённых проблем, то мы будем рады, если вы оставите комментарий к статье или напишите нам через форму обратной связи.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Svyatoslav Razmyslov. Moving from CruiseControl.NET to Jenkins in the PVS-Studio development team