Fastlane as Continuous Integration
Today we will see the continuous integration for our iOS application. Firstly, what is it? It allows us to integrate, test, and find bugs during the whole process of the development. Usually we build, test and report at every pull/merge requests. To not do it manually, we use some server like Jenkins, Gitlab-ci or Bitrise.
How does it work? A simple CI is a simple script that :
- fetch dependencies
- build the app
- run the automated tests
- report linters, code coverage
To do this in iOS, we use Fastlane. The final fastfile is available at the end of the article
Fastlane
Fastlane is a powerful tool written in ruby. It automates all the not-so-cool things like the signing of your app or the upload to the store.
For our purpose we will use it to call our CI with a simple command. It will also allow us to run our CI in any platform, like our computer or our CI server.
Install
The first thing to do is to install Fastlane. We use Bundler to do so. Bundler will manage our versions for us, and allow us to have exactly the same version of Fastlane for every platform.
Create a Gemfile at the root of our project and add this in it
source "https://rubygems.org"
gem "fastlane"
gem "cocoapods"
gem "slather"
As we can see, we also add cocoapods and slather right here, for exactly the same reason as fastlane. We want to have the same tools with the same version for every development computer and every server.
To install them, we just have to execute
bundle install
We can now initialize our fastlane project.
Run in your terminal, in the root of your project
bundle exec fastlane init
We precede every command implying cocoapods, fastlane or slather by bundle exec to ensure it uses the same version of the tools.
When Fastlane ask you about what you want, set the manual setup. It will create a blank fastfile.
The fastfile is where we configure our actions using lanes. A lane is a simple suite of actions, and an action is a simple command like cocoapods or sh.
If we open our fastfile, (in Fastlane/fastfile) we can see
default_platform(:ios)
platform :ios do
desc "Description of what the lane does"
lane :custom_lane do
# add actions here: https://docs.fastlane.tools/actions
end
end
As we can see, fastlane works using some lanes.
To create a lane we just have to add
desc "My lane description"
lane :new_lane do
end
Where desc is the description, new_lane the name of our lane and the script will be between the do and the end.
Dependencies
Now that we have fastlane, it’s time to write our first step. Every step will be a lane, and in the end we will write a lane CI that launch all of them. We do that so we can run every action independently if needed (e.g.: to debug).
The first step of our CI will be to fetch the dependencies of our application. In our case we use Cocoapods, but it’s the same thing for Carthage.
In this example, I assume that you already have a project with cocoapods and a proper podfile.
Add in your fastfile
desc "Cocoapods"
lane :dependencies do
cocoapods
end
If you have any troubles, you can find all the options in this doc.
Build
Next step, we will build our application.
To do so, add in your fastfile
desc "Build"
lane :build do
build_ios_app(
scheme: "MyScheme",
export_method: "development"
)
end
Do not forget to change MyScheme with your current scheme. We build there in development not to have to worry about signing with certificates and provisioning profiles.
We can use this lane if we don’t have any test and don’t want to add some, which is a bad idea, but it’s not the subject of this post.
Test
For this step you need to have a test target. If not you can add one using file->new->Target->Unit Testing Bundle
Add to your fastfile
desc "Tests"
lane :test do
run_tests(
scheme: "MyScheme",
output_types: "junit",
output_directory: "build",
output_files: "test_result.xml"
)
end
Do not forget to change MyScheme with your current scheme. We set the output_types to junit and set a specific path for the output_files and output_directory to use them in the slather lane.
If we use this lane, we won’t need the build lane. The run_test will build our application so it would be redundant to use build then test lanes.
SwiftLint
Now that our CI does all the dependencies, build, test stuffs, it’s time to add some cool reports to show in our merge/pull requests.
The first one that is great to add, is a linter. We use SwiftLint, it’s the most popular one. To install it, we use cocoapods, to allow us to have the same version everywhere.
Add to your fastfile
desc "Report Linters from Pods"
lane :lint do
swiftlint(
executable: "Pods/SwiftLint/swiftlint",
output_file: "build/swiftlint.json",
ignore_exit_status: true,
reporter: "json"
)
end
Every output will be in the build folder. It allows us to ignore it directly in the .gitignore. As said before, we installed it from cocoapods, so we have to set the executable path.
We set the ignore_exit_status to true, to allow our CI to continue even with a critical error, so that we have all the reports in our hands.
Slather
Another report to have is the code coverage. The lane run_test already gives us one, but we want a prettier one, so we will use Slather.
To enable slather to gather the code coverage, go to your scheme->Edit Scheme->test->Options and enable Gather coverage for and set some targets with only your app target.
The second step is to add a .slather.yml to set some information
coverage_service: cobertura_xml
workspace: MyWorkspace.xcworkspace
xcodeproj: MyProject.xcodeproj
scheme: MyScheme
output_directory: "build/slather-report"
ignore:
- Pods/*
- MyProjectTests/*
- Generated/*
Do not forget to update MyWorkspace, MyProject, MyScheme and MyProjectTests with yours. Here, we set cobertura_xml as a service to allow an other tool to manage it. You can also add a coveralls export to have a pretty view of our coverage. We can set html directly if we want a simple html view that can be hosted in gitlab page for example.
Here is an example of the html export
Final fastfile
Now we have all our lanes ready to be called. The last step is to add a simple lane we called CI.
desc "CI"
lane :ci do
dependencies
test
codecoverage
lint
end
To test all our CI, just run
bundle exec fastlane ci
We can see all the reports in the build folder, and have a direct return in the output of the command.
Our fasfile should look like that
default_platform(:ios)
platform :ios do
desc "Cocoapods"
lane :dependencies do
cocoapods
end
desc "Build"
lane :build do
build_ios_app(
scheme: "MyScheme",
export_method: "development"
)
end
desc "Tests"
lane :test do
run_tests(
scheme: "MyScheme",
output_types: "junit",
output_directory: "build",
output_files: "test_result.xml"
)
end
desc "Report Linters from Pods"
lane :lint do
swiftlint(
executable: "Pods/SwiftLint/swiftlint",
output_file: "build/swiftlint.json",
ignore_exit_status: true,
reporter: "json"
)
end
desc "Slather"
lane :codecoverage do
slather(
use_bundle_exec: "true",
)
end
desc "CI"
lane :ci do
dependencies
test
codecoverage
lint
end
end
What’s next ?
This article shows you one of the simplest CI using Fastlane.
Obviously we can add some notifications using slack, mail. We can add some reports to be written directly as a comment in our merge/pull request like SwiftLint. We can add pre-check for our merge/pull requests using Danger.
Now that we have this CI, feel free to go to the fastlane doc and add new actions.
The next step will be to use it in our CI server.