Setup CI pipeline for iOS projects on gitlab.com

Overview

  1. Setup CI pipeline with .gitlab-ci.yml
  2. Adding badges for pipeline status and code coverage

Prerequisite

  • A macOS machine with Xcode and Xcode command line tools installed
  • The above macOS machine is registered as a GitLab runner
  • An iOS app with a testing scheme with test cases
  • Tools
    • bundler (optional, if you have dependency for Ruby gems like cocoapods/fastlane, etc.)
    • jq (optional, for processing the code coverage report)
    • brew install jq

Key Terms of GitLab CI/CD

  • Pipelines are the top-level component of continuous integration, delivery, and deployment.

  • Jobs defines what to do, executed by runners

  • Stages defines when to run the jobs

Setup CI pipeline with .gitlab-ci.yml

stages:
  - prebuild
  - build

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - my-project-path/.bundle
    - my-project-path/vendor

install_dependencies:
  stage: prebuild
  script:
    - unset cd
    - cd my-project-path
    - bundle config set --local deployment 'true'
    - bundle install
  tags:
    - macos_11-2-3
    - xcode_12-4
    - ios_14-4

build_project:
  stage: build
  script:
    - unset cd
    - cd my-project-path
    - xcodebuild clean build test -project my-project.xcodeproj -scheme "CI" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
    - xcrun xccov view --report --json DerivedData/my-project-path/Logs/Test/*.xcresult > xcresult.json
    - cat xcresult.json | jq ".lineCoverage" -j | awk '{printf "XCTEST_COVERAGE=%0.2f%%\n",$1*100}'
  tags:
    - macos_11-2-3
    - xcode_12-4
    - ios_14-4

Structure of the configuration file:

line 1-3: defined a pipeline with 2 stages, prebuild and build

line 5-9: defined the paths that will be cached between the jobs

line 11-21: defined the install_dependencies Job

line 23-34: defined the build_project Job

line 28: build and test the Xcode project

line 29-30: gather the code coverage stat

Cache the dependencies between Jobs

  • my-project-path/.bundle is storing the bundle config
  • my-project-path/.vendor is storing the installed gems
  • ${CI_COMMIT_REF_SLUG} is the branch or tag name for which project is built

Without cache, the gems installed in the prebuild stage will be deleted when the build stage is executed, even the Jobs are executed on the same machine

The example will share the caches across the same branch

The use of unset cd

when rvm is used, it will redefine the cd command as below:

cd () 
{ 
    __zsh_like_cd cd "$@"
}
  • When the cd command is used in the Job, it will throw ERROR: Build failed with: exit status 1 and exit immediately
  • unset cd is used to reset cd to be the shell builtin command
    • it can be added before the step that used the cd command (as in the example)
    • or can be added in .bash_profile

Other points to note

  • the tags must match the configs in the Runners section in gitlab.com -> project settings -> CI/CD
  • the file .gitlab-ci.yml should be placed in the root of the git repo
  • the DerivedData path is set relative to the Xcode project

Adding badges for pipeline status and code coverage

Pipeline Status Badge

To configure the pipeline status:

  • gitlab.com -> project settings -> General -> Expand the Badges Section
  • Add Badges with following settings:
    • Name: Pipeline Status
    • Link: https://gitlab.com/%{project_path}/-/commits/%{default_branch}
    • Badge image URL: https://gitlab.com/%{project_path}/badges/%{default_branch}/pipeline.svg

Code Coverage Badge

  1. Get the code coverage report in JSON format after the project is built

    xcrun xccov view --report --json DerivedData/my-project/Logs/Test/*.xcresult > xcresult.json

  2. Print the code coverage to the job log

    cat xcresult.json | jq ".lineCoverage" -j | awk '{printf "XCTEST_COVERAGE=%0.2f%%\n",$1*100}'

the above line is to

  • get the lineCoveragefield from the JSON
  • multiply the value by 100
  • convert the value in percentage
  • print the value with 2 decimal places

noted that the percentage sign % must be included for Test coverage parsing

  1. Set the Test coverage parsing regular expression to grep the result in step 2.
  • gitlab.com -> project settings -> CI/CD -> Expand the General pipelines Section
  • In Test coverage parsing, fill in XCTEST_COVERAGE=(\d+.\d+%)
  1. Add the badge like pipeline status
  • gitlab.com -> project settings -> General -> Expand the Badges Section
  • Add Badges with following settings:
    • Name: Code Coverage
    • Link: https://gitlab.com/%{project_path}/-/commits/%{default_branch}
    • Badge image URL: https://gitlab.com/%{project_path}/badges/%{default_branch}/pipeline.svg

References

Concepts

Configurations

Caching dependencies between jobs

Badges

Troubleshoot

20