The process of Continuous Integration performs various activities like analyzing, building, testing, and deploying iOS apps. It would be a great idea to break down each build task into various stages and execute them independently. This process is also called Build Pipeline or Deployment Pipeline. As an iOS developer, you might have experienced that building an iOS app can take a lot of time, as can testing. The pipeline is the way to deal with each stage of iOS development to get fast feedback. We probably don't want to get a build failure for code styling issues after spending hours in the build and test phase. Self-hosted Continuous Integration servers like Jenkins and TeamCity have good support to create build pipelines, but cloud-based CI servers like TravisCI, CircleCI, and some others are also rolling out deployment pipelining features. In this post, we will see how to setup iOS deployment pipeline using Travis Stages feature. Please note, I have used TravisCI as an example because I used it for some of my open-source projects. There might be similar features available for other could based CI servers as well.
Stages of iOS Deployment
Before jumping into TravisCI setup, we will see the important stages of deploying any iOS apps. The major build phases are
- Analyzing source code for code styling issues,
- Testing with XCTest or other frameworks,
- Building and archiving iOS app,
- Distributing an iOS app to different beta testing services, and
- Uploading the iOS app to iTunes Connect.
These are the major stages that have to be automated on a continuous integration server to enable automatic deployments.
Just in case you are new to TravisCI, it is a cloud-based Continuous Integration platform embedded with GitHub, which allows you to run CI builds for open-source as well as enterprise projects. It has good documentation to get started with Continuous Integration. TravisCI has announced a new feature, Travis Stages, to enable deployment pipelines for open-source as well as enterprise projects. The stages can be enabled by adding the following code sample in the.travis.yml file:
jobs: include: - stage: test script: ./test 1 - # stage name not required, will continue to use `test` script: ./test 2 - stage: deploy script: ./deploy
In order to apply this principle in iOS projects, let's create an open-source project on GitHub (you can use any iOS app):
- Create a blank single view app named SwiftLint-CI with Unit and UI Test Targets in Xcode.
- Add SwiftLint using CocoaPods for checking the Swift code syntax. This can be done by creating a Podfile and running the pod install command.
Now that we have a basic iOS project to be configured for CI, we can now add support to run the build on TravisCI and enable the following stages:
- Build Stage to Lint Swift code with SwiftLint
- Build Stage to run unit tests
- Build Stages to run UI tests
Creating travis.yml With Stages
TravisCI uses macOS virtual images to run builds; the list of the all the images can be found here. We can pick latest xcode9 images to run a build for our demo app. We have to run a script to execute SwiftLint and run XCTest using Fastlane Scan. We can set up our travis.yml file like this:
language: objective-c osx_image: xcode9 notifications: email: false branches: only: - master before_install: - gem install fastlane --no-ri --no-rdoc --no-document jobs: include: - stage: Swiflint script: - ./Pods/SwiftLint/swiftlint --reporter junit - stage: XCTest script: - fastlane scan -s SwiftLint-CITests - stage: XCUITest script: - fastlane scan -s SwiftLint-CIUITests
The config file above will run three different stages for SwiftLint, unit testing, and UI testing respectively. Once build is triggered on TravisCI, it will look like this:
This indicates that we have a build pipeline for each stage, and no stage will run unless the previous stage is passed.
Extending Travis Stages for iOS Deployment
Now that we have setup up build pipeline for code analysis and running unit and UI tests. We can take this pipeline further to deploy an iOS app to iTunes Connects. Automating and distributing builds are not in the scope of this post but you can read my previous post on automating iOS deployments to iTunes connects using Fastlane tools here. We can extend Travis stages to deploy an app like this:
jobs: include: - stage: Swiflint script: - ./Pods/SwiftLint/swiftlint --reporter junit - stage: XCTest script: - fastlane scan -s SwiftLint-CITests - stage: XCUITest script: - fastlane scan -s SwiftLint-CIUITests - stage: Build App # Write a script to Create keychain, Import certificate, Download provisioning profies in the TravisVM before build script: - fastlane gym -s SwiftLint-CI -q Release - stage: Upload To TestFlight script: - fastlane pilot upload --changelog "New Features"
This configuration will take our iOS app to the TestFlight assuming we set up all the provisioning and certificates in the TravisCI virtual machines. Note that you have to use different Fastlane actions to create keychain, import certificates to the keychain to allow TravisCI virtual machine codesign an app. In this way, we can break down iOS deployment phases into TravisCI stages for better visibility.
Parallelizing Travis Builds Within Stages
We can still parallelize Travis builds within stages to speed up the overall build execution. We can run unit and UI tests in parallel as well we can distribute an IPA to multiple platforms like Fabric and TestFlight at the same time. Not sure TravisCI has documented the parallelism but there is a trick to run the builds in parallel in the stage; we have to give the same name to the stage to run in parallel. In our SwiftLint-CI app, we can parallelize running unit tests as well as UI tests by calling it the "XCTest" stage, something like this:
jobs: include: - stage: Swiflint script: - ./Pods/SwiftLint/swiftlint --reporter junit - stage: XCTest script: - fastlane scan -s SwiftLint-CITests - stage: XCTest script: - fastlane scan -s SwiftLint-CIUITests
Note that we have given the same name to the build stage but are running different scripts for each stage. This will run the "XCTest" stage in the parallel. That's a trick to run Travis builds in parallel within a stage. The parallel build will look like this:
Pros and Cons of Using Travis Stages
Although the Travis build stages seem to be great options to create deployment pipelines, there are some pitfalls we need to consider. Here are some pros and cons of using Travis build stages for iOS deployment pipelines:
- We can have visibility of builds for each stage of the deployment pipeline.
- We can get early feedback if a build stage fails within the deployment pipeline.
- We can apply parallelism within a build stage as we can run multiple jobs in parallel in the stage. Currently, we are running unit tests and UI tests in parallel.
- We can assign environment variablea per stage.
- Travis can be used for deploying apps to Fabric and TestFlight using different stages.
- Still in Beta and has some bugs. Check known issues here.
- Triggers a new VM for every build in the stage, so checking out the repo for each task is time-consuming.
- We need to run the stage with the same name to achieve parallelism, which is a duplication of script execution.
- We can not share data between stages; we need to use AWS S3 bucket, which isn't feasible.
Source Code & TravisCI Builds
- GitHub Source Code: The sample Source Code for this post is available on Github repository SwiftLint-CI.
- TravisCI builds: The sequential build is available here and the parallelized build is here.
The cloud-based CI servers are providing the ability to run iOS builds in multiple stages to create seamless deployment pipelines for iOS apps. We can take advantage of such features for better feedback and visibility in the build process.