Всем привет. Это продолжение статьи о том, как iOS-отдел компании Лайв Тайпинг внедрил методологию CI и развернул сервер для автоматизации сборок на Jenkins. Как мы и обещали, вторая часть посвящена тому, как получить основные метрики кода, заархивировать проект в .ipa и настроить взаимодействие со Slack.
1.Установка всех необходимых программ и плагинов.
Для начала установим программы, которые будут собирать для нас статистику:
#Определение степени покрытие кода тестами
brew install gcovr
#Счётчик строк кода
brew install cloc
#Счётчик строк кода, альтернативный вариант
brew install sloccount
#Поиск дублирования кода
brew install pmd
#Генерация отчётов о результатах тестов (также генерирует данные для oclint)
sudo gem install xcpretty
#Статический анализ кода
brew tap oclint/formulae
brew install oclint
Далее нам нужно установить плагины для Jenkins, которые будут отображать полученную статистику в удобочитаемом виде:
- PMD Plug-in — генерация отчёта по статистической сложности кода;
- SLOCCount Plug-in — генерация отчёта по количеству строк кода;
- Test Results Analyzer Plugin — генерация отчёта по результатам тестов;
- Cobertura Plugin — генерация отчета по покрытию кода тестами;
- DRY Plug-in — генерация отчёта по дублированию кода.
Также установим вспомогательные плагины:
- Environment Injector Plugin — внедрение переменных в проект;
- Pre SCM BuildStep Plugin — внедрение переменных до начала выполнения job’а;
- Build Authorization Token Root Plugin — запуск job’а по get-запросу с токеном;
- Parameterized Trigger plugin — позволяет запускать job’ы с параметрами по окончанию сборки;
- Slack Notification Plugin — отправка сообщений в командный чат Slack;
- Publish Over SSH — этот плагин указан здесь в качестве примера. Он подойдет вам, если вы, как и мы, отправляете данные через SFTP на сервер.
2. Интеграция с чатом Slack
Для получения уведомлений о состоянии сборки в командном чате Slack, нам необходимо добавить соответствующую интеграцию с Jenkins в настройках Slack’а. Это можно сделать здесь.
После создания интеграции будет сгенерирован уникальный токен, который необходимо добавить в настройки Jenkins’а (либо в настройки отдельного job’а) — как в примере на скриншоте:
Далее настроим запуск сборок с помощью встроенного механизма команд в Slack’е. Для начала нам необходимо добавить интеграцию. Для этого пройём в подраздел Slash commands в разделе Custom Integrations и нажмём на кнопку Add configurations. Эту операцию можно выполнить здесь.
При настройке вам нужно указать название вашей команды, выбрать метод передачи данных POST и указать URL-адрес, на который будет идти запрос.
Рассмотрим пример формирования URL для запроса подробнее. Наш URL для примера выглядит так:
http://server:8080/buildByToken/buildWithParameters?job=JenkinsExecutor&token=XXXXXXXXXXXXXXXXX
Разберём его по составляющим:
- server — это внешний адрес вашего сервера. Если необходимо, то здесь также указываем нужный порт (в нашем случае 8080);
- buildByToken — возможность, предоставляемая плагином Build Authorization Token Root Plugin. Позволяет запускать job по ссылке с указанием токена (в нашем случае XXXXXXXXXXXXXXXXX);
- buildWithParameters — указывает на то, что нужно запустить параметризованную сборку;
- JenkinsExecutor — название job’а, который мы создадим и будем использовать для запуска других job’ов. О нём речь пойдет ниже;
- XXXXXXXXXXXXXXXXX — значение токена, который устанавливается в настройках плагина в конфигурации каждого отдельного job’а.
В качестве примера будем использовать следующую структуру команды:
/build Example test master
- /build — название нашей команды;
- Example — название job’a;
- test — вспомогательный флаг, связанный с запуском тестов и созданием отчётов с метриками;
- master — ветка для сборки.
Рассмотренная конфигурация позволит нам запускать сборку любого проекта с указанием нужной ветки, при этом будет использоваться единая команда: /build.
3. Конфигурация вспомогательного job’a — JenkinsExecutor
Данный job будет нужен нам для того, чтобы запускать другие job’ы. В нём также можно будет обрабатывать ошибки, если пользователь ввёл не существующий проект, и добавить информацию о команде (своеобразный help).
Заходим на сервер и создаём новую задачу со свободной конфигурацией и названием JenkinsExecutor. Далее в настройках job’а выставляем флаг, указывающий на то, что сборка является параметризированной и принимает параметр text. При запуске команды в Slack’е все данные (Example master test) будут передаваться единой строкой в переменной text.
Далее устанавливаем флаг, отвечающий за запуск сборки удалённо. Здесь нужно указать токен, идентичный тому, который мы установили в настройках команды в Slack’е:
Теперь нам необходимо извлечь значения из переменной text. Для этого переходим в раздел «Сборка» и добавляем шаг сборки «выполнить команду shell». Пример команды:
#Создаём массив из элементов строки, разделённых пробелом
IFS=' ' read -a array <<< "$text"
#Согласно нашему примеру, первое значение — это название проекта
JOB_NAME=${array[0]}
#Флаг, ответственный за тесты
TEST=${array[1]}
#Название ветки проекта
BRANCH=${array[2]}
#Если необходимо, можно также получить другие значения:
USER_NAME=${user_name}
CHANNEL_NAME=${channel_name}
Для запуска сборки с параметрами отправим POST-запрос на исполнение конкретного job’а. Для этого к предыдущей shell-команде добавляем следующую строчку:
curl -d TEST=${TEST} -d BRANCH=${BRANCH} -X POST
-u username:password http://127.0.0.1:8080/job/${JOB_NAME}/buildWithParameters
Здесь password — это API key пользователя username (пользователь должен иметь права на запуск job’ов).
Чтобы получить ключ:
- Нажмите на username в правом верхнем углу веб-интерфейса Jenkins;
- Нажмите на кнопку «Настроить» в левой части экрана;
- Нажмите на Show API Key — искомый ключ у нас.
Обратите внимание, что все запускаемые сборки должны быть параметризированными!
4. Настройка сборки
4.1. Первое, на что стоит обратить внимание при настройки job’а — это то, что сборка должна быть параметризированной. Для этого выставляем соответствующий флаг, добавляем текстовые параметры BRANCH и TEST и задаём им параметры по умолчанию:
Здесь стоит отметить, что для переменной BRANCH нужно дополнительно добавить значение по умолчанию. Дело в том, что если вы запустите сборку из Slack без указания ветки, то в переменной BRANCH будет пустое значение и соответственно будет ошибка. Для этого мы добавим флаг Run buildstep before SCM runs в разделе «Среда сборки». Затем добавим шаг «выполнить команду shell» и шаг inject environment variables. Делаем по примеру:
4.2. Настраиваем взаимодействие с GitLab.
Указываем адрес репозитория проекта. Указываем ветку сборки (в нашем случае это переменная BRANCH).
4.3. Настраиваем сборку по веб-хуку.
В триггерах сборки устанавливаем флаг «Сборка по пушу в GitLab». Добавляем нужные параметры и указываем ветку, для которой будет срабатывать триггер:
Затем в настройках проекта на GitLab в категории Web hooks добавляем веб-хук на сервер Jenkins’а:
4.4. Этап сборки начинается с выполнения shell-команды, которая устанавливает Pod’ы, если файл был обновлён:
if [ $(( $(date +"%s") - $(stat -f %m Podfile) )) -le 60 ]; then
pod install
fi
Затем для удобства установим некоторые переменные для проекта и запишем их в файл:
#Название .ipa-файла
PROJECT_NAME="Example"
#Название файла .xcworkspace
WORKSPACE_NAME="Example"
#Название исполняемой схемы
SCHEME_NAME="Example"
#Название папки с исходниками. Будет использоваться для подсчёта количества строк кода
FOLDER_NAME_WITH_CODE="Example"
#Записываем переменные в файл, чтобы использовать в других этапах сборки
echo PROJECT_NAME=$PROJECT_NAME > build.properties
echo WORKSPACE_NAME=$WORKSPACE_NAME >> build.properties
echo SCHEME_NAME=$SCHEME_NAME >> build.properties
В зависимости от установленного параметра TEST запускаем или пропускаем этап тестирования и генерацию отчётов. Пример того, как это может выглядеть:
if [ "$TEST" == "test" ]; then
#Создание папки reports, в которую мы будем складывать отчёты
if [ ! -d "reports" ]; then
mkdir "reports"
fi
#Тестирование и создание отчётов для анализа
xcodebuild -workspace ${WORKSPACE_NAME}.xcworkspace
-scheme ${SCHEME_NAME}
-configuration Debug
-sdk iphonesimulator
-destination 'platform=iOS Simulator,name=iPhone 6'
-IDECustomDerivedDataLocation="build_ccov"
GCC_GENERATE_TEST_COVERAGE_FILES=YES
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES
clean test | xcpretty -r junit -o reports/junit.xml -r json-compilation-database -o compile_commands.json
#Publish JUNIT test = **/reports/junit.xml
#Анализ синтаксической сложности кода
oclint-json-compilation-database -v -e Pods --
-rc=LONG_LINE=200
-rc=NCSS_METHOD=60
-rc=LONG_METHOD=100
-rc=MINIMUM_CASES_IN_SWITCH=1
-report-type pmd
-o reports/oclint.xml
-max-priority-1 1000
-max-priority-2 1000
-max-priority-3 1000
#Publish PMD analysis = **/reports/oclint.xml
#Анализ покрытия кода тестами
gcovr --object-directory="build_ccov/${SCHEME_NAME}/Build/Intermediates/${SCHEME_NAME}.build/"
"Debug-iphonesimulator/${SCHEME_NAME}.build/Objects-normal/x86_64/"
--xml
--print-summary
--exclude '.*Tests.*'
--exclude '.*Libs.*'
--exclude '.*ExternalFrameworks.*'
--exclude '.*Platforms.*'
--output=reports/coverage.xml
#Publish Cobertura Coverage = **/reports/coverage.xml
#Подсчёт строк кода (два варианта):
cloc ${WORKSPACE}/${FOLDER_NAME_WITH_CODE} -by-file -skip-uniqueness -xml -out=${WORKSPACE}/reports/cloc.xml
#Publish SLOCCount analysis = **/reports/cloc.xml
sloccount --duplicates --wide --details ${WORKSPACE}/${FOLDER_NAME_WITH_CODE} -v > reports/sloccount.sc
#Publish SLOCCount analysis = **/reports/sloccount.sc
#Анализ дублирования кода
pmd cpd --files ${WORKSPACE}/${FOLDER_NAME_WITH_CODE}
--minimum-tokens 10 --language objectivec
--encoding UTF-8
--format net.sourceforge.pmd.cpd.XMLRenderer | iconv -f macRoman -t utf-8 | sed 's/MacRoman/UTF-8/g' > reports/duplicated-code.xml
#Publish duplicate code = **/reports/duplicated-code.xml
else
touch reports/junit.xml
#Данная строчка нужна, чтобы избежать провала при сборке из-за генерации отчета плагином Publish JUNIT test result report
fi
Подробную информацию по синтаксису команд ищите на соответствующих страницах документации:
4.5. Записанные ранее переменные необходимо внедрить в процесс сборки. Для этого добавляем шаг сборки Inject environment variables и указываем нужный путь:
4.6. Следующий этап — создание сборки и архивирование в .ipa-файл. Для этого воспользуемся плагином Xcode. Делаем по примеру:
4.7. Последний шаг — добавить послесборочные операции.
Мы сгенерировали файлы для пяти отчетов, и теперь нам нужно передать эти файлы соответствующим плагинам:
- Publish PMD analysis results = **/reports/oclint.xml
- Publish duplicate code analysis results = **/reports/duplicated-code.xml
- Publish Cobertura Coverage analysis results = **/reports/coverage.xml
- Publish SLOCCount analysis results = в зависимости от используемого модуля:
- **/reports/cloc.xml
- **/reports/sloccount.sc
- Publish JUNIT test result report = **/reports/junit.xml (Примечание: В расширенных настройках плагина нужно установить флаг Do not fail the build on empty test results. Это поможет избежать fail-статуса для сборки, если она была запущена без запуска тестов)
На этом этапе мы можем отправить полученный в случае успеха .ipa-файл туда, куда нам нужно (на сервер, по e-mail и т.д.). Если вы хотите отправить файлы на сервер по SFTP и вы используете плагин Publish Over SSH, то нужно перейти в раздел «Среда сборки», установить флаг для Send files or execute commands over SSH after the build runs и настроить плагин в соответствии с вашими требованиями.
Последний шаг, который нам нужно добавить — Slack Notification, который, как вы догадались, отправляет уведомления в Slack. В расширенных настройках плагина можно указать индивидуальные настройки для текущего job’а. Стоит заметить, что в качестве сообщения можно указать переменную (пример: $MESSAGE), значение которой менять на разных этапах сборки и тем самым отправлять более информативные сообщения в Slack.
На этом внедрение CI можно считать оконченным. Мы надеемся, что наша статья будет для вас полезной, и просим делиться своими вопросами, соображениями и замечаниями в комментариях.
Автор: Лайв Тайпинг