Veepee iOS app automation

Albert Montserrat Gambus
VeepeeTech
Published in
7 min readOct 21, 2020
Photo by Alex Knight

When I joined Veepee’s iOS Core Team, I knew very little about the technical details under the hood of CI / CD workflow. As an iOS developer, I had been using CI/CD on daily basis, but hadn’t dived deep into the topic. The automation was really focused on making iOS developers’ life easier. The Core team’s goal is to help the iOS developers to develop without the tedious work of deploying the app to beta testers, checking if the build is stable after their changes, etc. After more than a year and a half, the automation process is very stable and mature, although we are always looking for ways to improve it.

In this article I want to explain in broad strokes our environment, our pipelines and how we managed to make it all work. In the future blog posts we will talk in greater technical detail about some parts of this automation.

Infrastructure, tech stack and tools

Gitlab

Our repository is placed in a self-hosted Gitlab instance, which is great because Gitlab already provides many tools to manage the automation process.

When the repository’s root folder contains a .gitlab-ci.yml file, Gitlab automatically uses it to generate the required pipelines.

GitLab CI/CD pipeline configuration reference https://docs.gitlab.com/ee/ci/yaml/

Within this file you can specify different jobs to be executed under specified conditions. In contrast to other CI services, the pipelines are conformed by the conditions that the jobs specify. This means that one single job declaration inside this yml file can be present in different kind of pipelines wether in other CI services, you declare the pipelines individually.

Here’s an example of a simple yml file:

As you can see, first we need to specify the stages. Stages define the order in which you want your jobs to be executed. Before starting a new stage, all jobs from previous stages must be successfully completed.

Stages are followed by job declarations, each containing its stage, scripts to be run, and conditions to be met.

CI Variables

Several of our scripts require specification of username and password credentials, secret tokens, etc.

Those keys shouldn’t be placed inside the repository, so we cannot add them directly to the yml file.

Gitlab offers a dashboard where you can add different variables and secrets that are automatically injected to our jobs as part of ENV variables.

Gitlab Schedules

Another useful feature that Gitlab has is the schedule section. Here you can specify an interval pattern for the pipeline to be run and the branch where it has to start. Additionally, you can add some extra environment variables. This might also help to run different pipelines from the same branch.

And inside yml file:

CI Machines

To run those pipelines, Gitlab provides a script to be installed in a hosted machine called Gitlab Runner.

These runners will pick jobs from the pipelines, execute them, and, once finished, they will return the results back to Gitlab.

While this system is great because it’s the way Gitlab designed their CI infrastructure, it has a high cost in terms of the setup and maintenance of these machines. Fortunately there are some companies that provide the infrastructure needed to run CI/CD pipelines for iOS apps. Basically, they provide physical Macs or virtual environments to build, test and deploy your iOS apps.

Fastlane

This may be one of the most important parts of our automation because what Gitlab is running under the hood are mostly fastlane scripts.

fastlane is an open source platform aimed at simplifying Android and iOS deployment.
fastlane lets you automate every aspect of your development and release workflow.

https://fastlane.tools/

Fastlane covers the most used actions a developer can take in their daily work, from running unit tests with a single command to deploying the app to the AppStore.

It also makes it really simple to manage signing certificates thanks to their match module.

In Veepee we went way further than that. We developed a custom plugin to communicate to Gitlab API through fastlane commands. Like this we can really customize our workflow combining our Gitlab environment with all other automation features. Apart from that, we also developed custom actions, such as generating changelogs for the current features, managing local branches, starting new features easily, etc.

Custom scripts

Even though we have almost everything covered with Fastlane, we also developed some standalone scripts. To list a few of them, we have:

  • A build time recorder, where we store the build times from all iOS devs and display them on a Grafana board to easily spot potential increases build time over the time
  • A Slack helper to send messages to specific Slack channels
  • Measuring launch time of the application

Automation on a daily basis

In the iOS team, we use git flow as a system to organize and manage our branches.

https://nvie.com/posts/a-successful-git-branching-model/

Basically, there are thedevelop and master branches. New features start from develop and are merged back into develop. New releases start from develop and go back to develop and also tomaster branches. This way, the master branch only has the versions published to the AppStore while develop is the main branch and the starting point for new developments and new releases.

With this in mind, let’s talk about how everything is glued together. Next we’ll describe the pipelines we are currently running in our daily work, even though they are constantly being improved.

Feature branches

Every time a developer pushes changes to a feature branch, this pipeline is executed.

In the review stage, we have a linting job to ensure that code quality is maintained, a MR Convention job to check if the merge request meets the code style requirements, such as if there’s a new changelog for this feature, if the merge request is not too big, etc. Finally we have a manual job (that the developer can trigger at will in case it’s needed) to assign the approvers for this MR once it’s really ready. This job tries to rotate the iOS developers approvals to avoid overloading iOS developers with a lot of review work.

In the tests stage there are many validation jobs, most of them optional. The ones really required are UI Tests and Unit Tests. For these two jobs we developed an algorithm to determine the modified modules and their dependencies. The CI runs tests only for these modules to save time while ensuring code stability. On the other hand, devs can still run the full suite of tests manually if they want.

Finally, in the deploy stage we can manually send the builds to beta testers with a single click and receive a notification when they are ready for review.

The develop branch

Once a feature branch is merged to develop, we just want to ensure there are no regression issues, so we only run the full suite of Unit and UI Tests. In case they fail, a Slack message is sent to notify us that the develop branch is unstable.

Code freeze

Every week we start a new release train, which means that the code from thedevelop branch is frozen in a new release branch. We perform different tests to ensure the stability of the release and finally we deploy a new version to the AppStore.

To start this process, we execute a scheduled pipeline in Gitlab to perform the code freeze. This job basically opens a release branch from develop and prepares the first commit for the release (merging changelogs, etc.).

The release branch

After the code freeze creates the release branch, this pipeline is executed. It will also be executed if new commits are pushed to release branch.

First we run the tests in the tests stage.

Afterwards, in the pre-deploy stage, we use Gitlab API to create merge requests from this branch to develop and master.

Then the most important part is the deployment of this release to the AppStore and also to the internal beta test platform.

Finally, in the post-deploy stage, we automate the preparation of the AppStoreConnect version and a notification is sent to our Slack channel that the new build is ready to be tested.

The master branch

Similar to what we do in the develop branch, when the release branch is merged to the master branch we run all tests just in case there’s some regression. Afterwards we just want to announce the new release in a Slack message and also add the release tag to our repository.

Conclusion

As you can see, we have all the development flow covered by automation, from the start of a new feature with the custom lane start_feature to the deployment of the new version and the integration to the develop and master branches.

For the release train, in every code freeze we pick a different release manager from our iOS crew. The job that this manager has to do is really, really minimal in order to bring this new version to live. Basically, check if all the automation works fine and hit some submit buttons!

This has been a quick introduction to our automation system. In future articles we will describe more technically some of it’s parts.

--

--