Sunday 3 December 2017

One stop solution for setting up CI and CD system for iOS applications using Jenkins and Xcode - A complete reference guide (Part 2)

Hi,

This is a continuation of One stop solution for setting up CI and CD system for iOS applications using Jenkins and Xcode - A complete reference guide (Part 1). If in case you haven't read it, please consider doing so before further reading this tutorial.

If you have followed the Part 1 properly, by now you must have Jenkins system installed on your Mac machine which is completely configured to work with your manually signed Xcode project. You have your SSH connection established and your Jenkins constantly polls your Github/Bitbucket branch every 5 minutes to check if there is a change in code base.

If you have followed the Part 1 and managed to set up Jenkins, pat your self on the shoulder! Believe me you did a great job. That was the hardest part of your journey. From here onwards life will be more easy!

Though you have your Jenkins completely set up its not doing much as there are no build jobs added yet. We will do that in this part.

What will be accomplished by the end of this part ?
  • Add a job to build the code and run the unit test against the code.
  • Generate a test report and represent test results in colorful graphs.
  • If even a single unit test fails sending a email to developer with attached build log
  • Else archiving and exporting the ipa 
  • Finally uploading ipa to iTunesConnect
We will do all the above mentioned steps using Xcode command line tools. So buckle up and get ready for a smooth ride with Jenkins.

Step 1: Fire up your Jenkins and add a build job to run the unit test.
To do it, click on the item you added for your project in Part1 from the Jenkins dashboard.


Click on configure link



Once done you should be able to see the same setting page you were at the end of Part 1. Now scroll to Build section and click on add Build step. In the drop down menu select Execute Shell


Now go ahead and paste the following Xcodebuild command to build all the test target.
Now thats great, but dont you wanna generate test reports and see a colorful graphs representing test results? For that we will use Junit plugin. Modify your Xcode command as follow.
If you are a person with sharp eye, you must have noticed the usage of xcpretty. This is needed to format the test report generated by Xcodebuild to a form understandable by Junit.

Before you move on and run the build job, make sure you have xcpretty installed on your Mac. To do that open up your terminal and type xcpretty. If you see the help page your system has xcpretty installed. On the other hand if you receive the error command not found go ahead and install xcpretty.

Finally if you have noticed the command properly, it is creating a test report named reports.xml  which is placed in test-reports folder. These paths are relative to your project workspace. So obviously Jenkins will try to search for a folder named test-reports in your workspace and if it cant find it will fail the build step.

So go ahead and create folder named test-reports in your project workspace. Its often seen that Xcode will ignore the empty folder and hence its advised to create a empty file lets say test.txt in test-reports folder. Once done commit the project settings to your remote branch. By the end of it your remote branch should have a folder named test-reports. Cross verify it before proceeding further.

Great! Lets add one last step before running Jenkins for the very first time. Scroll down to Post Build Actions, click on Add Post Build Action and select Publish Junit Test Result Report.


This will read the reports.xml generated by Junit plugin and represent the report in colorful graphical charts. In order to do that, this action needs to know where exactly reports.xml is located. We know that Junit will produce the reports.xml in test-reports folder. So go ahead and provide reports.xml file path.


In your case on adding test-reports/*.xml you might see an error stating test-reports exists but *.xml does not exist. Thats ok because you have not run the Jenkins build before its likely that your test-reports folder does not contain any xml files.

But if Jenkins complains about test-reports folder being non-existent, thats a flag! Make sure your branch from which Jenkins pulls the code has a folder named test-reports. On failing to find such folder or file this action will mark the whole build as Failure. 

Finally absence of Unit test can not be treated as failure in running the unit test, hence don't forget to check the Do not fail the build on empty test results.

Thats it, now save the changes that should take you back to dashboard. Now click onBuild with Parameters and keep your fingers crossed.



If everything goes fine, you should see blue dot next to your build number in Build History. Now go ahead and click on drop down arrow next to build and select the console log to see the console logs.


In order to see the test reports, you need to run the build at least twice.

Step 2: Troubleshooting the simulator issue with xcodebuild.

Problem 1: SimDevice: iPhone X (AA063B5F-09D5-4908-A670-A4CE9236CC47, iOS 11.1 could not boot or Failed to boot iPhoneX simulator
If in case you open the console log and you happened to see the error that SimDevice: iPhone X (AA063B5F-09D5-4908-A670-A4CE9236CC47, iOS 11.1 could not boot or Failed to boot iPhoneX simulator best workaround is to run the code on Xcode manually and launch the simulator, once iPhone simulator is launched, reset the simulator. Now go ahead and kill the Xcode but leave the simulator alive. Head back to Jenkins and run the build again. Issue should be resolved.

Problem 2: Could not launch simulator because of duplicate simulator availability.
I have faced this issue only once, but I have not managed to reproduce the issue ever since. So I could  not post the exact error here. But I remember error says that Xcodebuild could not load the simulator because there are duplicate simulators available and Xcodebuild isn't sure of which one to launch. In that case modify your xcodebuild command in Build step to


All that we do here is rather than specifying the simulator by using its name and iOS version of it, you specify the UDID of the simulator itself. There by resolving all the confusions.

Step 3: Lets stop the build process even if a single unit test fails.
Unfortunately, even if all the unit test fails, the build step will still be considered as success as long as it completes the execution without any exception or error. But we would not want our build to upload to iTunesConnect even if a single unit test fails isn't it? Because there is no inherent support for that we have to deal with it on our own.

In order to stop the build even if a single unit test fails, we need a conditional build step. In order to get one go to Manage Jenkins and then select Manage Plugins. Select the available tab, and search for pipeline in search field. From the results that appear, check the Build Pipeline Plugin 



and click on install without restart. Once done with installation return to your project to continue configuration. 

Scroll to Build and click on Add Build step, this time you should see additional options namely Conditional Step (single) and Conditional Step (Multiple). Click on Conditional Step single.
Once added, select the drop down next to Run? and select Execute Shell


Now we should write a shell script that will conditionally decide whether to continue the with next jobs or to terminate the current job. We know that by the time we reach here our test reports are already generated and is kept in test-reports folder under project workspace. So we will now write a simple shell script which will parse this report.xml and look for a specific keyword like failure if found will call exit 1 on the thread there by stopping further builds from running, else it will let other jobs to continue execute. Sounds interesting? 

Lets go ahead and create a file named test.sh and lets keep it in Jenkins Document folder.
and enter the code below in test.sh

In order for Jenkins to execute test.sh it should have executable permission. But this is how the permission of a typical .sh file looks like

-rw-r--r-- 1 jenkins jenkins 162 Dec 3 23:51 /Users/Shared/Jenkins/Documents/test.sh

In order to provide executable permission, use chmod


Now if you look at the permission of test.sh it should look like

-rwxr--r-- 1 jenkins jenkins 162 Dec 3 23:51 /Users/Shared/Jenkins/Documents/test.sh

Clearly, Now you have executable permission to test.sh.

Now lets get back and add the command to execute test.sh in conditional build step we added just now. 

After adding the above command your Build section should look like



Now by default when a shell script fails the build will be considered as failed. But we rather all we want to do is to stop the further jobs being executed and denote the build as unstable. In order to do that click on Advanced button next to command field and select Don't run  for on evaluation failure.

Step 4: Archive and export the ipa using xcodebuild command
Select the Execute Shell for Builder option and add the following command to achieve and export the ipa using Xcode command line tools.

To create a archive use,

Parameter to be considered here is -archivePath. Obviously I have created a folder named build inside a document directory where I'll be saving the archive. You can decide where would you like to save it. If you are going to use the same command then make sure you create a folder named build inside Jenkins document directory. We will be using the path of the created archive in next command.

To export the ipa from created archive use,

As you can see the path provided to -archivePath argument here is exactly the same path you specified in previous command to create the archive. Second most important parameter here is the -exportPath as you can see for demo purpose am dumping the exported ipa on my desktop. You can decide to place it wherever you want it. Remember we will be using the path to the created .ipa in our next command to upload the build to iTunesConnect.

Wait a second! Xcode can't make the life of a developer so easy, there must be some thing fishy here! You are absolutely right. Though above commands executes absolutely fine on terminal when added to Jenkins will fail.

But why, Xcode why? Reason, both the commands try to access the distribution certificate to code sign the build and obviously Mac OSX restricts the access to Keychain so xcodebuild command fails miserably. Whats the work around then ?? Simple, allow the code sign to access your distribution certificate and private key.

To check, if code sign has access to your distribution certificate or not, go to Keychain -> login -> Certificates. Now click on the drop down arrow to expand your distribution certificate. Highlight the private key, and finally right click and select get info



When the pop appears, select the Access Control.  If your Access Control panel looks like 


That means code sign does not have an access to your distribution certificate and its private key and Jenkins will never be able to archive your code. In order to add code sign, you can either click on + below and add code sign if you know its path, or else simply open terminal, go to your local repo of the code and run the archive command

When you run it for the first time in terminal, OS will pop up authentication dialog asking you to enter the password for login keychain, after entering the password click on Always Allow. Thats it now if you go back and check the access control of your distribution certificate you must see code sign.


If in case you don't see code sign, make sure you kill the keychain and restart it. There seems to be some more bug with Keychain UI. Hah! Thats apple anyway.

Lets combine these archive and export ipa commands into a single command and add it as the command for Builder 
Go ahead and paste it in your Builder command field. After pasting the command, conditional step should look like


Now click on save. Return to Jenkins dashboard and click on Build with Parameters. If you have done everything right, your job should run successfully and should create a archive file in build folder and ipa file on desktop.

Step 5: Upload the ipa to iTunesConnect.
Make sure you have added the product in your iTunes Connect before proceeding further. Now that we have our ipa exported, all we need to do is to upload it to iTunesConnect using altool of Xcode.
In order to do it, scroll down to Build section and click on Add a Build Step and select Execute shell.

We will be using altool to upload the build to iTunesConnect. altool is usually present at

/Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool

Simply copy and paste the above path in your terminal. If you see help page appearing, then you have altool available with you. Go ahead and add the below command to command field of Execute Shell

After adding the command it should look like


Few important things in the command above, --upload-app -f takes the file path of ipa as parameter. Because I have dumped my ipa at desktop for demo, I have given the link for the same. -u takes your iTunesConnect email id as argument and -p takes your iTunesConnect password as parameter.

Now click save and return to Jenkins Dashboard and click on Build with parameter. If everything goes fine you should be able to see that ipa has been uploaded to iTunesConnect. Check the console log for more info about the status of ipa upload.

In case you have any issue with uploading ipa using altool make sure you upload the build at least once manually using Application Loader to iTunesConnect. Try running the job again after that and everything should be resolved :)

Step 6: Send Email.
To send email from Jenkins, you need to configure Jenkins system. In order to do that, Click on Jenkins -> Manage Jenkins ->Configure System. Now scroll down to Extended email notification section. 

Specify the smtp server as smtp.gmail.com. Click on Advanced button. Check use SMTP authentication. Enter the email id from which email should be sent out as Username and its password in password field.

Check use SSL and enter the SMTP port as 465. Enter a comma separated email ids like 
abc@gmail.com,cc:bcd@gmail.combcc:xyz@gmail.com
to specify the Default Recipients. Click Save. After the configuration your extended email notification should look like 


Finally, scroll further down to E-mail Notification, Specify the smtp server as smtp.gmail.com. Click on Advanced button. Check use SMTP authentication. Enter the email id from which email should be sent out as Username and its password in password field.

Check use SSL and enter the SMTP port as 465.  Check Test configuration by sending test e-mail option, enter the email id to which you want to send test email and finally click Test Configuration.  Wait for sometime if everything goes fine, you should be able to see Email was successfully sent message. Now hit Save. After configuration your E-mail Notification should look like


Thats it. Now return to your project, click configure and scroll down to Post Build Actions. Click on Add Post build action and select Email Notification from drop down.


 Enter the white space separated email ids to which you want to send email when a build becomes unstable.

Click on the Add post build action again and this time select Editable Email Notification option from dropdown. Scroll down and click on drop down next to Attach build log and select Attach Build log. 



Now click on Advanced Settings. Scroll down to Triggers section. Click on Add Trigger and select Always.

Remove Developers and Recipient list by clicking red X and click on Advanced. Enter the comma separated email ids in the Recipient List to specify the email id to whom you wish to send email every time Jenkins builds your code. Finally click on Attach build log and select Attach Build log from the dropdown. 

Click Save.Go back to Jenkins Dashboard and click on Build with Parameter. Now if everything goes fine you should receive the email once Jenkins finishes all its jobs.

Step 7: Automate Jenkins job execution on committing code to remote branch.
We have already hooked up our Jenkins System to monitor a specific branch in Part 1 and we have specified Poll SCM to query the remote branch once every 5 minutes and if there is change in commit hash map Jenkins will automatically will pull the code and start the jobs.

So go ahead, make some push to the branch you have hooked up your Jenkins system and give Jenkins 5 Minutes. Within 5 Minutes Jenkins should automatically detect the change in the code and make a pull and start all its jobs and send out an email to you about the status of those jobs.

Thats it! Now you have a fully functioning Jenkins set up as CI and CD system for iOS application development using Xcode. Whats next? Take some time and lemme know how awesome I am by commenting below :) Go ahead and flaunt your new skill to set up CI and CD system on your own.

2 comments: