When iOS CI/CD Config is not Just a File or a Dashboard
A while ago, when I heard the term “CI/CD”, I always thought of a dashboard to drag and drop, upload certificates and input the scheme… blah blah. That’s all! And I was kinda believe that a good CI/CD platform must be like that: convenient, as few setups as possible. Now, I have a different viewpoint. What I very much expect in a CI/CD platform is the ability to customize workflow. This does not mean those drag-and-drop platforms are inadequate. It depends on the scale of your project and the problems you want to solve.
In this post, I will talk about those problems on a general level. Details about how to tackle each will be discussed later in the upcoming posts.
A Large Code Base
In my project, there are many engineers contributing to a fairly large code base. How large is it? - you may wonder. Well, let’s imagine there are a lot of product features in the project. Each feature has its A/B testing logic, making our code base even bigger. Apart from product features, we also have engineering work. Some of them need A/B testing as well, in order to safely roll out to users. And of course, we do cover unit tests and UI tests for both features and engineering work. In addition to A/B testing, we have features toggles (as part of trunk-based development) which means more code than needed is added per feature.
Therefore, our code base has been growing over time. With our project, it takes:
- $10_m$ for a clean build 😞
- $7_m$ to run all unit tests 😐
- $40_m$ to run all UI tests (using 3 simulators in parallel) 😱
Those are the required steps to run on CI against a change in a merge request. Doing a simple math and you will realize that a developer has to wait for nearly an hour to land his/her change on master
(assume other checks such as approvals and code linting are already satisfied).
With such an increasingly large code base, CI/CD configuration is not just to make things work, but to make things work efficiently.
Build Time
iOS build time improvement is a classic problem of large code base projects. There are some tips to improve the project build time including:
- Changing some build settings
- Enabling/disabling some optimization flags
- Spotting code snippets that takes a long time to compile
- Modularizing code base to utilize build parallelism
- …
Those approaches are not much related to CI/CD configuration. Some tips are suitable for local runs but not for CI/CD runs. For example, changing some optimization flags would reduce build time but it screws up code coverage generation. We have to alternate build settings for CI/CD in such cases.
A Tale of UI Tests
We are pretty proud of our UI tests. Not only do they cover a lot of features in the app but also they are very useful for feature development/bug fixes (especially when we want to simulate complicated workflows without Staging backend). However, the more tests we write, the longer time it takes to execute all test suites. Reducing the overall test execution time is definitely a CI/CD work that drag-and-drop is not capable of.
A simple idea is to split UI tests and run them in parallel jobs. Then you need to answer the following questions:
- How to merge code coverage of those parallel jobs?
- How to split tests in a good way?
You cannot split them in a way that one job takes $30_m$ to finish while the other takes only $10_m$. - How to split build and test to 2 different jobs?
This seems trivial but in iOS, it’s a bit troublesome. First, you need to know what inDerivedData
that are needed to run tests, and pass them via job artifacts. With a large code base project, the build artifacts needed is really big (up to 2GB) and exceeds the artifacts limits set by your CI/CD platform. We will talk about how to tackle this problem in upcoming posts.
Another problem with UI tests is that they seem to be more unstable than unit tests. Dealing with unstable tests is not just iOS work, but also a CI/CD work. For example, you need to design your CI/CD pipelines so that it’s less vulnerable to unstable tests and the time it takes to retry (tests or jobs) is as fast as possible. And you need to track those unstable tests (not manually) so that you could revisit to investigate them.
CI Resources
Making good use of CI resources is also a key to make our CI/CD system work at its best. When there are more available runners, try to use them. However, determining CI resources status is not always easy. It usually involves sending api requests to the CI/CD platform (for ex. Gitlab).
Also, when we allocate resources differently, the number of CI jobs are dynamic. How to configure that?
A/B Testing for CI/CD
In our project, we not only have AB testing for features in the app, but also for CI/CD features. Changes related to CI/CD usually affects other engineers. I need to emphasize again that there are many engineers contributing to the project, not just 3-4 engineers. To avoid blocking others, we always think of a safe rollout for important CI/CD changes. If there is any unexpected issue that block others, we can just roll back the change at ease.
A rollout config is just simply a yaml
file (hosted somewhere), like this:
name: 'A feature'
description: 'Description of the feature'
rollout:
- if: 'XCODE_VERSION'
match: '11'
then: 2
- if: 'CI_COMMIT_REF_NAME'
match: '(master|release)'
then: 4
- default: 3 # 👈 use this value in CI/CD code
Automation
In our project, we try to automate tasks/chores as much as possible. Those automated tasks are usually non-standard problems and, of course, are something we need to code on our own.
Conclusion
With some use cases mentioned above, you can imagine that CI/CD work for iOS is not just integrating to CI/CD platform so that we can build and test our project. It’s not that simple, or your project is not complicated enough 🤔. It’s not just about a config dashboard or a config file… To me, it requires more implementation code to get things done in an appropriate way. The advantage of building them instead of relying on a 3rd party platform support is that you have more control and can customize them based on your needs. And it’s sometimes fun.
…
I know this post is a bit general, and lacks details (which you expect more). But I think it would be better if you have an overview first, and then we can dive into details later. So, for those who are interested in, stay tuned for the upcoming posts 😉.