Всем привет. Это продолжение статьи о том, как 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, которые будут отображать полученную статистику в удобочитаемом виде:
  1. PMD Plug-in — генерация отчёта по статистической сложности кода;
  2. SLOCCount Plug-in — генерация отчёта по количеству строк кода;
  3. Test Results Analyzer Plugin — генерация отчёта по результатам тестов;
  4. Cobertura Plugin — генерация отчета по покрытию кода тестами;
  5. DRY Plug-in — генерация отчёта по дублированию кода.

Также установим вспомогательные плагины:
  1. Environment Injector Plugin — внедрение переменных в проект;
  2. Pre SCM BuildStep Plugin — внедрение переменных до начала выполнения job’а;
  3. Build Authorization Token Root Plugin — запуск job’а по get-запросу с токеном;
  4. Parameterized Trigger plugin — позволяет запускать job’ы с параметрами по окончанию сборки;
  5. Slack Notification Plugin — отправка сообщений в командный чат Slack;
  6. 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

Разберём его по составляющим:
  1. server — это внешний адрес вашего сервера. Если необходимо, то здесь также указываем нужный порт (в нашем случае 8080);
  2. buildByToken — возможность, предоставляемая плагином Build Authorization Token Root Plugin. Позволяет запускать job по ссылке с указанием токена (в нашем случае XXXXXXXXXXXXXXXXX);
  3. buildWithParameters — указывает на то, что нужно запустить параметризованную сборку;
  4. JenkinsExecutor — название job’а, который мы создадим и будем использовать для запуска других job’ов. О нём речь пойдет ниже;
  5. XXXXXXXXXXXXXXXXX — значение токена, который устанавливается в настройках плагина в конфигурации каждого отдельного job’а.

В качестве примера будем использовать следующую структуру команды:

/build Example test master

  1. /build — название нашей команды;
  2. Example — название job’a;
  3. test — вспомогательный флаг, связанный с запуском тестов и созданием отчётов с метриками;
  4. 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’ов).
Чтобы получить ключ:
  1. Нажмите на username в правом верхнем углу веб-интерфейса Jenkins;
  2. Нажмите на кнопку «Настроить» в левой части экрана;
  3. Нажмите на 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

Подробную информацию по синтаксису команд ищите на соответствующих страницах документации:
  1. OCLint
  2. Gcovr
  3. CLOC
  4. SLOCount
  5. PMD (CPD)
  6. Xcode Build

4.5. Записанные ранее переменные необходимо внедрить в процесс сборки. Для этого добавляем шаг сборки Inject environment variables и указываем нужный путь:


4.6. Следующий этап — создание сборки и архивирование в .ipa-файл. Для этого воспользуемся плагином Xcode. Делаем по примеру:




4.7. Последний шаг — добавить послесборочные операции.
Мы сгенерировали файлы для пяти отчетов, и теперь нам нужно передать эти файлы соответствующим плагинам:
  1. Publish PMD analysis results = **/reports/oclint.xml
  2. Publish duplicate code analysis results = **/reports/duplicated-code.xml
  3. Publish Cobertura Coverage analysis results = **/reports/coverage.xml
  4. Publish SLOCCount analysis results = в зависимости от используемого модуля:
    1. **/reports/cloc.xml
    2. **/reports/sloccount.sc
  5. 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 можно считать оконченным. Мы надеемся, что наша статья будет для вас полезной, и просим делиться своими вопросами, соображениями и замечаниями в комментариях.
Поделиться с друзьями
-->

Комментарии (3)


  1. eserikov7
    09.06.2016 13:55

    А интеграция с TestFlight будет?


    1. pingwinator
      09.06.2016 14:05

      для этих целей есть deliver


  1. yermulnik
    10.06.2016 18:46

    Отличное руководство. Спасибо!