Hello. In anticipation of the start of the courses "iOS Developer. Basic" and "iOS Developer. Professional" , we publish the final part of the article about CI / CD integration for several environments with Jenkins and Fastlane.
We also invite you to a free demo lesson on the topic: "Combine up to iOS 13 and how to add SwiftUI 2.0 to any application"
Jenkins setup for different environments
Jenkins, Testflight . , , , , . Xcode (. . ) . , : MyScipt.groovy
, Deploy.groovy
Fastfile
, , , , :
, , Xcode
(provisioning profile)
, Fastlane. , . :
lane :build do |options|
:
parameter = options[:parameter_name]
, .
, Staging
TestProduction
. Jenkins , , , , , Stg.groovy
TestProduction.groovy
. , :
Stg-parameters.groovy
//
def getBundleId() {
return "com.our_project.stg"
}
//
def getConfiguration() {
return "Stg-Testflight"
}
//
def getProvisioningProfile() {
return "\'match AppStore com.our_project.stg'"
}
//
def getBundleId() {
return "com.our_project.test.production"
}
//
def getConfiguration() {
return "TestProduction-Testflight"
}
//
def getProvisioningProfile() {
return "\'match AppStore com.our_project.test.production'"
}
, :
TestProduction-parameters.groovy
lane :build do |options|
bundle_id = options[:bundle_id]
configuration = options[:configuration]
provisioning_profile = options[:provisioning_profile]
.
.
.
end
deploy Deploy.script
:
def deployWith(bundle_id, configuration, provisioning_profile) {
.
.
.
}
, : Run Tests, Build Upload to Testflight - , . (Checkout repo, Install dependencies, Reset simulators Cleanup ) .
Run Tests
Run tests, configuration , :
stage('Run Tests') {
sh 'bundle exec fastlane test configuration:$configuration'
}
- test Fastfile, . :
lane :a_lane do |options|
....
bundle_id = options[:bundle_id]
configuration = options[:configuration]
provisioning_profile = options[:provisioning_profile]
...
end
, test, configuration, :
lane :test do |options|
configuration = options[:configuration]
scan(
clean: true,
devices: ["iPhone X"],
workspace: "our_project.xcworkspace",
scheme: configuration,
code_coverage: true,
output_directory: "./test_output",
output_types: "html,junit"
)
slather(
cobertura_xml: true,
proj: "our_project.xcodeproj",
workspace: "our_project.xcworkspace",
output_directory: "./test_output",
scheme: configuration,
jenkins: true,
ignore: [array_of_docs_to_ignore]
)
end
, scheme configuration, .
Build
Build . build Fastlane, . Build, deployWith()
, :
stage('Build') {
withEnv(["FASTLANE_USER=fastlane_user_email_address"]) {
withCredentials([
string([
credentialsId:'match_password_id',
variable: 'MATCH_PASSWORD'
]),
string([
credentialsId: 'fastlane_password_id',
variable: 'FASTLANE_PASSWORD']),
]) {
sh 'bundle exec fastlane build bundle_id:$bundle_id configuration:$configuration provisioning_profile:$provisioning_profile'
}
}
}
, build , , :
lane :build do |options|
bundle_id = options[:bundle_id]
configuration = options[:configuration]
provisioning_profile = options[:provisioning_profile]
match(
git_branch: "the_branch_of_the_repo_with_the_prov_profile",
username: "github_username",
git_url: "github_repo_with_prov_profiles",
type: "appstore",
app_identifier: bundle_id,
force: true)
version = get_version_number(
xcodeproj: "our_project.xcodeproj",
target: "production_target"
)
build_number = latest_testflight_build_number(
version: version,
app_identifier: bundle_id,
initial_build_number: 0
)
increment_build_number({ build_number: build_number + 1 })
settings_to_override = {
:BUNDLE_IDENTIFIER => bundle_id,
:PROVISIONING_PROFILE_SPECIFIER => provisioning_profile,
:DEVELOPMENT_TEAM => "team_id"
}
export_options = {
iCloudContainerEnvironment: "Production",
provisioningProfiles: { bundle_id => provisioning_profile }
}
gym(
clean: true,
scheme: configuration,
configuration: configuration,
xcargs: settings_to_override,
export_method: "app-store",
include_bitcode: true,
include_symbols: true,
export_options: export_options
)
end
, , bundle_id
, configuration
provisioning_profiles
, .
Upload To Testflight
Fastlane upload_to_testflight
, bundle_id
. Upload to TestFlight deployWith()
Deploy.groovy
, :
stage('Upload to TestFlight') {
withEnv(["FASTLANE_USER=fastlane_user_email_address"]) {
withCredentials([
string([credentialsId: 'fastlane_password_id', variable: 'FASTLANE_PASSWORD']),
]) {
sh "bundle exec fastlane upload_to_testflight bundle_id:$bundle_id"
}
}
}
FastFile bundle_id
:
lane :upload_to_testflight do |options|
bundle_id = options[:bundle_id]
pilot(
ipa: "./build/WorkableApp.ipa",
skip_submission: true,
skip_waiting_for_build_processing: true,
app_identifier: bundle_id
)
end
! Deploy.groovy
Fastfile
. ?
Jenkins , , , . , , Stg
Stg.groovy
, :
node(label: 'ios') {
def deploy;
def utils;
String RVM = "ruby-2.5.0"
ansiColor('xterm') {
withEnv(["LANG=en_US.UTF-8", "LANGUAGE=en_US.UTF-8", "LC_ALL=en_US.UTF-8"]) {
deploy = load("jenkins/Deploy.groovy")
utils = load("jenkins/utils.groovy")
utils.withRvm(RVM) {
deploy.deployWith(getBundleId(), getConfiguration(), getProvisioningProfile())
}
}
}
}
//
def getBundleId() {
return "com.our_project.stg"
}
//
def getConfiguration() {
return "Stg-Testflight"
}
//
def getProvisioningProfile() {
return "\'match AppStore com.our_project.stg'"
}
TestProduction
, TestProduction.groovy
:
node(label: 'ios') {
def deploy;
def utils;
String RVM = "ruby-2.5.0"
ansiColor('xterm') {
withEnv(["LANG=en_US.UTF-8", "LANGUAGE=en_US.UTF-8", "LC_ALL=en_US.UTF-8"]) {
deploy = load("jenkins/Deploy.groovy")
utils = load("jenkins/utils.groovy")
utils.withRvm(RVM) {
deploy.deployWith(getBundleId(), getConfiguration(), getProvisioningProfile())
}
}
}
}
//
def getBundleId() {
return "com.our_project.test.production"
}
//
def getConfiguration() {
return "TestProduction-Testflight"
}
//
def getProvisioningProfile() {
return "\'match AppStore com.our_project.test.production'"
}
, Deploy.groovy
Fastfile
, :
def deployWith(bundle_id, configuration, provisioning_profile) {
stage('Checkout') {
checkout scm
}
stage('Install dependencies') {
sh 'gem install bundler'
sh 'bundle update'
sh 'bundle exec pod repo update'
sh 'bundle exec pod install'
}
stage('Reset Simulators') {
sh 'bundle exec fastlane snapshot reset_simulators --force'
}
stage('Run Tests') {
sh 'bundle exec fastlane test configuration:$configuration'
}
stage('Build') {
withEnv(["FASTLANE_USER=fastlane_user_email_address"]) {
withCredentials([
string([
credentialsId:'match_password_id',
variable: 'MATCH_PASSWORD'
]),
string([
credentialsId: 'fastlane_password_id',
variable: 'FASTLANE_PASSWORD']),
]) {
sh 'bundle exec fastlane build bundle_id:$bundle_id configuration:$configuration provisioning_profile:$provisioning_profile'
}
}
}
stage('Upload to TestFlight') {
withEnv(["FASTLANE_USER=fastlane_user_email_address"]) {
withCredentials([
string([
credentialsId: 'fastlane_password_id',
variable: 'FASTLANE_PASSWORD']),
]) {
sh "bundle exec fastlane upload_to_testflight bundle_id:$bundle_id"
}
}
}
stage('Cleanup') {
cleanWs notFailBuild: true
}
}
Fastfile_parameterized
fastlane_version "2.75.0"
default_platform :ios
lane :test do |options|
configuration = options[:configuration]
scan(
clean: true,
devices: ["iPhone X"],
workspace: "our_project.xcworkspace",
scheme: configuration,
code_coverage: true,
output_directory: "./test_output",
output_types: "html,junit"
)
slather(
cobertura_xml: true,
proj: "our_project.xcodeproj",
workspace: "our_project.xcworkspace",
output_directory: "./test_output",
scheme: configuration,
jenkins: true,
ignore: [array_of_docs_to_ignore]
)
end
lane :build do |options|
bundle_id = options[:bundle_id]
configuration = options[:configuration]
provisioning_profile = options[:provisioning_profile]
match(
git_branch: "the_branch_of_the_repo_with_the_prov_profile",
username: "github_username",
git_url: "github_repo_with_prov_profiles",
type: "appstore",
app_identifier: bundle_id,
force: true)
version = get_version_number(
xcodeproj: "our_project.xcodeproj",
target: "production_target"
)
build_number = latest_testflight_build_number(
version: version,
app_identifier: bundle_id,
initial_build_number: 0
)
increment_build_number({ build_number: build_number + 1 })
settings_to_override = {
:BUNDLE_IDENTIFIER => bundle_id,
:PROVISIONING_PROFILE_SPECIFIER => provisioning_profile,
:DEVELOPMENT_TEAM => "team_id"
}
export_options = {
iCloudContainerEnvironment: "Production",
provisioningProfiles: { bundle_id => provisioning_profile }
}
gym(
clean: true,
scheme: configuration,
configuration: configuration,
xcargs: settings_to_override,
export_method: "app-store",
include_bitcode: true,
include_symbols: true,
export_options: export_options
)
end
lane :upload_to_testflight do |options|
bundle_id = options[:bundle_id]
pilot(
ipa: "./build/WorkableApp.ipa",
skip_submission: true,
skip_waiting_for_build_processing: true,
app_identifier: bundle_id
)
end
, Testflight:
a)
b) ,
Jenkins, . Xcode , , . 2 (Deploy.groovy
Fastlane
) (Stg.groovy
TestProduction.groovy
).
, !
, ?, , ?!
- .
Twitter: @elenipapanikolo
: