Continuous Deployment — iOS Apps. CD is becoming part of mandate for iOS…
By: Rakesh Chander
Tags: Jenkins, Continuous Deployment, iOS, Xcode, Continuous Integration

CD is becoming part of mandate for iOS Apps Release process. Here I am going to explain — How to setup Jenkins Job for uploading iOS Apps on App Store.

Pre-Requiste —

Add one plist file at root of your project folder — ExportOptions.plist — This file is required for AppStore Connect Details of project.

Generate App Specific Password for your App Store Account — for Application Loader

Distribution Certificates & Provisioning profiles are already installed on Jenkins Runner Machine.

Jenkins Job SetUp —

Aborting Job in case error occurs at any stage of job — add below line as first line of your shell script -

set -e

Clean up workspace from previous builds -

rm -rf $WORKSPACE;mkdir $WORKSPACE || true

cd $WORKSPACE

Specify App Store Account credentials — app sepcific credentials for Application Loader —

Specify GIT_URL , GIT_USERNAME, GIT_PASSWORD —

GIT_URL=”<URL>”

GIT_USERNAME=”<Username>”

GIT_PASSWORD=”<Password>”

GIT_TAG=”<Tag Value>”

Specify Project Properties —

SCHEME=”<Project_Scheme>”

EXPORT_OPTIONS_PLIST=”<File_Name_ExportOptions>”

Add below lines to checkout code from tag only (if any of above value is incorrect (tag also), job will fail and abort) —

If your project is using cocoapods, add below lines —

rm -rf Pods

rm -rf Podfile.lock

pod install

Set up archive & ipa path —

workspace=$(ls | grep xcworkspace | awk -F.xcworkspace ‘{print $1}’)

ARCHIVE_PATH=$PWD/archive/${workspace}.xcarchive

EXPORT_PATH=$PWD/build

IPA=”$PWD/build/${SCHEME}.ipa”

Archive your project —

xcodebuild -quiet clean archive -workspace ${workspace}.xcworkspace -scheme “${SCHEME}” -sdk iphoneos -archivePath $ARCHIVE_PATH -allowProvisioningUpdates

Exporting ARCHIVE to generate IPA —

xcodebuild -quiet -exportArchive -archivePath $ARCHIVE_PATH -exportOptionsPlist ${EXPORT_OPTIONS_PLIST}.plist -exportPath $EXPORT_PATH

Upload IPA on App Store —

xcrun altool — upload-app -f “${IPA}” -u ${APPSTORE_USERNAME} -p ${APPSPECIFIC_PASSWORD}

Thats It!!

Now just run job and it will take care of everything — You can enjoy coffee!!

Add On —

Single Jenkins Job for different Variants of project — DEV / UAT / PVG / PROD

If your project is having different schemes for different EndPoints and you want to generate IPA accordingly then you can make SCHEME variable as choice parameter (Make Sure — Choice options are having exact names as that of your scheme names in Project)

- Parameter Name is SCHEME

- Remove SCHEME declaration from section “Specify Project Properties”

Setting Up Build Version / Build No from Jenkins Job

In your project target settings — set Build Version & Build No as variable VERSION_NUMBER & BUILD_NUMBER respectively. (By default, this is the setting)

Then in Jenkins Job, take two more inputs and name that as above. In Jenkins Script, just before archiving , put below lines -

xcrun agvtool new-marketing-version ${VERSION_NUMBER}

xcrun agvtool new-version -all ${BUILD_NUMBER}

Continuous Deployment —

We can execute this Jenkins Job as soon as project is getting tagged on Master Branch to automate the process. This is achieved via YML file in Continuous Integration. Refer my story Continuous Integration for setup and basics details.

At your CI runner machine, create a shell script file — deploy_app.sh & edit below script code —

#!/bin/bash VERSION_TAG=`git describe --tags` curl -X POST --data-urlencode json='{"parameter": [{"PROJECT": "<PROJECT_NAME>", "value": "VERSION_TAG", "scheme":"PROD"}]}' http://<JENKINS_SERVER_URL>/job/<TESTFLIGHT_JOB_NAME>/build

As per your Jenkins Job needs, you can pass required parameters via “json” from depoly_app.sh

Now, in your YML file, add a new stage named “deploy” and corresponding job with below details —

stages:

- test

- build

- sonar

- deploy build_core_all:

stage: build

script:

- rm -rf Pods

- rm -rf Podfile.lock

- pod install

- bash $HOME/Documents/Pre-Build/pre_build.sh

- xcodebuild -workspace <WORKSPACE_NAME>.xcworkspace -scheme <SCHEME_NAME> clean build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED=NO -destination generic/platform=iOS

except:

- tags test_all:

stage: test

script:

- rm -rf Pods

- rm -rf Podfile.lock

- pod install

- xcodebuild -workspace <WORKSPACE_NAME>.xcworkspace -scheme <SCHEME_NAME> clean test -destination 'platform=iOS Simulator,name=iPhone 11,OS=14.0' -enableCodeCoverage YES

except:

- tags

- master test_sonar:

stage: sonar

script:

- rm -rf Pods

- rm -rf Podfile.lock

- pod install

- xcodebuild -workspace <WORKSPACE_NAME>.xcworkspace -scheme <SCHEME_NAME> clean test -destination 'platform=iOS Simulator,name=iPhone 11,OS=14.0' -enableCodeCoverage YES -derivedDataPath Build/

- bash xcode-coverage-mapper.sh Build/Logs/Test/*.xcresult/ > sonarqube-generic-coverage.xml

- bash $HOME/Documents/Sonar/sonar*/bin/sonar-scanner

- bash sonar_validation.sh

only:

- develop deploy:

stage: deploy

script:

- echo "Release to Production"

- bash $HOME/Documents/Deploy/deploy_app.sh

only:

- tags

except:

- branches

So, You are all done now. As soon as you will be merging your code to master and creating a tag onto that your App will automatically be deployed to TestFlight.