CI / CD integration for multiple environments with Jenkins and Fastlane. Part 3

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, , , , :

  1. , , Xcode

  2. (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


:

- iOS Developer. Basic"

- iOS Developer. Professional


:




All Articles