In this tutorial, I'm going to show you how to set up a universal delivery workflow for continuously shipping your iOS app using Bitrise.


Quick intro to Bitrise workflows

A Bitrise workflow is a collection of Steps. When a build of an app is running, the steps will be executed in the order that is defined in the workflow. - link

A step is a simple representation of a build task, they run one after another in the workflow. Inserting a new one is pretty straightforward, there is a + button in between every 2 steps, so you can add a new element just to the right place with a single click. The workflow editor is a really nice way of adding, removing or arranging them, however, if you are familiar with the bitrise.yml file format, it can be faster to edit that manually.

There are lots of available open source steps for Bitrise, also if you have some really specific need, you can write your own one by using a simple shell script or golang. Anyway, it's more likely that you'll find what you're looking for in the step library, so don't worry too much about this, because you know "there is a step for that".

Setting up an automated nightly build

After the quick intro, let's dive into the technical details of my workflow setup. Usually, I like to have an automated nightly build that creates an internal release straight out from the dev branch of the git repository. This way all the latest changes are going to be available for internal testing every day in the morning.

Git workflow:
I'm mostly following the succesfull git branching model methodology created by Vincent Driessen back in 2010. It's really good stuff, if you don't have a branching strategy you should read this article too, because it's quite a gem. 💎

Basic iOS Bitrise setup

After you set up your iOS application on Bitrise, by default there is a primary workflow based on the platform of your app. We have to change this basic one a little bit. First, let's rename it to nightly-build. I always use the following steps for my iOS builds, but not necessary in the following order:

I like to use the Bitrise cache mechanism to cache Pods. In order to do this you just have to add a new environment variable called BITRISE_CACHE_DIR with the following value: ./Pods -> ./Podfile.lock. Using this technique will speed up your build time if you are dealing with a large number of CocoaPods.

I also prefer to use the set project number step to synchronize my build number with my actual Bitrise build number. The only thing I have to set is the Info.plist file's path, which is usually located under the following place: $BITRISE_SCHEME/Assets/Info.plist (could be different for you).

At this point, I'm going to assume that you exported the required provisioning profiles or you're using the auto provision step. That's the hardest part of the job, but Codesigndoc is an amazing tool, it can help you a lot, so go ahead & try it. You won't regret it. So at this point if you've done everything right you should have a workflow that builds without errors. It's time to set up a schedule for it. ⏰

Build schedule

If you press the Start/Schedule a Build button you can enable the "Schedule this build" option. The process is really straightforward, you can input the hour and minute values, and select any given days to kick off your builds. It's also possible to set a specific  workflow and if you go to the advanced tab, there will be a curl command on the bottom of the list that can be used to start the build remotely. 👍

Trigger on push instead of scheduled builds

On the Triggers tab of the Workflow Editor, you can map a branch to a given workflow. If a push event happens on that branch a new build will be triggered on Bitrise. This comes in pretty handy if you don't want to rely on a fixed schedule, but rather send out new versions from the release-x.y branches.

Organizing your workflows

So far so good, we have a working continuous integration service for our development process. However, if you'd like to support more delivery methods from multiple branches things can get quite complicated. The first thing that you can do is to duplicate your entire workflow, but in the long term that might cause some trouble.

If you follow the usual git branching technique you might need to create separate delivery workflows for the nightly, preview and release builds. Instead of copying the whole workflow, we are going to create a chain of smaller workflows. This way if you have to change a parameter in a step, you don't have to manually alter that setting in every other instance in the duplicates. Here is my solution. 🚀

Build vs delivery

The build and the delivery processes are quite different. Building an artifact through the CI service can be separated from delivering the end product. This is the exact same approach that I like to follow in my setup. The only problem here is mobile provisioning. Unfortunately, you can't simply resign your previously generated product, so the delivery process has to build the final signed iOS app by using Xcode. This specific codesign issue is the reason why I go with 3 stages.

Preparation, delivery, notification stages

So basically these are workflows that can be triggered individually, but they can be linked together and they all have different purposes. Let me give some details.

Preparation

In this phase I setup all the basic stuff that's required for the Xcode build process. It doesn't really matter how Xcode will sign the final product, the only goal of this workflow is to make sure that everything is ready for the actual build process. 🔨

Delivery

The main goal of the delivery process is to build & ship the signed application to its proper place. That can be a Bitrise.io deployment or a TestFlight delivery using fastlane tools. I only have to duplicate this stage for every release target.

Notification

The last stage is all about notifying people, so you can get notified via your preferred notification system about a successful delivery. In most of the cases I have a dedicated Slack channel for build status notifications, so everything goes there by default. 📩


Building by triggering workflows

As you can see it's relatively easy to separate the original nightly-build workflow into smaller reusable pieces, and trigger one workflow before (or after) another. Let's do exactly like this. Here are the new workflow components.

Preparation

  • Activate SSH key (RSA private key)
  • Git Clone Repository
  • Bitrise.io Cache:Pull (for CocoaPods cache)
  • Run CocoaPods install (if I have pods)
  • Bitrise.io Cache:Push
  • Recreate User Schemes
  • Set Xcode Project Build Number
  • Certificate and profile installer (or iOS Auto Provision)

Delivery (nightly)

  • Xcode Archive & Export for iOS
  • Deploy to Bitrise.io - Apps, Logs, Artifacts

Notification

  • Send a Slack message (optional)

So actually there are 4 workflows now, the 4th one is just calling the preparation, All the required workflows are actually called before the nightly-build. We can say that the nightly-build workflow is just an empty container that's responsible for calling the other 3 (preparation, nightly-delivery and the notification) stages in the proper order. I prefer having these names for the stages:

  • preparation: generic reusable component
  • nightly-delivery: builds & deploys ipa to Bitrise.io
  • notification: generic reusable component
  • nightly-build: scheduled (or triggered) builds from dev

Let's make one more delivery stage, that builds from the master branch and uploads the artifact for TestFlight beta testing.

Preview (TestFlight release) builds using App Store  connect delivery stage

Creating a new workflow for App Store connect builds is way easier with this setup. As you can see the preparation & notification stages are more or less the same, you only have to create a new delivery method (preview-delivery) for the TestFlight builds. Here is the overview of the entire workflow setup:

Preview build

  • Preparation
  • Delivery (preview)
  • Notification

Delivery (preview)

As I said before, you just have to duplicate the nightly-delivery flow and replace the deployment step with the Deploy to iTunes Connect step, that'll push your final product to the right place. In this step you'll have to set your Apple identifier and some details about the delivery, but that's quite straightforward and there is a pretty good description for each available option. Always make sure that you have all the required entitlements & code signing certificates for this delivery target too! 🔥

As you can see having smaller reusable components instead of just one big workflow clearly has some benefits. Continuously delivering your application can be really effective, especially if you are using the right tools with the proper setup. I hope this tutorial will help you to have a better experience.

If you’ve never heard about Bitrise before you should definitely try it out, because currently it's one of the best CI/CD solutions for mobile application developers. 😉


External sources