Swift Tricks

My own, mostly internal blog of Swift tips and tricks

Fastlane configuration file templates

Fastfile template

Here is a template that includes deployment to three environments, slack notifications, and build # incrementing.

# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#     https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
#     https://docs.fastlane.tools/plugins/available-plugins
#

######################################################
# Some example usages:
# - fastlane test
# - fastlane test environment:"LIVE"
# - fastlane deploy
# - fastlane deploy environment:"LIVE"
# - fastlane deploy environment:"LIVE" importCerts:true
# - fastlane deploy disableNotification:true
# - fastlane increment
# - fastlane increment environment:"LIVE"
######################################################

default_platform :ios

platform :ios do

  ######################################################
  # Settings
  ######################################################
  SLACK_DEVELOP_URL = "https://hooks.slack.com/...." # Hook for developer notification
  SLACK_DEPLOY_URL = "https://hooks.slack.com/...." # Hook for deployment notification
  SLACK_MESSAGE = "A new AppName build was deployed to TestFlight! Processing has started..."
  SLACK_USERNAME = "fastlane"
  SLACK_ICON = "https://www.example.com/myapp.jpg"
  SLACK_BUILD_NUMBER_PLIST = "./AppName/AppName/Info.plist"
  APPSTORE_CONNECT_DEV = "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng/app/12345/testflight?section=iosbuilds"
  APPSTORE_CONNECT_TEST = "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng/app/12345/testflight?section=iosbuilds"
  APPSTORE_CONNECT_LIVE = "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng/app/12345/testflight?section=iosbuilds"
  KEYCHAIN_PATH = "/Users/buildserveruser/Library/Keychains/login.keychain"
  KEYCHAIN_PASSWORD = "blabla"
  CERTS_GIT_BRANCH = "master"
  CERTS_GIT_USERNAME = "certs@example.com"
  CERTS_GIT_APP_IDENTIFIERS = ["de.company.appname.dev", "de.company.appname.dev.widget1", "de.company.appname.dev.widget2", "de.company.appname.test", "de.company.appname.test.widget1", "de.company.appname.test.widget2", "de.company.appname", "de.company.appname.widget1", "de.company.appname.widget2"]
  CERTS_GIT_TYPE = "appstore"

  # Plist files for build increment
  APP_PLIST_FILES = {
    "LIVE"=>["./AppName/AppName/Info.plist", "./AppName/Widget 1/Info.plist", "./AppName/Widget 2/Info.plist"],
    "TEST"=>["./AppName/AppName/AppName Test.plist", "./AppName/Widget 1/Widget 1-Test.plist", "./AppName/Widget 2/Widget 2 Test-Info.plist"],
    "DEV"=>["./AppName/AppName Dev.plist", "./AppName/Widget 1/Widget 1-Dev.plist", "./AppName/Widget 2/Widget 2 Dev-Info.plist"]
  }

  ######################################################
  # Private lanes
  ######################################################
  
  ##################################
  # Import certificates
  private_lane :importCerts do
    unlock_keychain(path: KEYCHAIN_PATH, password: KEYCHAIN_PASSWORD)
    match(git_branch: CERTS_GIT_BRANCH, username: CERTS_GIT_USERNAME, app_identifier: CERTS_GIT_APP_IDENTIFIERS, type: CERTS_GIT_TYPE, readonly: true)
  end

  ##################################
  # Build number incrementation
  # - paths: Paths to the plist file
  private_lane :incrementBuildNumber do |options|
    for path in options[:paths]
      new_build_number = get_info_plist_value(path: path, key: "CFBundleVersion").to_i + 1
      set_info_plist_value(path: path, key: "CFBundleVersion", value: new_build_number.to_s)
    end
  end

  ##################################
  # Slack notification lane
  # - environment: Option can be DEV, TEST, or LIVE. Defaults to DEV.
  private_lane :notifySlack do |options|

    # Which environment?
    if options[:environment] == "DEV"
      connect_link = APPSTORE_CONNECT_DEV
      environment_name = "Develop"
    elsif options[:environment] == "TEST"
      connect_link = APPSTORE_CONNECT_TEST
      environment_name = "Test"
    elsif options[:environment] == "LIVE"
      connect_link = APPSTORE_CONNECT_LIVE
      environment_name = "Live"
    else
      puts "[WARNING] Failed to find environment " + options[:environment] 
      next
    end

    # Notify slack
    commit = last_git_commit
    short_hash = commit[:abbreviated_commit_hash]
    build_number = get_info_plist_value(path: SLACK_BUILD_NUMBER_PLIST, key: "CFBundleVersion")
    slack(message: SLACK_MESSAGE, username: SLACK_USERNAME, icon_url: SLACK_ICON, payload: { 
      "App Store Connect" => connect_link,
      "Build"           => build_number,
      "Environment"     => environment_name,
      "Git version"     => short_hash,
    }, default_payloads: [], fail_on_error: false, slack_url: SLACK_DEPLOY_URL)
  end

  ######################################################
  # Public lanes
  ######################################################

  ##################################
  # Clean the project
  desc "Clean the project for all environments"
  lane :clean do
    xcclean
    clear_derived_data
  end

  ##################################
  # Increment build count
  # - environment: Option can be ALL, DEV, TEST, or LIVE. Defaults to ALL.
  desc "Increment build count, by default for ALL environments"
  lane :increment do |options|

    # Default to ALL
    if !options[:environment] || options[:environment] == "ALL"
      incrementBuildNumber paths:APP_PLIST_FILES["DEV"]
      incrementBuildNumber paths:APP_PLIST_FILES["TEST"]
      incrementBuildNumber paths:APP_PLIST_FILES["LIVE"]
    else
      incrementBuildNumber paths:APP_PLIST_FILES[options[:environment]]
    end

  end

  ##################################
  # Deployment (test, build, deploy)
  # - environment: Option can be DEV, TEST, or LIVE. Defaults to DEV.
  # - importCerts: A true or false option whether or not to do fastlane match before building. Defaults to false.
  # - disableNotification: A true or false option to disable notifications to Slack
  desc "Deploy project to TestFlight, by default DEV environment"
  lane :deploy do |options|

    # Do I need to import certificates?
    if options[:importCerts]
      importCerts
    end

    # Which environment to build?
    environmentForNotification = options[:environment]
    if options[:environment] == "LIVE"
      scan(workspace: "AppName.xcworkspace", scheme: "AppName Live", clean: false, slack_url: SLACK_DEVELOP_URL, slack_only_on_failure: true)
      gym(workspace: "AppName.xcworkspace", scheme: "AppName Live", export_method: "app-store", output_directory: "./build/fastlane/", output_name: "AppName-Live.ipa")
    elsif options[:environment] == "TEST"
      scan(workspace: "AppName.xcworkspace", scheme: "AppName Test", clean: false, slack_url: SLACK_DEVELOP_URL, slack_only_on_failure: true)
      gym(workspace: "AppName.xcworkspace", scheme: "AppName Test", export_method: "app-store", output_directory: "./build/fastlane/", output_name: "AppName-Test.ipa")
    else
      environmentForNotification = "DEV"
      scan(workspace: "AppName.xcworkspace", scheme: "AppName Dev", clean: false, slack_url: SLACK_DEVELOP_URL, slack_only_on_failure: true)
      gym(workspace: "AppName.xcworkspace", scheme: "AppName Dev", export_method: "app-store", output_directory: "./build/fastlane/", output_name: "AppName-Dev.ipa")
    end

    # Deploy
    pilot(skip_waiting_for_build_processing: true)

    # Switch to notify slack lane
    if options[:disableNotification]
      notifySlack environment:environmentForNotification
    end
  end

  ##################################
  # Run unit tests
  # - environment: Option can be DEV, TEST, or LIVE. Defaults to DEV.
  desc "Run unit tests, by default DEV environment"
  lane :test do |options|

    # Do I need to import certificates?
    if options[:importCerts]
      importCerts
    end

    # Which environment to test?
    if options[:environment] == "LIVE"
      scan(workspace: "AppName.xcworkspace", scheme: "AppName Live", clean: false, slack_url: SLACK_DEVELOP_URL, slack_only_on_failure: true)
    elsif options[:environment] == "TEST"
      scan(workspace: "AppName.xcworkspace", scheme: "AppName Test", clean: false, slack_url: SLACK_DEVELOP_URL, slack_only_on_failure: true)
    else
      scan(workspace: "AppName.xcworkspace", scheme: "AppName Dev", clean: false, slack_url: SLACK_DEVELOP_URL, slack_only_on_failure: true)
    end

  end
end

Appfile template

# For more information about the Appfile, see:
#     https://docs.fastlane.tools/advanced/#appfile

for_platform :ios do

    # iOS specific
    apple_dev_portal_id "user@example.com"    # Apple Developer Account
    itunes_connect_id "user@example.com"          # App Store Connect Account
    
    team_id "ABCD1234"                        # Developer Portal Team ID
    itc_team_id "ABCD1234"                    # App Store Connect Team ID

    # Development
    for_lane :buildDev do
        app_identifier 'de.company.appname.dev'
    end
    for_lane :deployDev do
        app_identifier 'de.company.appname.dev'
    end

    # Test
    for_lane :buildTest do
        app_identifier 'de.company.appname.test'
    end
    for_lane :deployTest do
        app_identifier 'de.company.appname.test'
    end

    # Live
    for_lane :buildLive do
        app_identifier 'de.company.appname'
    end
    for_lane :deployLive do
        app_identifier 'de.company.appname'
    end
    
end

Matchfile template

git_url "git@gitlab.com:company/iOS-Certificates.git"