Adding a linter, SwiftLint

Linting your code is an essential step in ensuring the style is consistent across contributors but also over time. Alongside Jazzy, realm also maintains the excellent SwiftLint tool. It is written in Swift and installable through Swift Package Manager (SPM). Now, SPM is not able to install CLI tools. We often turn to Gems or Homebrew to install those. Luckily, thanks to the vibrant Swift community, we can use Mint, the pure SPM for CLI tools.

  1. In the .gitlab-ci.yml file, you can add the following to create a lint step:
test:
image: swift:4.2.1
script:
- swift package update
- swift test

## Add linter step
lint:
image: swift:4.2.1
before_script:
# Keep the current directory in a variable
- export PWD=$(pwd)
# Install mint with the Swift PM method
- git clone https://github.com/yonaskolb/Mint.git /tmp/Mint
- cd /tmp/Mint
- swift run mint install yonaskolb/mint

# Install SwiftLint with mint
- mint install realm/SwiftLint
- cd $PWD

script:
- swiftlint
  1. You can see that the important part of the script is actually in before_script, where mint will be installed, and we’ll use mint to install swiftlint from realm/SwiftLint.
  1. The script phase will be simply using swiftlint.
  2. Commit those changes, and push your code to GitLab: both lint and test will run, and will succeed if your code is appropriately written.
You can find more information about GitLab’s CI configuration at https://docs.gitlab.com/ee/ci/yaml/README.html

What you will encounter now is that the build is quite slow, stretching into minutes, as it is always pulling and building Mint and SwiftLint from scratch, from their GitHub repositories.

In order to overcome the newly added slowness, you should consider creating a custom Docker image that contains the full toolchain. One of the great benefits of Docker is in ensuring the consistency of a particular environment. I went through that exercise and published a swift-ci image on Docker hub that contains both SwiftLint and Mint.

The Dockerfile is straightforward:

FROM swift:4.2.1
RUN git clone https://github.com/yonaskolb/Mint.git /tmp/Mint
WORKDIR /tmp/Mint
RUN swift run mint install yonaskolb/mint
RUN mint install realm/SwiftLint

We moved all the commands from the CI before_script step into a single docker image.

Following this, you can run the build's tag and push commands:

$ docker build .
Sending build context to Docker daemon 2.048kB
Step 1/5 : FROM swift:4.2.1
---> e863e310b19f
Step 2/5 : RUN git clone https://github.com/yonaskolb/Mint.git /tmp/Mint
---> Using cache
---> 9b3d8ae04db2
Step 3/5 : WORKDIR /tmp/Mint
---> Running in 38496a152ac9
Removing intermediate container 38496a152ac9
---> 40203dfb1956
Step 4/5 : RUN swift run mint install yonaskolb/mint
---> Running in 544d333228ea
Fetching https://github.com/onevcat/Rainbow.git
Fetching https://github.com/kylef/Spectre.git
Fetching https://github.com/jakeheis/SwiftCLI
Fetching https://github.com/apple/swift-package-manager.git
Fetching https://github.com/kylef/PathKit.git
Completed resolution in 7.10s
Cloning https://github.com/jakeheis/SwiftCLI
Resolving https://github.com/jakeheis/SwiftCLI at 5.1.3
Cloning https://github.com/apple/swift-package-manager.git
Resolving https://github.com/apple/swift-package-manager.git at 0.2.0
Cloning https://github.com/kylef/Spectre.git
Resolving https://github.com/kylef/Spectre.git at 0.8.0
Cloning https://github.com/kylef/PathKit.git
Resolving https://github.com/kylef/PathKit.git at 0.9.1
Cloning https://github.com/onevcat/Rainbow.git
Resolving https://github.com/onevcat/Rainbow.git at 3.1.4
Compile clibc libc.c
Compile Swift Module 'SwiftCLI' (20 sources)
Compile Swift Module 'Rainbow' (11 sources)
Compile Swift Module 'SPMLibc' (1 sources)
Compile Swift Module 'PathKit' (1 sources)
Compile Swift Module 'POSIX' (11 sources)
Compile Swift Module 'Basic' (37 sources)
Compile Swift Module 'Utility' (19 sources)
Compile Swift Module 'MintKit' (9 sources)
Compile Swift Module 'MintCLI' (9 sources)
Compile Swift Module 'Mint' (1 sources)
Linking ./.build/x86_64-unknown-linux/debug/mint
Finding latest version of mint
Resolved latest version of mint to 0.11.2
Cloning https://github.com/yonaskolb/mint.git 0.11.2...
Building Mint Package with SPM...
Installing Mint...
Installed Mint 0.11.2
Linked mint 0.11.2 to /usr/local/bin.
Removing intermediate container 544d333228ea
---> 0e205b06c84b
Step 5/5 : RUN mint install realm/SwiftLint
---> Running in 0cc1b6f74d82
Finding latest version of SwiftLint
Resolved latest version of SwiftLint to 0.28.2
Cloning https://github.com/realm/SwiftLint.git 0.28.2...
Building SwiftLint Package with SPM...
Installing SwiftLint...
Installed SwiftLint 0.28.2
Linked swiftlint 0.28.2 to /usr/local/bin.
Removing intermediate container 0cc1b6f74d82
---> 0da15117fc71
Successfully built 0da15117fc71
$ docker tag 0da15117fc71 flovilmart/swift-ci:4.2.1
$ docker push flovilmart/swift-ci:4.2.1

With this published module, we can now have a very lean .gitlab-ci.yml:

lint:
image: flovilmart/swift-ci:latest
script:
- swiftlint
test:
image: swift:4.2.1
script:
- swift package update
- swift test

It is only a matter of time before popular tools such as SwiftLint and Jazzy (which, at the time of writing, don’t support generation on Linux) jump on the Docker bandwagon and provide fully featured images that ultimately help to speed up the development flow.

In the swift-ci Docker image lives Mint, which can help install tools such as Sourcery, which was discussed in earlier chapters.

With this configuration, the linter step is down to a much faster time, purely the time to clone the code and lint, as we saved all the compilation time by migrating to Docker.