Github Actions are finally publicly released! It's an opportunity to easily enable continuous integration in your projects on GitHub, so here's how you set it up for your iOS projects to perform automated code validation and testing on pull requests.
Continuous Integration Using GitHub Actions for iOS Projects
Written by
This post will cover a simple approach to perform CI for iOS projects using the newly released Github Actions. This new CI tool is well-integrated into GitHub, making it the fastest option for effective CI in your GitHub projects. So without further ado, let me guide you through the setup.
Preparing Mint packages 🌱
As part of doing code validation, we'll need SwiftLint. My preferred way of setting up CLI tools such as SwiftLint is by using Mint. Mint is great for most CLI packages that you would normally download with Homebrew, as you can easily download and run a specific version of a package. This way, we can be sure that we're running the correct version in GitHub Actions. Add a Mintfile
in the root of your project. It should contain one line:
realm/SwiftLint@0.38.0
That's it. We'll create a script later to install Mint itself from Github Actions.
Preparing RubyGems 💎
We'll be using Bundler so we have a Gemfile specifying the RubyGem dependencies that we need to do CI. If you don't have Bundler installed already on your Mac, do it by running gem install bundler
. If you don't have permissions, you need to have rbenv installed, so you don't need to sudo
it.
Navigate to the directory of your project and run bundle init
. That will create a Gemfile for you. Here are the dependencies we need to install, so copy/paste this code into your newly created Gemfile:
source "https://rubygems.org" gem "danger" gem "danger-swiftlint" gem "fastlane" gem "xcode-install"
Now that we've specified the dependencies we need, install them by running bundle install
. This also adds a Gemfile.lock to your project, specifying the versions of the dependencies that were installed.
Script to automate the installation of dependencies 📄
Now that we've specified our dependencies, we'll create a script for automating the installation. Create a file called bootstrap.sh
in the root of your project containing the following script:
#!/bin/sh echo "checking for homebrew updates"; brew update function install_current { echo "trying to update $1" brew upgrade $1 || brew install $1 || true brew link $1 } if [ -e "Mintfile" ]; then install_current mint mint bootstrap fi # Install gems if a Gemfile exists if [ -e "Gemfile" ]; then echo "installing ruby gems"; # install bundler gem for ruby dependency management gem install bundler --no-document || echo "failed to install bundle"; bundle config set deployment 'true'; bundle config path vendor/bundle; bundle install || echo "failed to install bundle"; fi
By running sh bootstrap.sh
from the terminal, we'll have all dependencies ready - greatly simplifying our GitHub Actions setup.
Creating the workflow ⏭
Now, we need to create the workflow to be triggered when creating a pull request towards the master branch. Follow these steps - and we'll go through the setup after you've had a chance to see it.
- Go to your repo in GitHub and press
Actions -> Set up a workflow yourself
- Change the name to "ci.yml"
- Paste the following code into the editor:
name: CI on: pull_request: branches: - master jobs: validate-and-test: runs-on: macOS-latest steps: - name: Checkout uses: actions/checkout@v1 - name: Cache RubyGems uses: actions/cache@v1 with: path: vendor/bundle key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }} restore-keys: ${{ runner.os }}-gem- - name: Cache Mint packages uses: actions/cache@v1 with: path: ${{ env.MINT_PATH }} key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }} restore-keys: ${{ runner.os }}-mint- - name: Install dependencies run: sh ./bootstrap.sh - name: Run code validation run: bundle exec danger env: DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }} - name: Run tests run: bundle exec fastlane test env: MINT_PATH: ${{ github.workspace }}/mint
What you need to take away from this is that we have one job called "validate-and-test". It consists of several steps which are simple tasks run one after another. If one step passes, we proceed to the next step. In this setup, we have the following steps:
- Fetch cached dependencies, if any. This uses an official action called "cache". You can read about the concepts of actions here.
- Install all dependencies using the
bootstrap.sh
if no cache was found. - Run code validation using Danger. This reads the access token provided as an environment variable.
- Run tests using fastlane. The command
bundle exec fastlane test
triggers a lane called "test" in our Fastfile that executes the tests.
if you're not familiar with the concepts of workflows, jobs, and steps, I recommend that you read about the workflow syntax from the GitHub Actions docs.
Code validation using Danger + SwiftLint ⚠️
Danger is one of the dependencies we specified in our Gemfile earlier. It's a great tool to improve our code reviews and simplify pull requests to our project. The Danger docs describe itself like this:
Danger runs during your CI process, and gives teams the chance to automate common code review chores.
These chores could be anything from formatting issues in the code to formalities such as not leaving a description in the pull request. You decide on the rules using the Dangerfile. What happens is that Danger will leave messages in your pull requests when you violate any of the rules.
We need to provide Danger with an access token. Go to GitHub's Personal Access Token in Settings and generate a new access token. You can name it "danger" if you like. Then you need to go to the secrets section under Settings in your GitHub project. Copy/paste the access token to a new secret called "DANGER_GITHUB_API_TOKEN". That will give Danger the access it needs.
We also specified "danger-swiftlint" in the Gemfile. It's a plugin for Danger that allows us to use SwiftLint in combination with Danger to perform linting and report back if there are any violations. Create a ".swiftlint.yml" file in the root of your project. You can copy/paste the linting rules that I normally use here. Lastly, make sure that you have these two lines in your Dangerfile:
swiftlint.config_file = '.swiftlint.yml' swiftlint.lint_files inline_mode: true
That's the code validation! Now, onto the automated testing.
Testing using Fastlane scan 🛠
We are using Fastlane scan to run our tests. So we need to set up Fastlane by running fastlane init
at the root of our project and select: "4. Manual setup - manually set up your project to automate your tasks". That will create a Fastfile inside the newly created fastlane folder.
Now, go ahead and run fastlane scan init
in your project to create a Scanfile inside the fastlane folder. This is a configuration file with all the default parameters we want to set when we run the tests. It could look like this, but you want to check the docs to see what makes sense in your project:
devices(["iPhone 11 Pro"]) reset_simulator(true) clean(true)
In your Fastfile, create one or more lanes where you'll run your tests. In my case, it looks like this:
default_platform(:ios) before_all do xcversion(version: "11.3") end platform :ios do desc 'Runs the tests in Introspect' lane :test do scan(scheme: "Introspect") end end
You should change the code to match the scheme in your project.
Results ✅
Now you should be all set with the testing. If you've come this far, you should be able to create a pull request with changes to your code and see the CI in action. If everything is successful, you should see a screen like this in the details about the run:
Code validation, build and testing in about 5 minutes is quite acceptable. In the project settings on GitHub, make sure to enable branch protection for the master branch to only accept pull requests - allowing only changes that pass code validation and testing.
Conclusion
That's it! A good CI setup can speed up your team's workflow significantly. It may even determine if your app makes or breaks it during development. You can find the source code for this setup on GitHub.
Also, check out this free course, How to Accelerate Your iOS Career and Double Your Income, that I just published this week. Thanks!
Share this post
Facebook
Twitter
LinkedIn
Reddit
You may also like
Automation
iOS Continuous Deployment Using Travis CI and Fastlane
Continuous Deployment (CD) is a practice of using automation to release your code in a quick and sustainable way. Here’s how you set up CD for iOS apps to perform automated building, code signing and deployment to the App Store.
Automation
Automate Screenshots for App Store Using Fastlane
Save yourself from spending hours on screenshots for the App Store. Here’s a step-by-step guide on how you automate the process of taking screenshots for all supported screen sizes and languages.
DevOps
3 EAS Pipelines for Deploying React Native Apps in Teams
One of the best ways to increase productivity in a react native project is to automate the process of deploying builds you want to share internally in your team or with actual costumers via the app store. And you might be surprised at how easy it can actually be implemented when you have the right set of tools like GitHub Actions and EAS.