DevOpsiOS Development

Configuring Jenkins Pipeline Job For iOS Project (using Groovy script)

By December 10, 2016 No Comments

This is the 3rd and last part of our series of “Continuous Integration and Delivery for iOS using Jenkins and Fastlane”. Check below links for the 1st and 2nd Parts

We need to have a workflow in order to know what process is currently taking place or what job is currently being executed. Traditional way of piping has several drawbacks:

  1. It lacks Visualization of jobs being executed
  2. It is not possible to have human intervention during the pipeline
  3. Maintaining a large number of jobs for a single project can soon become a nightmare

We can eliminate these disadvantages by automating the job pipelining using ‘Pipeline’ plugin.

The ‘Pipeline’ plugin of Jenkins helps us by providing manual triggers and visualization of the build pipeline for continuous delivery purposes.  

Follow these steps to configure Jenkins Pipeline Job for iOS project using Groovy script

Step 1:

Go to Jenkins dashboard by navigating to http://localhost:8080. Install the plugins which are required for building and deploying iOS projects. Here is the list of required plugins:

  • ANSI Color Plugin
  • Git Plugin
  • Credentials Binding Plugin
  • Cobertura Plugin (For code coverage reports)
  • Copy Artifact Plugin
  • Email Extension Plugin
  • HTML Publisher Plugin
  • Pipeline
  • PMD Plugin (For static Code Analysis reports)
  • Test Results Analyzer (For Unit Tests reports)
  • Workspace Cleanup Plugin

Go to the Jenkins dashboard and click on Manage Jenkins ->  Manage Plugins. Click on the Available tab and search for all the above mentioned plugins by clicking on the search icon. Select all the plugins and click on the ‘Download now and Install after restart’ button. In the next page, select to restart Jenkins. Let the Jenkins restart after the download process is complete.

Step 2:

Once all the plugins are installed, we need to create our groovy pipeline job. Click ‘New Item’ on the Jenkins dashboard and create a pipeline project by selecting ‘Pipeline’ and clicking on ‘OK’ as shown in the below screenshot

Groovy 2Step 3:

We will now be presented with the job configuration page. Configure the things that are required for the project. In the last section that says ‘Pipeline’, we will have a drop down and a script text area where we need to write our ‘Groovy Script’ to define the workflow. We can have the script checked into  SCM and let the Jenkins job automatically grab it from there. To do that, select ‘Pipeline script from SCM’ in the ‘Definition’ drop down.

Groovy 3

Step 4:

Deselect ‘Use Groovy Sandbox’ (as shown in the above screenshot)

Step 5:

One does not needs to be an expert in  Groovy to use pipeline. The ‘Pipeline Syntax’ link (as shown in the above screenshot) helps in getting the things up and working. It generates the syntax for the steps and commands written  in Groovy script.

Sample groovy Script:

This is a sample groovy script with lots of juicy comments.

def init(){
// Adding Environment Path where our fastlane ruby script exists
env.PATH = "/usr/local/bin:${env.PATH}"
// Adding Language isandatory or else console output will not print properly
env.LANG = "en_US.UTF-8"
// Adding Fastlane Match command Keychain name to environment
env.MATCH_KEYCHAIN_NAME = "Jenkins.keychain"
// Adding Fastlane Keychain name to environment
env.KEYCHAIN_NAME = "Jenkins.keychain"
// Adding Fastlane Keychain Password for unlocking the environment
env.KEYCHAIN_PASSWORD = "PASS123456"
// Shell script to make sure keychain is unlocked so it can be accessed
sh "security -v unlock-keychain -p '${env.KEYCHAIN_PASSWORD}' '/Users/Shared/Jenkins/Library/Keychains/Jenkins.keychain'"
}

def sendmail(){
// Getting list of all persons who committed from last successful build to present build
def to = emailextrecipients([[$class:'CulpritsRecipientProvider']])
// Sending Email Notifications to all the persons which we got from above step
step([$class:'Mailer', notifyEveryUnstableBuild:true, recipients: to, sendToIndividuals:true])
}

stage 'Checkout'
node{
// Calling init method to initialize things
init()
// Checking out the latest commit from Git
checkout([$class:'GitSCM', branches:[[name:'*/Develop']], doGenerateSubmoduleConfigurations:false, extensions:[[$class:'CleanBeforeCheckout']], submoduleCfg:[], userRemoteConfigs:[[credentialsId:'349d9853-6gfd5-4a42-b4bf-b6337e7af019', url:'git@10.0.0.248:sriharsha.y/password-manager.git']]])
}

stage 'Unit Tests'
node{
// Calling init method to initialize things
init()
// Flag to check error
def err = null
// AnsiColor Build wrapper for colorful output in console instead of plain output
wrap([$class:'AnsiColorBuildWrapper','colorMapName':'XTerm','defaultFg':1,'defaultBg':2]) {
// ‘Try Catch Finally’ block to handle result of the Unit test
try{
// Shell command to execute fastlane ‘unittest’ lane
sh 'fastlane unittest'
}
catch(caughtErr){
// If there is any error, assign it back to err variable
err = caughtErr
}
finally{
// Shell command to change unit test .junit report to .xml report for Jenkins to properly display the results in job page
sh "cp output/report.junit output/report.xml"
// Giving report to JunitResultsArchiver plugin for display purpose
step([$class:'JUnitResultArchiver', testResults:'output/report.xml'])
// Giving report to ArtifactArchiver to archive the report for this build
step([$class:'ArtifactArchiver', artifacts:'output/*.xml', excludes:null])

//Stop the pipeline if err is not null, else execute the next stages
if(err){
// currentBuild is a pipeline environment variable. Setting its result property to FAILURE causes the pipeline to stop
currentBuild.result ='FAILURE'
}
// Call sendmail method to send mails if this unit test fails
sendmail()
// Propagate the error to stop the pipeline
if(err)
throw err
}
}
}

stage 'UI Tests'
node{
// Calling init method to initialize things
init()
// Flag to check error
def err = null
// AnsiColor Build wrapper for colorful output in console instead of plain output
wrap([$class:'AnsiColorBuildWrapper','colorMapName':'XTerm','defaultFg':1,'defaultBg':2]) {
// ‘Try Catch Finally’ block to handle result of the Unit test
try{
// Shell command to execute fastlane ‘uitest’ lane
sh 'fastlane uitest'
}
catch(caughtErr){
// If there is any error, assign it back to err variable
err = caughtErr
}
finally{
// Stop the pipeline if err is not null, else execute the next stages
if(err){
// currentBuild is a pipeline environment variable. Setting its result property to FAILURE causes the pipeline to stop
currentBuild.result ='FAILURE'
}
// Propagate the error to stop the pipeline
if(err)
throw err
}
}
}

stage 'Code Coverage'
node{
// Calling init method to initialize things
init()
// Flag to check error
def err = null
// AnsiColor Build wrapper for colorful output in console instead of plain output
wrap([$class:'AnsiColorBuildWrapper','colorMapName':'XTerm','defaultFg':1,'defaultBg':2]) {
// ‘Try Catch Finally’ block to handle result of the Unit test
try{
// Shell command to execute fastlane ‘coverage’ lane (code coverage)
sh 'fastlane coverage'
// Giving report generated by fastlane coverage lane to ArtifactArchiver to archive the report for this build
step([$class:'ArtifactArchiver', artifacts:'output/*.xml', excludes:null])
}
catch(caughtErr){
// If there is any error, assign it back to err variable
err = caughtErr
}
finally{
// Stop the pipeline if err is not null, else execute the next stages
if(err){
// currentBuild is a pipeline environment variable. Setting its result property to FAILURE causes the pipeline to stop
currentBuild.result ='FAILURE'
}
// Call sendmail method to send mails if this unit test fails
sendmail()
// Propagate the error to stop the pipeline
if(err)
throw err
}
}
}

stage 'Static Analysis'
node{
// Calling init method to initialize things
init()
// Flag to check error
def err = null
// AnsiColor Build wrapper for colorful output in concole instead of plain output
wrap([$class:'AnsiColorBuildWrapper','colorMapName':'XTerm','defaultFg':1,'defaultBg':2]) {
// ‘Try Catch Finally’ block to handle the result of the Unit Test
try{
// Shell command to execute fastlane ‘analyze’ lane (static code analysis)
sh 'fastlane analyze'
// Giving report generated by fastlane analyze lane to PmdPublisher plugin to show static analysis report for this build in job page / build page
step([$class:'PmdPublisher', canComputeNew:false, defaultEncoding:'', healthy:'', pattern:'oclint_report.pmd', unHealthy:''])
// Giving report generated by fastlane analyze lane to ArtifactArchiver to archive the report for this build
step([$class:'ArtifactArchiver', artifacts:'*.pmd', excludes:null])
}
catch(caughtErr){
// If there is any error,assign it back to err variable
err = caughtErr
}
finally{
// Stop the pipeline if err is not null, else execute the next stages
if(err){
// currentBuild is a pipeline environment variable. Setting its result property to FAILURE causes the pipeline to stop
currentBuild.result ='FAILURE'
}
// Call sendmail method to send mails if this unit test fails
sendmail()
// Propagate the error to stop the pipeline
if(err)
throw err
}
}
}

stage 'Package'
node {
init()
def err =null
wrap([$class:'AnsiColorBuildWrapper','colorMapName':'XTerm','defaultFg':1,'defaultBg':2]) {
try{
sh 'fastlane release_package'
step([$class:'ArtifactArchiver', artifacts:'output/*.ipa', excludes:null])
}
catch(caughtErr){
err = caughtErr
}
finally{
if(err)
currentBuild.result ='FAILURE'
sendmail()
if(err)
throw err
}
}
}

stage 'Upload to Testflight'
node {
init()
def err =null
wrap([$class:'AnsiColorBuildWrapper','colorMapName':'XTerm','defaultFg':1,'defaultBg':2]) {
try{
sh 'fastlane upload_to_itc'
}
catch(caughtErr){
err = caughtErr
}
finally{
if(err)
currentBuild.result ='FAILURE'
sendmail()
if(err)
throw err
}
}
}

Important things to note:

  • As you can see from above code, there are two methods init() and sendmail(). Methods should be defined by ‘def’ keyword
  • ‘stage’ step gives a visual of the job being executed in the pipeline.Groovy note
  • ‘node’ step is where the actual job is allocated an executor in Jenkins. It is the place where we build or run the projects.
  • Pipeline can wait for human interactions between the stages using a command called ‘input’ (Syntax for this can be generated from the Pipeline Syntax link, which we have seen earlier).
  • When creating for the first time, the pipeline job needs to be executed manually, so that it can know which Git repository it should associate with. After that, it runs according to Build triggers set in the pipeline job page.

Related Post

Related Post

Leave a Reply