Continuous Integration and Continuous Delivery with SemaphoreCI and Fastlane for iOS apps
Tags: swift, swiftcicd, semaphoreci, tutorial
Published: 2019-08-12 17:01:14

When you develop mobile apps, **you manage the process of the app publication manually**, using **Xcode Organizer**, after following some steps like signing, test, build, archive and deploy your build, then change versions and send new builds again and again to the AppStore. 

If you generate your builds daily, **this process is tedious and tiring**. So, soon or later, you will ask yourself:  **— How could I automate all this proccess?** The answer to this question becomes essential to your development and deploy flow, it's essential to have a quick feedback loop to iterate when deploying your builds.

**Using SemaphoreCI and Fastlane it's a great choice to build products at high velocity**. Whenever you push your code to your remote repository, SemaphoreCI will automatically run your custom build, test and deploy pipeline.

So, let’s start playing with some configuration and set up our first continuous integration flow.


## Introducing the project

The steps that we will follow in this tutorial to use our Continuous Integration pipeline is going to look like this:

**1.** **Clone SemaphoreCI Swift demo project from Github**
**2.** **Configuring SemaphoreCI pipeline** 
**3.** **Understanding the configuration of the files in our project**
**4.** **Push our first build with Continuous Integration**


## Clone SemaphoreCI Swift demo project from Github

In order to continue with this tutorial, you should grab a copy of the SemaphoreCI demo project from GitHub and **clone it to your computer**.

We will use along the tutorial the following tools:
**1. Git**
**2. Xcode**
**3. Fastlane**
**4. SemaphoreCI**

```
git clone git@github.com:semaphoreci-demos/semaphore-demo-ios-swift-xcode.git
```

**Note:** I recommend to you, to **fork the project** and clone from your forked repo.


## Configuring SemaphoreCI pipeline

Go to your SemaphoreCI dashboard and **create a new project**. Select a repository `semaphore-demo-ios-swift-xcode`and connect to SemaphoreCI. 

![](https://amarildolucas.s3-sa-east-1.amazonaws.com/SemaphoreCI/Captura+de+Tela+2019-08-12+às+09.42.26.png)

At this moment, you will see an empty connected project.  This is because we didn’t push anything to the project yet.

![](https://amarildolucas.s3-sa-east-1.amazonaws.com/SemaphoreCI/Captura+de+Tela+2019-08-12+às+11.18.34.png)

On your cloned project, open `.semaphore/semaphore.yml` file. This is the configuration file to run your pipeline every time that you push your code. The file is well commented and you need to change it according to your own necessities.

**.semaphore/semaphore.yml**
```
# Use the latest stable version of Semaphore 2.0 YML syntax:
version: v1.0

# Name your pipeline. In case you connect multiple pipelines with promotions,
# the name will help you differentiate between, for example, a CI build phase
# and delivery phases.
name: Semaphore iOS Swift example with Fastlane

# An agent defines the environment in which your code runs.
# It is a combination of one of available machine types and operating
# system images.
# See https://docs.semaphoreci.com/article/20-machine-types
# and https://docs.semaphoreci.com/article/120-macos-mojave-image
agent:
  machine:
    type: a1-standard-4
    os_image: macos-mojave

# Blocks are the heart of a pipeline and are executed sequentially.
# Each block has a task that defines one or more jobs. Jobs define the
# commands to execute.
# See https://docs.semaphoreci.com/article/62-concepts
blocks:
  - name: Run tests
    task:
      # Set environment variables that your project requires.
      # See https://docs.semaphoreci.com/article/66-environment-variables-and-secrets
      env_vars:
        - name: LANG
          value: en_US.UTF-8
      prologue:
        commands:
          # Download source code from GitHub:
          - checkout

          # Restore dependencies from cache. This command will not fail in
          # case of a cache miss. In case of a cache hit, bundle  install will
          # complete in about a second.
          # For more info on caching, see https://docs.semaphoreci.com/article/68-caching-dependencies
          - cache restore gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),gems-$SEMAPHORE_GIT_BRANCH-,gems-master-
          - bundle install --path vendor/bundle
          - cache store gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock) vendor/bundle
      jobs:
        - name: Fastlane test
          commands:
            # Select an Xcode version, for available versions
            # See https://docs.semaphoreci.com/article/120-macos-mojave-image
            - bundle exec xcversion select 10.3
            # Run tests of iOS and Mac app on a simulator or connected device
            # See https://docs.fastlane.tools/actions/scan/
            - bundle exec fastlane test

  - name: Build app
    task:
      env_vars:
        - name: LANG
          value: en_US.UTF-8
      prologue:
        commands:
          - checkout
          - cache restore gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),gems-$SEMAPHORE_GIT_BRANCH-,gems-master-
          - bundle install --path vendor/bundle
          - cache store gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock) vendor/bundle
      jobs:
        - name: Fastlane build
          commands:
            - bundle exec xcversion select 10.3
            # Gym builds and packages iOS apps.
            # See https://docs.fastlane.tools/actions/build_app/
            - bundle exec fastlane release

```

If you try to push your code at this moment, your pipeline wouldn’t work yet and will fail. You need to **configure your keys and secrets** to allow SemaphoreCI to integrate with your project. 

To add a deploy key to your Semaphore project you will need to create secrets to add to your pipeline configuration file. So, let's store the deploy key as a secret file in the SemaphoreCI environment.

If you have not installed the sem command-line tool that we are using in this tutorial, this is a good time to install it, see the [sem reference](https://docs.semaphoreci.com/article/53-sem-reference) and follow the instructions.


```
$ sem create secret your-fastlane-ios-certificates-repo -f id_rsa_semaphoreci:/Users/semaphore/.keys/your-fastlane-ios-certificates-repo
```

You will see the following message on the terminal, `Secret ‘fastlane-ios-certificates-repo’ created.`. This means that everything worked as expected.

`id_rsa_semaphoreci` it's your ssh file for SemaphoreCI. Upload it to GitHub so you can deploy your code without further problems. 

In our case:

```
ssh-keygen -t rsa -f id_rsa_semaphoreci
cd ~/.ssh/id_rsa_semaphoreci.pub
```

**Copy and paste to Github SSH keys configuration.**

Next, **add the URL for the certificates repository and the encryption password as environment variables** that will be accessible in SemaphoreCI. I recommend also adding the App Store developer account’s credentials to the same secret.

```
$ sem create secret fastlane-env \
  -e MATCH_GIT_URL=“<your ssh git url>” \
  -e MATCH_PASSWORD=“<password for decryption>” \
  -e FASTLANE_USER=“<App Store developer’s Apple ID>” \
  -e FASTLANE_PASSWORD=“<App Store developer’s password>”
```

After run this command, you will find your keys in configuration secrets inside SemaphoreCI dashboard.

![](https://amarildolucas.s3-sa-east-1.amazonaws.com/SemaphoreCI/Captura+de+Tela+2019-08-12+às+10.37.36.png)

Updates your `.semaphore/semaphore.yml` file with your keys and secrets.

```
jobs:
        - name: Fastlane build
        commands:
            - chmod 0600 ~/.keys/*
            - ssh-add ~/.keys/*
            - bundle exec fastlane release
secrets:
        - name: fastlane-env
        - name: fastlane-ios-certificates-repo
```


## Understanding the Fastlane Configuration

When you open the demo project folder, you will see that you have a fastlane folder with some files inside. This folder was added after we installed and run fastlane for the first time. To understand more about the process of fastlane installation and configuration you should read this [introductory article](https://docs.fastlane.tools/getting-started/ios/setup/). 

Let's go with the steps that usually you will take.

**1. Install fastlane**
**2. Configure fastlane files**
**3. Test if everything is working**

As fastlane describes in their own website:

> Fastlane is the easiest way to automate beta deployments and releases for your iOS apps. It handles all tedious tasks, like generating screenshots, dealing with code signing, and releasing your application.  

It also supports other platforms.

Install the latest Xcode command line tools if you haven’t installed yet.

```
xcode-select —install
```

Install fastlane.

```
# Using RubyGems
sudo gem install fastlane -NV

# Alternatively using Homebrew
brew cask install fastlane
```

After Fastlane installation, run the following command. 

```
fastlane init
```

**Fastlane will automatically detect your project** and ask for any missing information. In our case, these folders and files installed by fastlane exists as we downloaded from the demo project in Github. Depending on what kind of setup you choose, different files will be set up for you out of the box.

The most interesting file is `fastlane/Fastfile`, which contains all the information that is needed to distribute your app.

Our fastlane current folder. Again, the files are well commented, so I will not give further explanations about each of them.

![](https://amarildolucas.s3-sa-east-1.amazonaws.com/SemaphoreCI/Captura+de+Tela+2019-08-12+%C3%A0s+09.34.37.png)

**fastlane/Appfile**
```
# For more information about the Appfile, see:
#     https://docs.fastlane.tools/advanced/#appfile

app_identifier "***" # The bundle identifier of your app
apple_id "***" # Your Apple email address
team_id "***" # Developer Portal Team ID
```

**fastlane/Fastfile**
```
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#     https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
#     https://docs.fastlane.tools/plugins/available-plugins
#

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform(:ios)

platform :ios do

  before_all do
    # installed via the semaphore plugin with `fastlane add_plugin semaphore`
    setup_semaphore
  end

  desc "Run Tests"
  lane :test do
    scan
  end

  desc "Deploy a new version to the App Store"
  lane :release do
    match(type: "appstore")
    gym(export_method: "app-store") # Build your app - more options available
    deliver(force: true)
  end
end

```

**fastlane/Gymfile**
```
# For more information about this configuration visit
# https://github.com/fastlane/fastlane/tree/master/gym#gymfile
#
# In general, you can use the options available
# fastlane gym --help

# Remove the # in front of the line to enable the option

scheme "HelloWorld"

sdk "iphoneos12.4"

output_directory "./"
```

**fastlane/Matchfile**
```
git_url("https://github.com/amarildolucas/fastlane_certficates")

storage_mode("git")

type("appstore") # The default type, can be: appstore, adhoc, enterprise or development

# app_identifier(["com.amarildolucas.com"])
# username("***") # Your Apple Developer Portal username

# For all available options run `fastlane match --help`
# Remove the # in the beginning of the line to enable the other options

# The docs are available on https://docs.fastlane.tools/actions/match
```

**fastlane/Pluginfile**
```
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!

gem 'fastlane-plugin-semaphore'

```

This is everything we need to set up and deploy our first build with continuous integration. The code is very specific and self-explanatory. You just defined 2 different lanes, one for your test deployment, one for App Store release. To release your app in the App Store, all you have to do now is push your code. 

**Note:** Ensure that Xcode "automatically manage signing" is unchecked in Xcode. And that your Signing (Release) contains the match AppStore provisioning profile selected. 


## Some considerations

### Gemfile 

It is recommended that you use a Gemfile to define your dependency on fastlane. This will clearly define the used fastlane version, and its dependencies, and will also speed up using fastlane.

Our current Gemfile:

```
source 'https://rubygems.org'

gem "fastlane"
gem "xcode-install"
gem "cocoapods"

plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
```

 
### Setting up Fastlane Match

You can use fastlane match to manage code signing. We use it in our fastlane setup.

To set up the match, you’ll need a private Git repository. Follow the  [documentation](https://docs.fastlane.tools/actions/match/) and run the init command from the root directory of our project and follow the instructions shown in the terminal.

```
bundle exec fastlane match init
```

In our case, our Matchfile exists from the cloned demo project in Github. So, run `fastlane match`  without init to get all required keys, certificates and provisioning profiles installed to your project. 🙌

**Note:**
> Private information like API keys or deploy credentials shouldn’t be written in the pipeline definition file or elsewhere committed to source control. On Semaphore you define these values as *secrets* using either the sem CLI or through the web interface (Configuration part of the sidebar -> Secrets). Secrets are shared by all projects in the organization. **— Semaphore Team**  

Build your project and run fastlane to check if everything that we did it until now is working as expected.

![](https://amarildolucas.s3-sa-east-1.amazonaws.com/SemaphoreCI/Captura+de+Tela+2019-08-12+%C3%A0s+10.26.41.png)


If you see the message `[10:28:38]: fastlane.tools finished successfully 🎉`, celebrate 🎉.

**Now, we just need to push our code to GitHub, and SemaphoreCI will run the CI/CD pipeline for us.**

```
git add -A
git commit -m "setup semaphore"
git push origin master
```

After you push your code to master branch in GitHub, your semaphore pipeline will run for the first time. If you click in the commit link, you can see all the processes (steps) in a beautiful layout components separated the steps of your pipeline configuration.

![](https://amarildolucas.s3-sa-east-1.amazonaws.com/SemaphoreCI/Captura+de+Tela+2019-08-12+%C3%A0s+11.58.59.png)

**Voilá! Now you just need to continuously push code to your branches and App Store Connect processes the build, and it becomes available in TestFlight and you will be able to select it for your new iOS app version.**

## Conclusion

There is much more that you can do with automated deployments. CI/CD pipeline has a lot of advantages, more than from the steps we follow through this tutorial. You can add more steps to your pipeline configuration, like get your CHANGELOG file, push messages to Slack, automate build number incrementation, dSYM uploads to Crashlytics, etc. 

After you take some time configuring, all this stuff will help you stay focused on writing code rather to manually deliver your app every time. So, your build updates are not stressful and tedious. You can inspect your code by adding tools in the process, for example to run static code reviews, linting, tests or any other important step that you could automate.

**However, start with simple things first.** If you've made it this far, congrats! 😃 I hope you found this post helpful. 

Want to deliver continuously your iOS applications? Check out[Build, test and deliver your iOS apps with Semaphore CI/CD](https://semaphoreci.com/product/ios).