Continuous Deployment — iOS Apps
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 —
APPSTORE_USERNAME=”<Account_Email_ID>”
APPSPECIFIC_PASSWORD=”<Password_Generated_On_Portal>”
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) —
url=${GIT_URL#*//}
git clone http://${GIT_USERNAME}:${GIT_PASSWORD}@${url} .
git checkout tags/${GIT_TAG}
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/bashVERSION_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
- deploybuild_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:
- tagstest_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
- mastertest_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:
- developdeploy:
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.