How to raise CI for iOS developers in a day. Part 2

  • Tutorial
Hello. This is a continuation of the article about how the Live Typing iOS department implemented the CI methodology and deployed a server to automate assemblies on Jenkins. As we promised, the second part is devoted to how to get the basic metrics of the code, archive the project in .ipa and configure interaction with Slack.

1. Installation of all necessary programs and plugins.


First, install the programs that will collect statistics for us:

#Определение степени покрытие кода тестами
brew install gcovr
#Счётчик строк кода
brew install cloc
#Счётчик строк кода, альтернативный вариант
brew install sloccount
#Поиск дублирования кода
brew install pmd
#Генерация отчётов о результатах тестов (также генерирует данные для oclint)
sudo gem install xcpretty
#Статический анализ кода
brew tap oclint/formulae
brew install oclint

Next, we need to install plugins for Jenkins, which will display the received statistics in a human-readable form:
  1. PMD Plug-in - report generation on statistical code complexity;
  2. SLOCCount Plug-in - report generation by the number of lines of code;
  3. Test Results Analyzer Plugin - report generation according to test results;
  4. Cobertura Plugin - report generation on code coverage with tests;
  5. DRY Plug-in - generating a code duplication report.

We will also install auxiliary plugins:
  1. Environment Injector Plugin - implementation of variables in the project;
  2. Pre SCM BuildStep Plugin - implementation of variables before job execution;
  3. Build Authorization Token Root Plugin - start a job on a get request with a token;
  4. Parameterized Trigger plugin - allows you to run jobs with parameters at the end of the assembly;
  5. Slack Notification Plugin - sending messages to the Slack team chat;
  6. Publish Over SSH - this plugin is listed here as an example. It will suit you if you, like us, send data via SFTP to the server.

2. Integration with Slack chat


To receive notifications about the build status in the Slack team chat, we need to add the appropriate integration with Jenkins in the Slack settings. It can be done here .
After creating the integration, a unique token will be generated, which must be added to Jenkins’s settings (or to the settings of a separate job) - as in the example in the screenshot:



Next, configure the launch of assemblies using the built-in command mechanism in Slack. First we need to add integration. To do this, go to the Slash commands subsection in the Custom Integrations section and click on the Add configurations button. This operation can be performed here .
When setting up, you need to specify the name of your team, select the POST data transfer method and specify the URL to which the request will go.



Consider an example of generating a URL for a request in more detail. Our example URL looks like this: Let's parse it into its components:

http://server:8080/buildByToken/buildWithParameters?job=JenkinsExecutor&token=XXXXXXXXXXXXXXXXX


  1. server is the external address of your server. If necessary, then here we also indicate the desired port (in our case, 8080);
  2. buildByToken is a feature provided by the Build Authorization Token Root Plugin plugin. Allows you to run a job by reference with a token (in our case, XXXXXXXXXXXXXXXXX);
  3. buildWithParameters - indicates that you need to start a parameterized assembly;
  4. JenkinsExecutor - the name of the job, which we will create and use to run other jobs. We will discuss it below;
  5. XXXXXXXXXXXXXXXXXX - the value of the token, which is set in the plugin settings in the configuration of each individual job.

As an example, we will use the following command structure:

/build Example test master

  1. / build - the name of our team;
  2. Example - name of job'a;
  3. test - an auxiliary flag related to running tests and creating reports with metrics;
  4. master - branch for assembly.

The considered configuration will allow us to start building any project with an indication of the desired branch, and a single command will be used: / build.

3. Auxiliary job configuration - JenkinsExecutor


We will need this job in order to run other jobs. It will also be possible to handle errors in it if the user has entered a non-existing project, and add information about the command (a kind of help).
We go to the server and create a new task with a free configuration and the name JenkinsExecutor. Next, in the job settings, we set a flag indicating that the assembly is parameterized and accepts the text parameter. When the command is launched in Slack, all data (Example master test) will be transmitted as a single line in the text variable.



Next, set the flag responsible for starting the assembly remotely. Here you need to specify a token that is identical to the one we set in the command settings in Slack:



Now we need to extract the values ​​from the text variable. To do this, go to the "Assembly" section and add the assembly step "execute the shell command". Command example:

#Создаём массив из элементов строки, разделённых пробелом
IFS=' ' read -a array <<< "$text"
#Согласно нашему примеру, первое значение — это название проекта
JOB_NAME=${array[0]}
#Флаг, ответственный за тесты
TEST=${array[1]}
#Название ветки проекта
BRANCH=${array[2]}
#Если необходимо, можно также получить другие значения:
USER_NAME=${user_name}
CHANNEL_NAME=${channel_name}

To start the assembly with the parameters, we will send a POST request to execute a specific job. To do this, add the following line to the previous shell command:

curl -d TEST=${TEST} -d BRANCH=${BRANCH} -X POST \
-u username:password http://127.0.0.1:8080/job/${JOB_NAME}/buildWithParameters

Here password is the API key of the username user (the user must have permission to run jobs).
To get the key:
  1. Click on username in the upper right corner of the Jenkins web interface;
  2. Click on the “Configure” button on the left side of the screen;
  3. Click on Show API Key - the desired key is with us.

Please note that all running assemblies must be parameterized!

4. Build setup


4.1. The first thing you should pay attention to when setting up a job is that the assembly must be parameterized. To do this, set the appropriate flag, add the text parameters BRANCH and TEST and set the default parameters for them:


It is worth noting that for the BRANCH variable, you need to add an additional default value. The fact is that if you run the assembly from Slack without specifying a branch, then the BRANCH variable will have an empty value and accordingly there will be an error. To do this, we will add the Run buildstep before SCM runs flag in the Build Environment section. Then we add the step “execute the shell command” and the step inject environment variables. We do as an example:



4.2. Configuring interaction with GitLab.
Specify the address of the project repository. We specify an assembly branch (in our case it is a BRANCH variable).



4.3. Set up the web hook assembly.
In the assembly triggers, set the flag “Push assembly in GitLab”. Add the necessary parameters and specify the branch for which the trigger will fire:



Then in the project settings on GitLab in the category Web hooks add a web hook to Jenkins server:



4.4. The build phase begins with the execution of a shell command that installs Pods if the file has been updated:

if [ $(( $(date +"%s") - $(stat -f %m Podfile) )) -le 60 ]; then
    pod install
fi

Then, for convenience, set some variables for the project and write them to a file:

#Название .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


Depending on the TEST parameter set, we start or skip the testing phase and report generation. An example of how this might look:

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

For more information on command syntax, see the corresponding documentation pages:
  1. Oclint
  2. Gcovr
  3. CLOC
  4. SLOCount
  5. PMD (CPD)
  6. Xcode build

4.5. Previously written variables must be embedded in the build process. To do this, add the Inject environment variables assembly step and specify the desired path:


4.6. The next step is to create the assembly and archive it into an .ipa file. To do this, use the Xcode plugin. We do as an example:




4.7. The final step is to add post-assembly operations.
We generated files for five reports, and now we need to transfer these files to the appropriate plugins:
  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 = depending on the module used:
    1. ** / reports / cloc.xml
    2. ** / reports / sloccount.sc
  5. Publish JUNIT test result report = ** / reports / junit.xml ( Note : In the advanced settings of the plugin you need to set the Do not fail the build on empty test results flag. This will help to avoid the fail-status for the assembly if it was run without running the tests )

At this stage, we can send the received .ipa-file to the place we need (to the server, by e-mail, etc.). If you want to send files to the server via SFTP and you use the Publish Over SSH plugin, you need to go to the "Build Environment" section, set the flag for Send files or execute commands over SSH after the build runs and configure the plugin according to your requirements.

The last step we need to add is Slack Notification, which, you guessed it, sends notifications to Slack. In the advanced settings of the plugin, you can specify individual settings for the current job. It is worth noting that as a message you can specify a variable (example: $ MESSAGE), the value of which can be changed at different stages of the assembly and thereby send more informative messages to Slack.

At this, the implementation of CI can be considered complete. We hope that our article will be useful for you, and ask you to share your questions, thoughts and comments in the comments.

Also popular now: