We’ve recently taken over an existing project that suffered from some significant issues with the build and deployment processes: builds were tied to a specific machine and developer account; credentials were manually managed; and release processes were handled manually and tracked in spreadsheets.
We’ve updated this to a build and deployment process managed through merge requests, which automatically creates builds targeting different environments - enabling comprehensive testing and rapid app updates.
We’re now in the post-React Native upgrade era. Our philosophy for this exercise was “first make it work, then make it better”.
We started by automating as much as possible, and then improving what needed to be done. We had 2 short-term objectives:
For the sake of efficiency, we wanted our solution to be familiar, simple to use, and at hand.
Since the code was already in a GitLab repository, we started by exploring Gitlab Pipelines. That dream was short-lived because the runners in GitLab Pipelines are Linux-based and aren’t able to build iOS applications.
We then started looking for alternatives:
Bitrise is a fun and useful tool - so why did we choose it?
Our first question was how we wanted to build iOS and Android apps - in parallel or consecutive builds.
Running the Android and iOS builds simultaneously using the two concurrencies offered by BitRise is the faster of the two approaches, with the overall duration lasting the length of the longest build.
The consecutive approach, by comparison, provides the ability to run two different builds - for example, two branches or two merge requests. This is a lot slower, as the overall duration is the sum of the time taken for both builds.
There are also some other differences - parallel builds clone the repository and fetch dependencies at the start of each parallel run, whereas the consecutive approach does this only once for both runs.
Build logs and build artifacts are also handled differently - the parallel approach consolidates these into a single location, whereas consecutive builds result in multiple versions.
Having considered all of the above, we decided to go with the parallel approach.
This is what the results look like:
The build is triggered by a push or merge request. The first script, built by us, determines the type of build we want to do (testing, staging, or production). A parallel build is then started for iOS and the main thread continues the build for Android.
Once all the steps are executed, the main thread waits for the iOS build to finish and then reports the status back to Gitlab. This is the main reason why the Android main thread must wait for the iOS build.
Bitrise also helped us a lot by offering many out-of-the-box steps. With just a glance, we can see what actions are happening and which steps are needed for the build to be successful.
As helpful a tool as Bitrise proved to be, the process still wasn’t as smooth as we had hoped.
We encountered some issues along the way:
After a lot of trial and error we eventually started seeing consistent success.
This example shows an Android build - it’s the primary workflow for a merge request. At a glance, it’s possible to see that this is merge request 164 which targets development.
Here, a build is started directly from development in response to a push or merge request.
Builds can also be started on demand to make a custom configuration, for example.
One of the most important steps in the build process is Fastlane.
When we took over the project, it was already running with Fastlane, saving time when it came to build and release. We also like the fact that it shows how many hours we save!
Fastlane started out initially as a tool for iOS developers to help with the complexity of managing certificates, signing, and pushing apps to the App Store. It is open source and automates the entire process from build to deployment, as well as handling screenshots and release notes.
Based on Ruby scripts, it runs from the command line without any interface interaction required. It’s also decoupled from the continuous integration and continuous deployment systems.
Each release track or build track is called a “lane”. We need a feature lane to build for QA, a staging lane for staging builds, and a production lane for production builds.
Before Fastlane
Making a test build involved a number of manual steps:
With Fastlane, everything is more efficient. Configuration steps are defined in “lanes” as simple Ruby scripts. The match function makes sure you always have the necessary certificates and the provision profiles. If they don’t exist, it creates them; if they exist, it downloads them for you; if you change devices, it re-generates the provision profiles in the background.
Cocoapods is used to install dependencies, and then Fastlane does the app build. By doing all these actions, it saves developers a lot of time.
With Fastlane, we’ve built our release process in code. As it goes for pretty much everything, this too has advantages and disadvantages.
Basically, from our point of view, Fastlane + Bitrise = Love 💜.
Fastlane helps us to manage the release process in code, while Bitrise helps us take our benchmarking system, and identify what lanes to start and what environments to use for that lane. Putting these two together has proven to be the optimum solution for us for now.
But we’re not finished yet - there are still some steps that will further improve our processes:
1. The Fastlane configuration
When you have an iOS app that builds successfully applying this configuration, whichever lane you’d define will probably be successful as well, as long as the application has already been properly configured. The same applies to Android. If gradle is correctly configured, the app builds successfully, you have all the necessary credentials running the Fastlane lane and the app will get in the Play Store. The problem is that there are still several manual steps that need to be automated to be able to make configurations in Store.
2. There are still some steps in Bitrise that could be extracted as .yml code, although this would tie the project to Bitrise. An improvement point we’d like to tackle in the future is to migrate what we have in Bitrise to Fastlane, taking advantage of the modularization that has already taken place. The decisions we made when first starting out with this process made sense at that time, but as our understanding of both Bitrise and Fastlane has deepened, new solutions have emerged.
It all comes down to finding solutions that work for our particular context, taking all the steps that we need, transposing the process into automated steps, and, then extracting everything in our repository.
Together with the Penta team, we’re finding creative solutions every day and we’re very fortunate to share the same innovation mindset in the pursuit of the ideal mobile app experience.