iOS 底層原理38:自動化打包(三)Fastlane

iOS 底層原理 文章匯總

除了Jenkins可以自動打包,還有另一個方式:Fastlane,下面來了解下。

Fastlane是一個完全開源的項目,是一款為iOS和Android開發者提供的自動化構建工具。它可以幫助開發者將App打包、簽名、測試、發布等流程串聯起來,實現完全自動化的工作流。其官網地址為:https://fastlane.tools/

Fastlane安裝

參考配置文檔,安裝步驟如下:

  • 檢查ruby版本,需要2.0及以上,并且將gem的source改為gems.ruby-china.com/
// 查看 Ruby 版本
ruby -v

// 查看 gem 的source
gem sources
  • 檢查Xcode命令行工具是否安裝,如果沒有安裝則會自動開始安裝
xcode-select --install
  • 安裝Fastlane
sudo gem install fastlane --verbose

//如果安裝出錯,則使用以下命令
sudo gem install -n /usr/local/bin fastlane

//查看版本驗證是否安裝成功
fastlane --version
查看ruby版本圖示

Fastlane相關block、lanes、actions

在完善打包邏輯之前,首先介紹Fastlane中提供的常用的blocks、lanes和actions

  • blocks:fastlane中除了lane的其他模塊

  • lanes:Lanes的相關用法介紹

  • actions:fastlane內置好的常用操作

Blocks

block主要分為三類:

  • before_all & before_each

  • after_all & after_each

  • error

before_all & before_each

其中before_all會先于before_each執行,且僅執行一次,
所以許多通用操作流入git代碼拉取等,可以放在before_all或者befoe_each中

  • before_all:在lane執行前執行一次,
before_all do |lane| 
   # ...
end

<!--舉例-->
before_all do |lane| 
   cocoapods #這里會執行pod install
end
  • before_each:會在任意lane執行前執行,與before_all只執行一次不同,如果在一個 lane 中多次調用了其他的 lane,則其他的 lane 執行前都會執行 before_each。
before_each do |lane, options|
  # ...
end

after_all & after_each

after_all 會在 after_each 執行完后執行,且僅執行一次。
許多通用操作例如發送打包成功的通知等,可以放在 after_all 或 after_each 中。

  • after_all:會在lane執行完后執行一次
after_all do |lane|
  # ...
end


<!--舉例-->
after_all do |lane|
  say("Successfully finished release (#{lane})!")
  slack(
    message: "Successfully submitted new App Update"
  )
  sh("./send_screenshots_to_team.sh") # Example
end
  • after_each:會在任意lane執行完后執行,如果在一個lane中多次調用了其他的lane,則其他的lane執行完后都會執行after_each
after_each do |lane, options|
  # ...
end

例如下面的例子,before_each將after_each被調用 4 次

  • before_each:調用deploy之前、切換到archive之前、切換到sign之前、切換到upload之前
  • after_each:執行deploy之后、切換到archive執行之后、切換到sign執行之后、切換到upload執行之后
before_each do |lane, options|
  # ...
end

lane :deploy do
  archive
  sign
  upload
end

lane :archive do
  # ...
end

lane :sign do
  # ...
end

lane :upload do
  # ...
end

after_each do |lane, options|
  # ...
end

error

在任何流程中發生錯誤時,都會退出并執行error,錯誤后的after_all和after_each將不會執行

error do |lane, exception|
  slack(
    message: "Something went wrong with the release.",
    success: false,
    payload: { "Error Info" => exception.error_info.to_s } 
  )
end

Actions

一個命名的lane代表一個持續集成的任務,每個任務由多個步驟組成,步驟組成是已經定義好的action工具。在終端可以通過fastlane action actionName查看某個具體的action,也可以通過fastlane action查看fastlane中定義好的action及說明。

除了使用ruby代碼在lane中實現的各種功能,fastlane也內置了許多寫好的獨立方法庫即action,每一個action都是一個獨立Ruby腳本,是fastlane的最小執行單位,下面介紹幾個常用的action

  • 1、cocoapods:執行pod install

  • 2、gym:項目編譯、打包等

  • 3、increment_build_number:build號自增

  • 4、match:管理證書和配置文件

  • 5、app_store_connect_api_key:為其他 action 生成 App Store Connect API token

1、cocoapods

調用cocoapods action會執行pod install,如果工程中使用了cocoapods管理三方庫,需要在Gemfile中添加以下命令

gem "cocoapods"

在lane中使用直接調用即可

lane :debug do
  cocoapods  # 執行 pod install
end

2、gym

gym是fastlane提供的用于構建、打包的action,是build_app的別名。可以根據配置參數編譯iOS應用,并生成ipa包。以下是常用的參數

  • workspace:workspace 文件路徑,使用 cocoapods 后需要使用

  • project:project 文件路徑,若有 workspace 此項可忽略

  • scheme: schema名

  • clean:是否在編譯前清理工程

  • configuration:編譯環境設置,Relese、Debug、自定義

  • export_method:包輸出類型,app-store、ad-hoc、package、enterprise、development

  • archive_path : 讀取xarchive的路徑

  • output_directory:ipa包輸出路徑

  • output_name:ipa包名稱

  • include_symbols:是否集成調試符號,若為 false 則會單獨生成符號文件

  • include_bitcode: 是否開啟bitcode

其他參數如下:

  • export_options: 可以指定更詳細的打包配置,可以是配置文件路徑

  • skip_build_archive: 跳過構建,打包階段,直接簽名;使用archive_path 作為輸入

  • skip_archive:僅構建

  • skip_codesigning: 僅構建,打包,不簽名

以下是CI中具體的例子

lane :release do
  # ...
  gym(
      workspace: "app.xcworkspace",
      scheme: "app",
      # 打包前clean
      clean: true,
      # Release、Debug、自定義
      configuration: "Release",
      # app-store, ad-hoc, package, enterprise, development
      export_method: "ad-hoc",
      # 文件輸出路徑
      output_directory: "/Users/user/Desktop/",
      # ipa名稱
      output_name: "app.ipa",
      # 是否包含調試符號
      include_symbols: true,
      # 是否開啟bitcode
      include_bitcode: false,
    )
    # ...
end

3、increment_build_number

  • 通過app_store_build_number獲取最新版本的build_number
currentBuildNumber = app_store_build_number(
  api_key: api_key,        # 使用app_store_connect_api_key認證
  live: false,             #  live 為 true 查詢已發售的版本,false 查詢測試的版本
  app_identifier: "com.zm.ZLGithubClient"
)
  • 通過increment_build_number設置項目的build_number
increment_build_number(
  build_number: currentBuildNumber + 1
)

4、match

match主要用于證書和配置文件的管理,用于開發成員之間共享,具體的過程可參考代碼簽名指南

match配置步驟

  • 在gitLab上創建一個倉庫用于保存證書

  • 在項目根目錄下執行 fastlane match init,輸入倉庫的git地址,會在fastlane目錄下生成Matchfile文件,包含match的配置信息

match參數說明

  • type: 同步的配置文件類型: appstore,adhoc,development,enterprise,默認 development

  • readonly: 默認false,如果是true,不會生成新的證書和描述配置文件

  • app_identifier: 指定描述配置文件的bundle id;如果不指定則使用 AppFile 中的 app_identifier

  • git_url: 證書保存的git地址

  • keychain_name : 保存證書的keychain,默認login.keychain

以下是在CI中使用match的例子

# match 中需要配置參數
# 證書類型   appstore , adhoc 還是 development
# app id  
# kaychain 證書保存的keychain
# git_url  證書保存的github遠端庫
lane :github_action_testFlight do

match(type: "appstore",       
      readonly: true,
      app_identifier: "com.used.id",
      keychain_name: ENV['MATCH_KEYCHAIN_NAME'],
      keychain_password: ENV['MATCH_PASSWORD'],
      git_url: ENV['MATCH_GIT_URL'])

end

# ENV['MATCH_GIT_URL'] 是加密保存的參數

match說明

  • 執行這些命令fastlane match development, fastlane match adhoc, fastlane match enterprisefastlane match appstore,首次執行自動在apple store connect中創建provisioning file,證書并下載加密保存在git倉庫,并上傳.

  • 其他開發者就可以使用fastlane match命令共享github中的證書和配置文件。

5、App Store Connect API

在早先訪問App Store Connect信息時需要雙因素認證。而在持續集成的過程中一般無法人機交互(例如github-action),會導致持續集成無法完成。在WWDC18中,蘋果提出了App Store Connect API,提供另外的認證方式。Fastlane也對App Store Connect API提供了支持,具體查看Using App Store Connect API文檔

Using App Store Connect API是一個官方公共API,用于管理應用元數據、定價、配置等。但是在使用之前,需要獲取AppStore Connect的訪問權限,即獲取issuer ID和apiKey

app_store_connect_api_key 是用來為其他 action 生成 App Store Connect API token 的 action; match,pilot以及 deliver等 action 都可以使用 App Store Connect API token,有如下參數:

  • key_id:密鑰ID,需要在AppStore Connect -> 用戶和訪問 -> 密鑰中創建并獲取

  • issuer_id:標識創建認證令牌的發放者。也是在AppStore Connect -> 用戶和訪問 -> 密鑰中獲取

  • key_filePath: p8文件的路徑

  • key_content: p8文件的內容,未編碼直接提供需要將回車替換為\n

  • is_key_content_base64: 是否key的內容經過base64編碼

  • in_house: 是app store還是 enterprise

以下是在CI中的使用例子

lane :release do
  api_key = app_store_connect_api_key(
    key_id: "xxxxxxxxx",
    issuer_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    key_filepath: "./private_keys/AuthKey_xxxxxxxxxx.p8",
    duration: 1200, # optional
    in_house: false, # optional but may be required if using match/sigh
  )
   
  # 在piolt中使用app_store_connect_api_key
  pilot(api_key: api_key)
end

Lanes

lanes的使用方式請參考文檔,可以分為以下幾種:

  • 命令行參數傳遞

  • lane之間的調用

  • 返回值

  • 如何停止執行中的lane

  • lane的上下文通信

  • 如果訪問lane的屬性

  • 私有lane

  • 如何配置多個lane的環境變量

命令行參數傳遞

1、傳遞:如果需要將參數從命令行傳遞到lane,語法如下

//格式
fastlane [lane] key:value key2:value2

//舉例
fastlane deploy submit:false build_number:24

2、使用:在lanes中接收傳入的值,是通過options實現的,格式為:options[:參數名]

lane :deploy do |options| 
    # 獲取傳入的submit
    if options[:submit] 
        # Only when submit is true 
    end 
    # build號增加,獲取傳入的build_number 
    increment_build_number(build_number: options[:build_number])  
 end

lane之間的調用

lane之間的調用,有點類似于函數的調用,通過lane的名字來調用

lane :deploy do |options|
  # deploy調用名為build的lanes
  build(release: true) # 傳入打包的方式
end

lane :staging do |options|
  # deploy調用名為build的lanes
  build # 不傳參也可以工作
end

lane :build do |options|
  build_config = (options[:release] ? "Release" : "Staging")
  build_ios_app(configuration: build_config)
end

返回值

除此之外,lane還可以檢索返回值,在Ruby中,lane定義的最后一行是返回值,在其他lane中通過options進行使用

lane :deploy do |options|
  value = calculate(value: 3)
  puts value # => 5
end

lane :calculate do |options|
  # 返回值
  2 + options[:value] # the last line will always be the return value
end

如何停止執行中的lane

在lane中可以通過next關鍵字來停止執行中的lane,如下所示

lane :build do |options|
  if cached_build_available?
    UI.important 'Skipping build because a cached build is available!'
    next # skip doing the rest of this lane
  end
  match
  gym
end

private_lane :cached_build_available? do |options|
  # ...
  true
end

當next作用在切換lane時,會控制流程返回到前lane正在執行的位置,即當前lane的next以后的代碼將不會執行

lane :first_lane do |options|
  puts "If you run: `fastlane first_lane`"
  puts "You'll see this!"
  second_lane
  puts "As well as this!"
end

private_lane :second_lane do |options|
  next
  puts "This won't be shown"
end

lane的上下文通信

lane的上下文通信,簡單來說就是如何在不同的action間通信。不同的action可以通過分享哈希來進行通信,如下所示

lane_context[SharedValues::VARIABLE_NAME_HERE]

//舉例
lane_context[SharedValues::BUILD_NUMBER]                # Generated by `increment_build_number`
lane_context[SharedValues::VERSION_NUMBER]              # Generated by `increment_version_number`
lane_context[SharedValues::SNAPSHOT_SCREENSHOTS_PATH]   # Generated by _snapshot_
lane_context[SharedValues::PRODUCE_APPLE_ID]            # The Apple ID of the newly created app
lane_context[SharedValues::IPA_OUTPUT_PATH]             # Generated by _gym_
lane_context[SharedValues::DSYM_OUTPUT_PATH]            # Generated by _gym_
lane_context[SharedValues::SIGH_PROFILE_PATH]           # Generated by _sigh_
lane_context[SharedValues::SIGH_UDID]                   # The UDID of the generated provisioning profile
lane_context[SharedValues::HOCKEY_DOWNLOAD_LINK]        # Generated by `hockey`
lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]      # Generated by `gradle`
lane_context[SharedValues::GRADLE_ALL_APK_OUTPUT_PATHS] # Generated by `gradle`
lane_context[SharedValues::GRADLE_FLAVOR]               # Generated by `gradle`
lane_context[SharedValues::GRADLE_BUILD_TYPE]           # Generated by `gradle`

如果訪問lane的屬性

我們也可以通過lane_context動態訪問當前lane的屬性,如下所示

lane_context[SharedValues::PLATFORM_NAME]        # Platform name, e.g. `:ios`, `:android` or empty (for root level lanes)

lane_context[SharedValues::LANE_NAME]            # The name of the current lane preceded by the platform name (stays the same when switching lanes)

lane_context[SharedValues::DEFAULT_PLATFORM]     # Default platform

同時這些屬性也可用作.env文件的環境變量

ENV["FASTLANE_PLATFORM_NAME"]
ENV["FASTLANE_LANE_NAME"]

私有lane

當我們有不同lane調用同一個lane時,可以將這個lane定義為私有的lane,防止在外部通過fastlane laneName進行調用,如下所示,我們不能通過fastlane build來訪問私有的lane

lane :production do
  # ...
  build(release: true)
  appstore # Deploy to the AppStore
  # ...
end

lane :beta do
  # ...
  build(release: false)
  crashlytics # Distribute to testers
  # ...
end

lane :build do |options|
  # ...
  ipa
  # ...
end

<!--更改為-->
lane :production do
  # ...
  build(release: true)
  appstore # Deploy to the AppStore
  # ...
end

lane :beta do
  # ...
  build(release: false)
  crashlytics # Distribute to testers
  # ...
end

private_lane :build do |options|
  # ...
  ipa
  # ...
end

如何配置多個lane的環境變量

通常Appfile只會使用配置項的第一個值,如下所示,app_identifier配置了兩個值,我們一般只會取第一個com.used.id,而com.ignored.id將被忽略。

app_identifier "com.used.id"
app_identifier "com.ignored.id"

為了避免以上情況,fastlane提供了for_lanefor_platform來解決多個配置的情況,所有的配置文件中都可使用

  • for_lane:當調用的lane和指定的lane名稱匹配時,會調用對應的block
locales ['en-US', 'fr-FR', 'ja-JP']

for_lane :screenshots_english_only do
  locales ['en-US']
end

for_lane :screenshots_french_only do
  locales ['fr-FR']
end
  • for_platform:根據當前的platform決定執行的block
app_identifier "com.default.id"

for_lane :enterprise do
  app_identifier "com.forlane.enterprise"
end

for_platform :mac do
  app_identifier "com.forplatform.mac"

  for_lane :release do
    app_identifier "com.forplatform.mac.forlane.release"
  end
end

Fastlane配置

Fastlane的配置主要分為三步:

  • fastlane初始化
  • 創建.env配置全局變量,并修改Appfile
  • fastfile完善打包邏輯

【第一步】fastlane的初始化配置

主要執行以下命令

cd [項目根目錄,xcodeproj的同級目錄]
fastlane init

init操作主要完成以下操作:

  • 1、會要求輸入開發者賬戶和密碼,會存儲在鑰匙串中,后續使用無需再輸入密碼

  • 2、會檢測當前項目的App Identifier是否已經存在Developer中

  • 3、會檢測App是否已經在AppStore Connect中,如果都滿足,過程是比較順利的

  • 4、會在項目工程的目錄下生成一個fastlane文件夾,里面是Fastlane的一些配置文件。fastlane可以通過配置AppfileDeliverfileFastfile 來完成各種工作。

    fastlane文件夾

下面分別介紹下fastlane文件夾中常用的文件:

  • Appfile:存放 App 的基本信息包括 App_Identifier 、AppID 、Team_ID 等。這個文件的參數是已經定義好的,新增并沒有用

  • Fastfile:最核心的用來控制流程走向的配置文件,是最重要的一個文件,在這個文件里面可以編寫和定制我們打包腳本的一個文件,所有自定義的功能都寫在這里

  • Deliverfile:可用于配置提交AppStore Connect的一些參數。(注:這個文件只有在選擇由fastlane管理metadata為YES時,才會生成,如下所示)

    Deliverfile是否有的選擇

  • .env或.env.default:為了更靈活的使用Appfile的內容,可以使用.env文件來進行環境配置,新增其他的變量在fastfile中使用

除此之外,還需要關注fastlane同級的Gemfile和Gemfile.lock文件

  • Gemfile:私用bundler來安裝和引入該App所需的gem,類似于cocoapods中的podfile

  • Gemfile.lock:gem的版本控制文件,類似于cocoapods的podfile.lock文件

【第二步】創建.env配置全局變量,并修改Appfile

注:因為是.env文件是.開頭文件,默認是在finder中隱藏的,可以通過快捷鍵來顯示隱藏文件:CMD + Shift + .

為了更加靈活的使用Appfile的內容,可以引入.env文件來進行環境配置,具體的請參考
.env環境配置文檔。這樣在執行命令時,可以在氣候加上環境變量,以達到使用不同配置的目的。

其命名規則為:.env.<environment>,例如:.env.development、.env.release。

  • 在命令中如下使用
fastlane <lane-name> --env development
  • Appfile中使用.env,直接讀取變量即可
//.env內容
WORKSPACE=YourApp.xcworkspace 
HOCKEYAPP_API_TOKEN=your-hockey-api-token

//Fastfile中使用.env文件
xcworkspace ENV['WORKSPACE']
hockey-api-token ENV['HOCKEYAPP_API_TOKEN'] 

【第三步】Fastfile完善打包邏輯

編輯fastlane的邏輯可參考fastlane自動部署iOS AppStore文檔,構建步驟如下:

  • 更新pod,清理緩存

  • 版本號修改

  • 編譯并打包

  • 上傳AppStore

以下是具體的編譯、打包代碼

# 因為fastlane存在新老版本兼容問題,所以一般會指定fastlane版本
fastlane_version "2.205.1"
# 設置默認的平臺
default_platform(:ios)

# ======================== .env文件配置獲取相關參數 ========================
# 定義兩個方便使用和修改的常量
scheme = ENV['Scheme'] #scheme名稱
xcodeproj = ENV['Xcodeproj'] 
workspace = ENV['Workspace']
info_plist = ENV['Info_Plist']

# 定義指定平臺的操作
platform :ios do

  # ======================== 執行lane前的操作 ========================
  # 所有lane執行之前,可以做如執行cocoapods的pod install
  before_all do |lane, options|
    # 更新pod
    cocoapods(use_bundle_exec: FALSE)
    # 清理緩存
    xcclean(scheme: scheme)
  end

  # 將正式應用打包并上傳到App Store,release是自己取的名字,因為是發布正式版,所以取名叫 release
  desc "Push a new release build to the App Store"
  lane :release do

    # 打包之前,先將build號遞增
    increment_build_number(xcodeproj: "#{xcodeproj}")

    # 對應用進行打包
    build_app(
      workspace: "#{workspace}",
      scheme: "#{scheme}",
      export_method: "app-store",
      clean: true,
      xcargs: "-allowProvisioningUpdates"
    )

    # 將打包完的應用上傳到AppStore
    upload_to_app_store(
      skip_metadata: true,
      skip_screenshots: true
    )
  end
  
  # ======================== 執行lane成功后的操作 ========================
  # 所有lane完成之后,可以使用參數lane來區分
  after_all do |lane, options|
    puts "所有lane執行完畢"
  end

# ======================== 執行lane失敗后的操作 ========================
  # 所有lane失敗之后,可以使用參數lane來區分
  error do |lane, options|
    puts "lane執行失敗"
  end
  
end

到此,fastlane就配置完成了

Fastlane使用

  • 跳轉到項目的根目錄:cd [項目根目錄]
  • 自動打包并上傳到AppStore Connect,在終端執行lane:fastlane lanename

除了可以在本地終端執行,還可以在Jenkins構建打包服務,具體步驟參考文檔:Jenkins集成fastlane

最終,Fastlane整體流程匯總如下


Fastlane打包流程圖示

iOS中打包方式匯總如下


iOS 打包-流程圖示

參考文章

Fastlane一鍵自動化打包發布 iOS 項目
使用fastlane進行iOS打包
iOS 使用fastlane實現自動化打包
Fastlane
使用fastlane打包并發布iOS應用(一) - 新思路
bundler官網 - 局部使用fastlane
使用 Fastlane 自動打包應用 - block全面

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容