Your guide to Danger Swift on CI
We were excited to automate static analysis (linting) and rigorous testing with Danger CI because we are committed to delivering stable and maintainable codebases for our clients here at Kin + Carta. 🧱
As covered in The missing handbook for CI/CD, code coverage and the number of linting warnings are helpful metrics for our engineering team to measure product health 📊. Many companies (including Kin + Carta!) use CI company-wide so improving our CI on one app can improve our approach on other projects too ⭐️!
We inherited a Danger Swift configuration running SwiftLint for a client iOS project but wanted to extend Danger further to supercharge our tooling. 🍄 We love pushing the boundaries of technology, so wanted to explore this together.
We extended Danger Swift’s official docs CI configuration on a sample project to run tests, track code coverage, analyse PRs, as well as run SwiftLint automatically ✨. We are excited to share our work because many team members found our CI setup useful in developing our sample app 😃.
PS: For those of you who haven’t worked with SwiftLint before, skimming their README to understand the tool to set it up locally could be helpful (optional though!).
We supplement Danger’s getting started guidelines with steps for M1 machines, updated Package configurations, plugin support and other helpful enhancements 💪.
Requirements ☑️
- We’ll use Swift Package Manager 📦 instead of Homebrew so that we can lock versions (Xcode 10+).
- We’ll use GitHub as our source control provider rather than Bitbucket / GitLab. The steps for other source control providers can be adapted on a case-by-case basis, and should be similar.
- A CI environment ☁️ to run the Danger builds. I’ve used TeamCity but the steps are very similar to other CI providers.
Article Agenda ⏱
- Installation 📲
- Create a Dangerfile ⚠️
- Import Danger Plugins 🔌 (optional!)
- Create a GitHub Danger bot 🤖
- Test Danger locally 📍
- Set up Danger on CI ⚙️
- Conclusion 🎬
To skip to the final working solution, you can clone ModularSlothCreator 🦥. Our project uses an extra Danger plugin (WeTransfer-iOS-CI) which we used for code coverage reporting 📈 and SwiftLint-ing PRs, but not deployment.
Note: For those of you wondering “What’s a Modular Sloth Creator ??” 😄, I’m adapting Apple’s Sample Code for DocC that used an app called Sloth Creator 🦥. I’m extending the Modular “SlothCreator” sample project from my previous post about DocC with CI to test Danger.
You may prefer to try a configuration without the plugin, so we’ve provided alternate steps for a more lightweight project.
Installation 📲
First, we run our Dangerfile locally before deploying to CI.
We are using Danger Swift rather than Danger JS for several reasons:
- We wanted to report code coverage with Danger, so we leveraged Danger Swift‘s wider iOS plugin ecosystem. I loved using WeTransfer-iOS-CI because it was very easy to set up and came with a lot of great features for free e.g. code coverage,
fastlane
integration, provisioning and App Store Connect deployments. We wanted to extend our DangerJS config with these extra features because Danger-Swift is now stable to use. - You may also prefer to stay within Xcode for a smoother developer experience. While editing our config
Dangerfile.swift
file in Xcode, we leverage Swift’s great features of type safety, optionals, and more. As an iOS developer, I really liked being able to write my CI configuration using the same language I used for dev and was able to set it up faster. - Type completion and static analysis in Xcode ensure your Dangerfile is valid before you deploy using
swift run danger-swift edit
. This gave me confidence in my deployments and saved time.
To install Danger-Swift correctly, we need to use Danger-JS as a dependency rather than Danger-Ruby(!!). Danger-Ruby is installed by default when we install danger-swift
through Homebrew 😅.
Install Danger-JS globally on your local machine with npm
/ yarn
:
npm install -g danger
Now, we can install Danger-Swift using SPM. For example, our ModularSlothCreator
project uses the following Package.swift
:
Our Package.swift
differs from Danger’s getting started documentation to enable builds on M1 machines. You may notice we’ve added a defaultLocalization
to our Package, and used a .dynamic
library to get Danger to build successfully (at the time of writing).
We’ve now got two projects, one Swift Package solely to integrate Danger and a separate iOS Xcode project.
I’ve used the main target name ModularSlothCreator
and appended -PRLinter
to clearly separate the main target from the Package. Feel free to adopt the same or a similar naming convention to help you navigate the codebase.
We are linking a stub called DangerProxy.swift
from our main target that only contains import Foundation
so that our Danger Package can link with our Swift codebase. Danger needs to be able to read the files to lint them 🪄.
swift build
installs Danger-Swift and its dependencies. If you ever run into issues, I suggest reading the Issues page on the Danger-Swift repo or closed PRs with similar bugs before filing a new Issue. There are lots of helpful and talented community members to help out.
From this point on you can use swift run danger-swift [cmd]
to run Danger locked correctly to your project. 😎
Create a Dangerfile ⚠️
A Dangerfile
defines our project’s Danger configuration as code.
If you want to run Danger without any plugins as a lightweight solution, create the following Dangerfile.swift
in the root folder for your project:
This Dangerfile generates warnings for large PRs, WIP PRs, and PRs with no description for PR linting and runs SwiftLint out of the box with Danger without any plugins.
If you’re happy to configure the CI a bit more, we can leverage WeTransfer-iOS-CI
which defines the following Dangerfile.swift
:
WeTransferPRLinter
does the same work as the above config and incorporates code coverage tracking! I really like the WeTransfer-iOS-CI because it is modular and very powerful. WeTransfer’s CI is one of the most popular Danger-Swift plugins (153 GitHub ⭐️s!).
ModularSlothCreator
imports WeTransfer-iOS-CI
as a Git submodule so we don't actually need to create a Dangerfile. You can skip the next section if you don’t want to use WeTransfer-iOS-CI, because we’ve now set up Danger. ⭐️
Import Danger Plugins 🔌 (optional!)
We need to follow additional set-up steps for Danger plugins, defined in their READMEs. WeTransfer-iOS-CI
current README.md outlines the following steps:
- Add as a submodule with the correct path
Submodules/WeTransfer-iOS-CI
. - Enable
fastlane
on your project. Import thefastlane/Fastfile
from this repo and trigger thetest
lane. - Add a run script to the main target:
./Submodules/WeTransfer-iOS-CI/BuildTools/swiftlint.sh
.
We can now authenticate our GitHub requests and test our local Danger configuration 🧪.
Note 🚨: Third-party plugin steps may change in the future, so double-check the latest plugin README before committing.
Create a GitHub Danger bot 🤖
Fun fact 🤗
We can skip creating a new email if we already use Gmail. The feature is called plus addressing. Simply sign up for a new GitHub account with your email address suffixed with a + and your identifier. e.g. pranav+dangerbot@gmail.com. 🎉
Sign up for a new GitHub account for your Danger bot to use exclusively. I used a plus addressing email address, but you may prefer to use a dedicated bot email.
Create a GitHub Personal Access Token for your bot account with the repo permission.
Copy the token, and save it for the next step.
Add the bot as a collaborator with Write access to your repository from your main GitHub account.
Test Danger locally 📍
In order to access the GitHub API with Danger, we’ll need to export our Danger GitHub token as an environment variable to authenticate our GitHub operations. We run danger-swift
from within the root folder as follows:
$ brew install mint # Only for WeTransfer-iOS-CI projects
$ cd ModularSlothCreator
$ bundle exec fastlane test # Only for WeTransfer-iOS-CI projects
$ export DANGER_GITHUB_API_TOKEN=<insert token here>
$ swift build
$ swift run danger-swift pr https://github.com/theappbusiness/ModularSlothCreator/pull/1
Great! We’ve now verified that Danger compiles locally. 🥳
For ModularSlothCreator
, bundle exec fastlane test
generates a .xcresult bundle that Danger uses for calculating code coverage and test metadata. This is a great time to validate our local fastlane
setup. We technically only use fastlane test
with WeTransfer-iOS-CI in this article, but I highly recommend using fastlane scan
on your PR workflows separately.
Once we’ve verified our Danger setup works as expected, we can deploy our configuration to CI with confidence ⬆️.
Set up Danger on CI ⚙️
Environment Variables 🌳
Now add your Danger Bot GitHub Personal Access token to your CI configuration as an envvar DANGER_GITHUB_API_TOKEN
.
Configure a CI envvar PULL_REQUEST_URL
with a dynamic URL to point to the PR URL. How you configure this will depend on your CI provider. In TeamCity, we nest envvars:
env.PULL_REQUEST_URL=https://github.com/theappbusiness/ModularSlothCreator/%teamcity.build.branch%
CI Build Steps 🪜
In these build steps, we are simply migrating what we ran with danger-swift local
locally to a danger-swift ci
command by leveraging envvars.
If you’re running Danger without WeTransfer-iOS-CI then you only need one build step to run a shell script:
CI build steps for WeTransfer-iOS-CI 📲
We tweak the configuration slightly to run WeTransfer-iOS-CI with fastlane
, and you may need to do a similar plugin-specific configuration for other Danger plugins.
We are writing shell scripts that run fastlane test
as above, however, we may need to install mint
in a different way on CI before running danger
. I’ve added it to the shell script, but you may prefer to use a Build Feature on your CI provider (or another CI installation method) and remove this line from Build step 1.
Build step 1:
Build step 2:
Do note that Danger configurations vary by CI provider and VCS provider, so reference the official docs to be safe. We used TeamCity CI for ModularSlothCreator
and adapted Bitrise workflow steps from the WeTransfer-iOS-CI repository.
Try triggering a build to check everything is hooked up correctly! 🙏
Conclusion 🎬
I hope you enjoyed learning about Danger Swift as much as I did writing this article! 🎉
I wrote this article to supplement Danger-Swifts official documentation, because I wanted to extend my configuration and help people set up faster. I hope that my pain saves you some sweat down the road 😅.
Danger Swift should now post to GitHub PRs without any extra work. I hope you enjoy the automation of tabulated code coverage reporting within every PR as much as I do 😍!
Ps. Danger edits summaries for later commits so that your PR doesn’t get spammed with comments ✍️.
We also get inlined PR comments for SwiftLint warnings 📝 out of the box. The great thing is Danger deletes warnings that have been fixed in later commits! Here’s an example for ModularSlothCreator
:
Commit Status Publishers ensure Danger PR checks pass before merging ⬆️.
I recommend Danger Swift on CI because the tool codifies and enforces best practices on projects through easy automation. As with most automation, it takes time to reap the benefits, so it’s worth the initial effort setting up for most projects.
Check out the real PR where the magic is all happening to see Danger Swift in action 🪄. I’ve left the SwiftLint warnings unaddressed for demo purposes but fix warnings on real projects!
I recommend iOS developers to learn and implement CI on projects because tooling is an important component in the app development ecosystem (raises project awareness!). It’s been a pleasure using Danger on our internal project at K+C and we’ve already addressed 1 code coverage regression! ✅
Ps. I‘m really proud to work somewhere where everybody strives to constantly push the boundaries of what’s possible. If that sounds like your cup of tea, why not join us?
I hope you found the article useful! 🙌 If you want to hear more from me, feel free to follow me on Twitter 🐤.
Thanks to Felipe Ricieri and Oliver Dew for providing feedback.