How to set up a CI CD pipeline for your iOS app using fastlane and GitHub Actions?
Original Article Published At YourQuorum
Such a things are done rather sometimes. Fortunately if a few times each year. That is the reason I need to think back the cycle how it’s done without fail.
Once more I chose to compose an instructional exercise once, so next time I didn’t need to research. Most likely that would be useful additionally for you.
Introduction
What we have
iOS measured project splitted into neighborhood SPM submodules.
What we going to do
- Arrangement SwiftLint for static code examination
- Arrangement Fastlane as a partner for various types of occupations
- Arrangement GitHub Activities CI/Album pipeline for running tests
Project structure
Swift Package Manager
I’ve referenced that the undertaking is isolated into neighborhood submodules with SPM as reliance supervisor.
Why splitting a project?
It implements composing particular code that can be incorporated later in different activities.
It’s likewise versatile on the off chance that the group unexpectedly develops. Simply envision that the client unexpectedly recruits 10 more devs and you need to coordinate their work in such a manner they wouldn’t disrupt every others work.
Why use local submodules instead of separate repos?
Separate repos carry above and bother with pushing changes that you should keep away from on beginning phases of dynamic improvement of an undertaking.
Nearby submodule is totally free. It behaves like a nearby dir so you can transform it without any problem. While it exists under git variant control of the fundamental task repo.
Simultaneously it’s remembered for the fundamental Xcode project as a completely fledged separate bundle with every one of those public/inward access limitations. So it very well may be moved to a different repo whenever with next to no progressions of the code.
For what reason do I actually have XCWorkspace and Units assuming that I use SPM?
Tragically I actually need to utilize CocoaPods to introduce SwiftLint in the most helpful manner.
Nearby SPM modules
How to add nearby SPM modules?
Xcode → New → Quick Bundle → Add to work area and undertaking
Project settings → Targets → General → Systems, Libraries → Add your bundle here
SwiftLint
We will run SwiftLint from two spots, as a matter of fact:
Locally, during our nearby improvement process. Each time just before the venture is fabricated utilizing assemble stage script.
From a distance, on our CI/Disc server utilizing Fastlane
Installation
There are multiple ways of introducing SwiftLint including neighborhood establishment through blend.
CocoaPods establishment really introduces paired with conditions, not the source code of it. It’s ideal way or establishment as it will permit to utilize a similar SwiftLint rendition locally for all colleagues and run a similar paired on CI/Disc server too.
In view of true docs, establishment is just about as straightforward as
- Add a line to a Podfile:
pod 'SwiftLint'
2. Go to Project → Targets → Fabricate Stages and add a stage run script (marginally unique in relation to the authority docs):
if [ -z "$CI" ]; then
${PODS_ROOT}/SwiftLint/swiftlint
fi
It will run the content just in if “CI” contention is absent. So SwiftLint construct content will run locally before the form, yet will be avoided on CI server.
UPD: Really, it isn’t so much that simple since CI env vars are not available as of now. To pass env vars to fabricate stage script on CI really look at this article.
3. Add .swiftlint.yml config record to the root project dir (where you have .xcodeproj document). That is the dir SwiftLint will be sent off from.
In .swiftlint.yml config document we can incorporate or prohibit our SPM nearby bundles too:
# By default, SwiftLint uses a set of sensible default rules you can adjust:
#disabled_rules: # rule identifiers turned on by default to exclude from running
# - colon
# - comma
# - control_statement
#opt_in_rules: # some rules are turned off by default, so you need to opt-in
# - empty_count # Find all the available rules by running: `swiftlint rules`
# Alternatively, specify all rules explicitly by uncommenting this option:
# whitelist_rules: # delete `disabled_rules` & `opt_in_rules` if using this
# - empty_parameters
# - vertical_whitespace
included: # paths to include during linting. `--path` is ignored if present.
- Portfolius-iOS
- PortfoliusCore
- NetworkOperator
- ReduxStore
- PortfoliusBackendAPI
- SwiftUIExtensions
- UIKitExtensions
- FoundationExtensions
excluded: # paths to ignore during linting. Takes precedence over `included`.
- Carthage
- Pods
- Source/ExcludedFolder
- Source/ExcludedFile.swift
- Source/*/ExcludedFile.swift # Exclude files with a wildcard
analyzer_rules: # Rules run by `swiftlint analyze` (experimental)
- explicit_self
# configurable rules can be customized from this configuration file
# binary rules can set their severity level
force_cast: warning # implicitly
force_try:
severity: warning # explicitly
# rules that have both warning and error levels, can set just the warning level
# implicitly
line_length: 110
# they can set both implicitly with an array
type_body_length:
- 300 # warning
- 400 # error
# or they can set both explicitly
file_length:
warning: 500
error: 1200
# naming rules can set warnings/errors for min_length and max_length
# additionally they can set excluded names
type_name:
min_length: 4 # only warning
max_length: # warning and error
warning: 40
error: 50
excluded: iPhone # excluded via string
allowed_symbols: ["_"] # these are allowed in type names
identifier_name:
min_length: # only min_length
error: 3 # only error
excluded: # excluded via string array
- id
- URL
- GlobalAPIKey
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown, github-actions-logging)
4. We can add SwiftLint paired to .git repo. So SwiftLint will be accessible just subsequent to cloning of the repo and there will be compelling reason need to run Case Introduce once more. That is somewhat convenient for onboarding new colleagues.
Fastlane
We will arrangement a few paths for later use in our CI/Disc pipeline occupations.
Beginning arrangement
Simply go to true docs iOS area and play out the arrangement.
I chose to attempt fastlane quick, which is practically something very similar with just contrast that it permits to compose FastFile in quick. What’s more, it’s in beta at this point.
So as indicated by the Fastlane quick docs:
- Run init command in project dir:
fastlane init swift
2. Then open recently made fastlane’s xcode project:
[project]/fastlane/swift/FastlaneRunner/FastlaneRunner.xcodeproj
Furthermore, we are right here. There is a Fastfile.swift — a spot for characterizing your paths.
Create lanes
Presently we will make a few paths that we will later use as independent positions.
SwiftLint Lane
We will run it at the absolute starting point before we construct the venture
func swiftLintLane() {
desc("Run SwiftLint")
swiftlint(configFile: ".swiftlint.yml",
strict: true,
ignoreExitStatus: false,
raiseIfSwiftlintError: true,
executable: "Pods/SwiftLint/swiftlint"
)
}
It’s severe so it doesn’t permit alerts and it bombs the entire work in the event that quick build up could do without your code.
Build Lane
It will run straightaway and will just form the venture, without taking any kind of action else. Its outcome will be a curio that will be utilized further in test-related positions.
Doing like this we can fabricate once as an obstructing undertaking and afterward just run test-related positions in equal.
As you would have seen, it passes “CI=true” as xcargs, so it will turn off our construct stage SwiftLint run script that we designed previously. We simply don’t have to run it two times.
func buildLane() {
desc("Build for testing")
scan(workspace: "Portfolius-iOS.xcworkspace",
derivedDataPath: "derivedData",
buildForTesting: true,
xcargs: "CI=true")
}
Unit tests Lane
For my situation, PortfoliusCore contains revival activities, center parts of the application with their minimizers. So I consider them as unit trial of my task.
func unitTestLane() {
desc("Run unit tests")
scan(workspace: "Portfolius-iOS.xcworkspace",
onlyTesting: ["PortfoliusCoreTests"],
derivedDataPath: "derivedData",
testWithoutBuilding: true)
}
Integration tests Lane
As per the undertaking structure, the actual application relies upon other SPM modules, consolidating them generally together, so I believe Portofius-iOSTests to be reconciliation trial of the task.
func integrationTestLane() {
desc("Run integration tests")
scan(workspace: "Portfolius-iOS.xcworkspace",
onlyTesting: ["Portfolius-iOSTests"],
derivedDataPath: "derivedData",
testWithoutBuilding: true)
}
UI tests Lane
As per the undertaking structure, application UI is executed in the highest task, so UI tests are additionally there.
func uiTestLane() {
desc("Run UI tests")
scan(workspace: "Portfolius-iOS.xcworkspace",
onlyTesting: "Portfolius-iOSUITests",
derivedDataPath: "derivedData",
testWithoutBuilding: true)
}
Build Fastlane binary
Presently we ought to assemble the twofold for Fastlane. Just Cmd+B. Definitely that is the fundamental distinction among Quick and Ruby execution. You need to modify to apply changes.
Add test targets of underlying SPM modules
Presently we ought to add test focuses of hidden SPM modules to the fundamental undertaking’s test plot.
In any case we basically will not have the option to run tests for the hidden modules.
Note. Presently we want to run tests for them, since this time, we added them as neighborhood SPM modules. When we move them to a different repo we could arrangement CI/Compact disc there and won’t have to run tests for them in the principal project.
Github Actions CI/CD
Pipelines are called work processes in Github Activities world. Presently we will make the work process that will utilize those paths we characterized on the past step.
Our workflow will do the following:
On pull solicitation to dominate branch
Run SwiftLintLane
Run BuildLane and transfer essential outcomes as antiquities
Test occupations will begin in equal. Every one of them will download required antiques and run tests.
Occupations will be subject to one another, so the following stage will begin if past completions effectively.
Make work process
Go to Activities → New Work process → Arrangement new quick work process
Workflow config
name: Swift
on:
pull_request:
branches: [ master ]
jobs:
swiftLint:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Install Bundle
run: bundle install
- name: Run swiftlint
run: bundle exec fastlane swiftLintLane
build:
needs: swiftLint
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Install Bundle
run: bundle install
- name: Build
run: bundle exec fastlane buildLane
- name: Upload build
uses: actions/upload-artifact@v2
with:
name: build
path: derivedData/Build/Products/Debug-iphonesimulator/Portfolius-iOS.app
- name: Upload Runner
uses: actions/upload-artifact@v2
with:
name: runner
path: derivedData/Build/Products/Debug-iphonesimulator/Portfolius-iOSUITests-Runner.app
- name: Upload Core Tests
uses: actions/upload-artifact@v2
with:
name: coreTests
path: derivedData/Build/Products/Debug-iphonesimulator/PortfoliusCoreTests.xctest
unitTests:
needs: build
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Download core tests
uses: actions/download-artifact@v2
with:
name: coreTests
path: derivedData/Build/Products/Debug-iphonesimulator/PortfoliusCoreTests.xctest
- name: Install Bundle
run: bundle install
- name: Run unit tests
run: bundle exec fastlane unitTestLane
integrationTests:
needs: build
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Download build
uses: actions/download-artifact@v2
with:
name: build
path: derivedData/Build/Products/Debug-iphonesimulator/Portfolius-iOS.app
- name: Install Bundle
run: bundle install
- name: Run integration tests
run: bundle exec fastlane integrationTestLane
uiTests:
needs: build
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Download build
uses: actions/download-artifact@v2
with:
name: build
path: derivedData/Build/Products/Debug-iphonesimulator/Portfolius-iOS.app
- name: Download runner
uses: actions/download-artifact@v2
with:
name: runner
path: derivedData/Build/Products/Debug-iphonesimulator/Portfolius-iOSUITests-Runner.app
- name: Install Bundle
run: bundle install
- name: Run ui tests
run: bundle exec fastlane uiTestLane
Understanding workflow config
Characterizes the trigger for the work process to begin. Here we will run it on pull solicitation to dominate branch. We can have various work processes with various arrangement of occupations for various branches
on:
pull_request:
branches: [ master ]
We run this move toward each occupation since he’s a necessary person for Fastlane:
- name: Install Bundle
run: bundle install
Implies that the occupation relies upon the gig “swiftLint” and will hang tight for fruitful finish. On the off chance that occupation falls flat, all reliant positions will be skipped:
needs: swiftLint
At construct work we transfer the form as antiquity with name “fabricate” utilizing exceptional github activity “transfer relic”. Btw, “derivedData” a piece of the way is the one we referenced in our paths as “derivedDataPath”:
- name: Upload build
uses: actions/upload-artifact@v2
with:
name: build
path: derivedData/Build/Products/Debug-iphonesimulator/Portfolius-iOS.app
We additionally transfer UI Tests Sprinter. He is a person, required for running UI tests:
- name: Upload Runner
uses: actions/upload-artifact@v2
with:
name: runner
path: derivedData/Build/Products/Debug-iphonesimulator/Portfolius-iOSUITests-Runner.app
What’s more, we additionally transfer trial of basic SPM module:
- name: Upload Core Tests
uses: actions/upload-artifact@v2
with:
name: coreTests
path: derivedData/Build/Products/Debug-iphonesimulator/PortfoliusCoreTests.xctest
Test occupations are practically something similar, so we should view one of them:
uiTests:
needs: build
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Download build
uses: actions/download-artifact@v2
with:
name: build
path: derivedData/Build/Products/Debug-iphonesimulator/Portfolius-iOS.app
- name: Download runner
uses: actions/download-artifact@v2
with:
name: runner
path: derivedData/Build/Products/Debug-iphonesimulator/Portfolius-iOSUITests-Runner.app
- name: Install Bundle
run: bundle install
- name: Run ui tests
run: bundle exec fastlane uiTestLane
- Download curio named “fabricate”
- Download “sprinter” for UI tests
- Introduce Fastlane group
- Run required path
Protecting branches from failing code
Presently we can go to storehouse settings → branches and arrangement branch assurance rules as indicated by the docs.
We ought to add required status makes sure that will not permit to consolidate pull demand that neglects to finish all assessments.
Next Steps
To support the acquired achievement, we can take one the accompanying assignments:
Prod build
Make another path that will make a creation work with knock construct variant, then transfer it to Testflight
Add relating position to our github work process for it, that will rely upon our test occupations