Fastlane簡介
Fastlane是用Ruby語言編寫的一套自動化工具集和框架,每一個工具實際都對應一個Ruby腳本,用來執行某一個特定的任務,而Fastlane核心框架則允許使用者通過類似配置文件的形式,將不同的工具有機而靈活的結合在一起,從而形成一個個完整的自動化流程。
到目前為止,Fastlane的工具集大約包含170多個小工具,基本上涵蓋了打包、簽名、測試、部署、發布、庫管理等等移動開發中涉及到的內容。如果這些工具仍然沒有符合你需求的,沒有關系,得益于Fastlane本身強大的Action和Plugin機制,如果你恰好懂一些Ruby開發的話,可以很輕易的編寫出自己想要的工具。
Fastlane依賴環境
- OS X 10.9 (Mavericks) or newer
- Ruby 2.0 or newer
- Xcode Command Line Tools (CLT)
- Paid Apple Developer Account
Fastlane安裝
- 安裝最新版Xcode command line tools
xcode-select --install
- 安裝Fastlane
sudo gem install fastlane --verbose
或:sudo gem install -n /usr/local/bin fastlane --verbose
Fastlane工具鏈
Fastlane使用
- 進入項目文件夾,初始化fastlane
fastlane init
完成后,如有提示新版本更新,然后你按照指令 sudo gem update fastlane 執行后, 可能出現 Nothing to update 提示,那就是當前設置的ruby源并沒有同步這項,添加ruby源可解決
gem sources --add https://rubygems.org
該步驟結束后,會在項目根目錄生成一個fastlane的文件夾,如下圖:
Appfile里面存放了App的基本信息包括app_identifier、apple_id、team_id等等。如果在第一步init的時候你輸入了正確的appId賬號和密碼會在這里生成正確的team_id信息。
Fastfile是最重要的一個文件,可以編寫和定制我們打包腳本的一個文件,我們自定義的一些功能就寫在這里。
Fastfile文件中內容:
從platform :ios do開始下面的就是fastlane可以執行的任務。
1.執行所有lane之前都先執行before_all,該功能里執行了cocoapods 這個action,cocoapods用于為項目執行pod install操作
before_all do
# ENV["SLACK_URL"] = "https://hooks.slack.com/services/..."
cocoapods
end
2.test這個lane執行了scan這個action,scan用于運行單元測試
desc "Runs all the tests"
lane :test do
scan
end
3.beta這個lane執行了gym和pilot,gym用于build、sign程序,pilot用于上傳應用到TestFlight
desc "Submit a new Beta Build to Apple TestFlight"
desc "This will also make sure the profile is up to date"
lane :beta do
# match(type: "appstore") # more information: https://codesigning.guide
gym(scheme: "YourAppScheme") # Build your app - more options available
pilot
# sh "your_script.sh"
# You can also use other beta testing services here (run `fastlane actions`)
end
4.release這個lane執行了gym和deliver,deliver用于上傳應用到App Store
desc "Deploy a new version to the App Store"
lane :release do
# match(type: "appstore")
# snapshot
gym(scheme: "YourAppScheme") # Build your app - more options available
deliver(force: true)
# frameit
end
5.自定義lane
6.被執行的lane成功之后,執行after_all
after_all do |lane|
# This block is called, only if the executed lane was successful
# slack(
# message: "Successfully deployed new App Update."
# )
end
7.被執行的lane失敗之后,執行error
error do |lane, exception|
# slack(
# message: exception.message,
# success: false
# )
end
Fastlane中部分action介紹
- gym:build、sign應用
gym(
workspace: "MyApp.xcworkspace", # 指定.xcworkspace文件的路徑。
scheme: "MyApp", # 指定項目的scheme名稱
clean: true, # 在打包前是否先執行clean。
output_directory: "path/to/dir", # 指定.ipa文件的輸出目錄,默認為當前文件夾。
output_name: "my-app.ipa", # 指定生成的.ipa文件的名稱,應包含文件擴展名。
configuration: "Debug", # 指定打包時的配置項,默認為Release。
silent: true, # 是否隱藏打包時不需要的信息。
codesigning_identity: "iPhone Distribution: xxx Co.,Ltd. (5JC8GZ432G)", # 代碼簽名證書。(XCode8 該配置已忽略)
include_symbols: true,
include_bitcode: true,
export_method: "ad-hoc", # 指定導出.ipa時使用的方法,可用選項:app-store, ad-hoc, package, enterprise, development, developer-id
)
- xcode_select:根據安裝路徑指定要使用的Xcode
xcode_select "/Applications/Xcode6.1.app"
- xcversion:根據版本號指定要使用的Xcode
xcversion(version: "8.1")
- snapshot:生成多設備上的本地化屏幕截圖,如果在fastlane init之后沒有生成Snapfile文件需執行
fastlane snapshot init
1.將./SnapshotHelper.swift文加到UI Test target
2.在配置完成后在 UI Tests 的 setup 方法中添加
let app = XCUIApplication()
setupSnapshot(app)
app.launch()
3.在需要截屏的地方是調 snapshot("some name") 方法進行截屏。
4.可以修改 Snapfile 文件來配置需要截屏的設備型號、語言、輸出路徑等,如下修改設備和語言
devices([
"iPhone 6",
"iPhone 6 Plus",
"iPhone 5",
"iPhone 4s"
])
languages([
"en-US",
["pt", "pt_BR"] # Portuguese with Brazilian locale
])
output_directory "./screenshots" #輸出路徑
clear_previous_screenshots true #清除之前圖片
- increment_build_number:設置Build號
increment_build_number # 設置Build號每次自增1
increment_build_number(
build_number: "75" # 設置一個指定的Build號
)
- set_info_plist_value:設置項目Info.plist文件中某個key
set_info_plist_value(
path: "./Info.plist", # 指定Info.plist文件的路徑
key: "CFBundleIdentifier", # 指定要修改的key
value: "com.krausefx.app.beta" # 指定key要設定的value
)
- update_info_plist:更新項目Info.plist文件中的bundle identifier 和 display name
Note:這個action允許你在building之前修改項目的Info.plist文件
update_info_plist(
display_name: "MyApp-Beta", # 更新display_name
app_identifier: "com.example.newappidentifier" # 更新app_identifier,在Xcode7這里只會修改Info.plist請調用 update_app_identifier來更新
)
- increment_version_number:設置Version號
Note:patch Version指補丁版本號(7.2.3中的3),minor Version指副版本號(7.2.3中的2),major Version指主版本號(7.2.3中的7)
version = increment_version_number # 設置patch Version每次自增1
increment_version_number # 設置patch Version每次自增1
increment_version_number(
bump_type: "patch" # 設置需要自增1的版本類型,可用選項:patch, minor, major
)
increment_version_number(
version_number: "2.1.1" # 設置版本號,會覆蓋bump_type參數的值
)
- update_app_identifier:更新項目的bundle identifier
update_app_identifier(
xcodeproj: PROJECT_FILE_PATH , #可選
plist_path: "MyApp/Info.plist", # 指定項目Info.plist文件的路徑
app_identifier: "com.test. MyApp" # 設置bundle identifier
)
- get_ipa_info_plist_value:獲取.ipa文件中Info.plist文件里某個key的value
get_ipa_info_plist_value(
ipa: "path.ipa", # 設置.ipa文件的路徑
key: "KEY_YOU_READ" # 指定要獲取value的key
)
- match:自動同步開發團隊遠程git中的證書和描述文件到本地
match(
git_url: "path", # 指定包含所有證書的git私有倉庫地址
git_branch: "branch", # 指定所使用的git分支
type: "appstore", # 指定創建證書的類型,可用選項:appstore(生產)、development(開發)
app_identifier: ["tools.fastlane.app", "tools.fastlane.sleepy"], # 程序的bundle identifier(s),多個時用逗號分隔
readonly: true, # true:僅獲取已經存在的證書和描述文件,而不生成新的
force: true, # 每次執行match時,是否更新描述文件
force_for_new_devices: true # 當Apple Developer Portal上的設備數量發生變化時,是否更新描述文件
)
- sigh:生成描述文件并存入當前文件夾
Note:當生成、匹配證書時,推薦使用match
sigh(
adhoc: true, # true:生成AdHoc profiles,false:生成App Store Profiles
development: ??? # 更新開發證書而不是生產證書
skip_install: ??? # 默認會自動添加證書到你的本地機器上,設置該參數可以跳過該步驟
force: true, # 更新描述文件并忽略其狀態,同時自動為ad hoc profiles添加所有設備
provisioning_name: ??? # 指定Apple Developer Portal(蘋果開發者中心網)上使用的描述文件名稱
ignore_profiles_with_different_name: true # 與provisioning_name參數聯合使用,true:當描述文件名稱完全匹配provisioning_name時才下載,false:不完全匹配也下載
filename: "myFile.mobileprovision" # 設置所生成描述文件的名稱,必須包含文件類型后綴. mobileprovision
)
- cert:獲取或生成最新可用的code signing identity
Note:當生成、匹配證書時,推薦使用match
cert
- update_project_provisioning:通過描述文件更新項目的code signing設置
update_project_provisioning(
xcodeproj: "Project.xcodeproj", # 項目.xcodeproj文件路徑
profile: "./watch_app_store.mobileprovision", # 描述文件路徑,必須包含文件類型后綴. mobileprovision
target_filter: ".*WatchKit Extension.*", # 使用正則表達式來過濾target名稱
build_configuration: "Release", # 使用正則表達式來過濾build configuration項,如果不指定該參數,則表示update_project_provisioning這個action作用于所有build configuration項
certificate: "path" # 蘋果根證書路徑
)
- resign:為已存在的. ipa文件重新Codesign
Note:當你的項目包含嵌套程序或者程序擴展,而這些嵌套程序、程序擴展需要使用各自的描述文件時,你需要提供多個描述文件
resign(
ipa: "path/to/ipa", # 指定需要進行重新簽名的.ipa文件路徑
signing_identity: "iPhone Distribution: Luka Mirosevic (0123456789)", # 指定簽名類型
#provisioning_profile: "path/to/profile" # 指定描述文件路徑,單個bundle identifier、描述文件時
provisioning_profile: {
"com.example.awesome-app" => "path/to/profile",
"com.example.awesome-app.app-extension" => "path/to/app-extension/profile"
} # 指定描述文件路徑,多個bundle identifier、描述文件時
)
- register_devices:注冊新設備到Apple Developer Portal
Note:這個action默認使用Appfile文件中指定的apple_id作為username參數的值,你可以自己指定username參數的值、或者使用環境變量ENV['DELIVER_USER']來覆蓋默認值
register_devices(
#devices_file: "./devices.txt", # 指定包含設備信息的文件路徑,文件具體格式參考https://devimages.apple.com.edgekey.net/downloads/devices/Multiple-Upload-Samples.zip
devices: {
"Luka iPhone 6" => "1234567890123456789012345678901234567890",
"Felix iPad Air 2" => "abcdefghijklmnopqrstvuwxyzabcdefghijklmn"
}, # 指定要注冊的設備列表,格式為:設備名稱 => UDID
username: "luka@goonbee.com" # 設置Apple ID
)
- pilot:上傳應用到TestFlight
testflight # pilot的別名
pilot
testflight(
changelog: “change”, # 當上傳一個新測試包時,提供測試包的更新信息
beta_app_description: “description”, # 當上傳一個新測試包時,提供測試包的描述信息
beta_app_feedback_email: “***@163.com”, # 當上傳一個新測試包時,提供測試包的email地址
skip_submission: true, # 是否跳過pilot這個action的發布步驟,而僅僅是上傳.ipa文件
distribute_external: true # 是否需要將應用發布給外部測試者
)
- pem:確保推送描述文件有效,如有需要則自動創建推送描述文件
pem(
development: true, # true:更新開發推送證書,false:更新生產推送證書
generate_p12: true, # 生成p12和gem文件
force: true, # true:即使舊推送描述文件依然可用,仍然創建新的推送描述文件
app_identifier: "net.sunapps.9", # optional app identifier,
save_private_key: true,
p12_password: 123456, # 所生成p12文件的密碼
new_profile: proc do |profile_path| # 如果生成了新的推送描述文件,該block會被調用
puts profile_path # 新的PEM文件的絕對路徑
# 添加上傳PEM文件到服務器的代碼
end
)
- deliver:上傳元數據、屏幕截圖、應用到AppStore
appstore # deliver的別名
deliver(
force: true, #忽略認證
itc_provider: "abcde12345" # iTMSTransporter的名字
)
- commit_version_bump:創建一個’Version Bump’提交操作,在increment_build_number之后使用
Note:這個action將會創建一個’Version Bump’提交操作,與increment_build_number聯合使用才有效。該action會檢索git倉庫,確保你僅僅修改了相關文件(如.plist、.xcodeproj文件)時,提交這些文件到git倉庫。如果你還有其他未提交的修改,這個action將會失敗。
commit_version_bump(
message: "message" # 設置提交信息,默認為Version Bump
)
- push_to_git_remote:push本地修改到遠程分支
push_to_git_remote # push”master”分支到遠程”origin”
push_to_git_remote(
local_branch: "develop", # 將被push的本地分支,默認為當前分支
remote_branch: "develop", # push的目的分支,默認為本地分支
force: true, # 是否push到遠程,默認為false
tags: false, # ???,默認為true
remote: "origin" # ???,默認為”origin”
)
- add_git_tag:給當前分支添加tag
add_git_tag(
tag: "my_custom_tag" # 設置tag
)
- ensure_git_branch:如果當前lane不是在指定git分支上進行的,將會拋出一個異常
Note:這個action會檢查你當前使用的git倉庫是不是從指定git分支上check out的,比如,一般我們會在特定的分支上發布應用,此時如果不是在正確的分支上,則該action會終止lane
ensure_git_branch # 默認為master分支
ensure_git_branch(
branch: 'develop' # 指定分支名,可用分支全名,也可用正則表達式自動匹配
)
- mailgun:發送成功、錯誤信息給你的團隊
mailgun(
postmaster: "MY_POSTMASTER",
apikey: "MY_API_KEY",
to: "DESTINATION_EMAIL", # 目標對象
from: "EMAIL_FROM_NAME", # 發送對象名稱
message: "Mail Body", # 郵件內容
subject: “subject”, # 郵件主題
success: true, # 本次build是否成功
app_link: "http://www.myapplink.com", # 所發布的應用鏈接
ci_build_link: "http://www.mycibuildlink.com",
template_path: "HTML_TEMPLATE_PATH”, # HTML郵件模板
reply_to: "EMAIL_REPLY_TO"
)
Fastlane進階
- 傳遞參數
從command line傳遞參數給你自定義的lane:
fastlane [lane] key:value key2:value2
e.g. fast lane deploy submit:false build_number:24
同時,你需要修改自定義的lane聲明包含|options|,才能訪問所傳的值:
before_all do |lane, options|
# ...
end
before_each do |lane, options|
# ...
end
lane :deploy do |options|
# ...
if options[:submit]
# Only when submit is true
end
# ...
increment_build_number(build_number: options[:build_number])
# ...
end
after_all do |lane, options|
# ...
end
after_each do |lane, options|
# ...
end
error do |lane, exception, options|
if options[:debug]
puts "Hi :)"
end
end
- 切換lanes
正在執行lane時,切換lanes:
lane :deploy do |options|
# ...
build(release: true) # that's the important bit
hockey
# ...
end
lane :staging do |options|
# ...
build # it also works when you don't pass parameters
hockey
# ...
end
lane :build do |options|
scheme = (options[:release] ? "Release" : "Staging")
ipa(scheme: scheme)
end
- 返回values
lane中定義的最后一行為返回值
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
關鍵字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
切換lane期間使用next時,會返回到上一個正在執行的lane
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
當你使用next提前終止正在執行的lane時,你所定義的after_each、after_all blocks依然會如常觸發;
在任意lane被調用前會先執行before_each blocks;
在任意lane成功執行完后會執行after_each blocks,如果lane執行失敗,則不會調用after_each blocks,而是調用error block;
e.g. 如下案例,會執行4次before_each、after_each
lane :deploy do
archive
sign
upload
end
lane :archive do
# ...
end
lane :sign do
# ...
end
lane :upload do
# ...
end
- 直接執行actions
如果你未將actions加入Fastfile中就想執行某個action,如下,但是這種方式在某些類型的parameters 時會失敗:
fastlane run notification message:"My Text" title:"The Title"
查看action具體信息(如可用options):
fastlane action [action_name]
- parameters、options的優先級
1.CLI parameter (e.g. gym --scheme Example) or Fastfile (e.g. gym(scheme: 'Example'))
2.Environment variable (e.g. GYM_SCHEME)
3.Tool specific config file (e.g. Gymfile containing scheme 'Example')
4.Default value (which might be taken from the Appfile, e.g. app_identifier from the Appfile)
5.If this value is required, you'll be asked for it (e.g. you have multiple schemes, you'll be asked for it)
- Note
1.如果需要,請在自定義lane聲明前導入其他Fastfile
2.定義一個新的lane時,請確保不會出現名字沖突
3.如果你用重寫一個已經存在的lane(比如重寫導入的Fastfile中的lane),請使用關鍵字override_lane
- 環境變量
你可以在Fastfile文件的同級目錄下的.env 或 .env.default文件中定義環境變量
- 私有lanes
直接執行某些lanes可能并沒有意義,這時你可以私有化他們:
private_lane :build do |options|
# ...
end
Fastlane實戰
Appfile
app_identifier "your identifier" # The bundle identifier of your app
apple_id "your id" # # Your Apple email address
team_id "3MK2KJ67U7" # Developer Portal Team ID
for_lane :testBeta do # 可在這里對自定義lane修改appId,identifier,也可以在自定義lane里面修改
app_identifier "YourAppId"
apple_id "YourAppleAccount"
end
# you can even provide different app identifiers, Apple IDs and team names per lane:
# More information: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Appfile.md
Fastfile
# Customise this file, documentation can be found here:
# https://github.com/fastlane/fastlane/tree/master/fastlane/docs
# All available actions: https://docs.fastlane.tools/actions
# can also be listed using the `fastlane actions` command
# Change the syntax highlighting to Ruby
# All lines starting with a # are ignored when running `fastlane`
# If you want to automatically update fastlane if a new version is available:
# update_fastlane
# This is the minimum version number required.
# Update this, if you use features of a newer version
fastlane_version "2.3.0"
default_platform :ios
PROJECT_FILE_PATH = './test1.xcodeproj'
APP_NAME = 'test1'
SCHEME_NAME = 'test1'
APPSTORE_IDENTIFIER = 'com.test.app'
TESTFlIGHT_IDENTIFIER = 'com.test.app6'
PUSH_IDENTIFIER = 'com.test.apptest'
PLIST_FILE_PATH = 'test1/Info.plist'
# 更新bundle信息
def update_app_bundle(bundle)
update_app_identifier(
xcodeproj: PROJECT_FILE_PATH ,
plist_path: "#{PLIST_FILE_PATH}",
app_identifier: bundle
)
end
# build number++
def prepare_version(options)
#增加版本號
say 'version number:'
say options[:version]
increment_version_number(
version_number: options[:version],
xcodeproj: PROJECT_FILE_PATH,
)
#增加build號 只能是整數和浮點數
say 'build number:'
say options[:build]
increment_build_number(
build_number: options[:build],
xcodeproj: "#{PROJECT_FILE_PATH}",
)
end
# 設置Info_plist_value里的值(也可直接用 update_info_plist )
def set_info_plist_value(path,key,value)
#sh 這里是fastline目錄里
sh "/usr/libexec/PlistBuddy -c \"set :#{key} #{value}\" ../#{path}"
end
# 打包 注:這里needClean 針對本人項目使用
def generate_ipa(needClean,exportMethod,options)
#say 'generate ipa'
fullVersion = options[:version] + '.' + options[:build]
gym(
project: "test1.xcodeproj",
#workspace: ‘./test1.xcworkspace',
scheme: 'test1',
clean: needClean,
output_directory: "../Test" + fullVersion,
output_name: 'test1.ipa',
#configuration: 'Release',
include_symbols: 'true',
include_bitcode: 'false',
archive_path: "../Test" + fullVersion,
export_method: "#{exportMethod}”
)
# sh "mv ./../build/#{APP_NAME}.app.dSYM.zip ./../build/#{APP_NAME}_#{fullVersion}_#{typePrefix}.app.dSYM.zip"
end
platform :ios do
before_all do
# ENV["SLACK_URL"] = "https://hooks.slack.com/services/..."
#cocoapods #執行cocoapods
end
desc "打App Store包"
lane :release do
ensure_git_branch(
branch: "develop"
)
# match(type: "appstore")
# snapshot
needClean = true
prepare_version(options)
update_app_bundle("#{APPSTORE_IDENTIFIER}")
generate_ipa(needClean,"app-store",options)
deliver(force: true)
# frameit
end
desc "打develop包"
lane :Develop do |options| #測試都是在本地已經有證書和描述文件情況下,如果沒有證書和對應描述文件,可打開cert和sigh(待測試)
#cert
#sigh(
#adhoc:true.
#app_identifier: "#{TESTFlIGHT_IDENTIFIER}"
#)
needClean = false
prepare_version(options)
update_app_bundle("#{TESTFlIGHT_IDENTIFIER}")
generate_ipa(needClean,"ad-hoc",options)
end
desc "打testFlight"
lane :beta do |options|
#cert
#sigh
needClean = false
prepare_version(options)
update_app_bundle("#{TESTFlIGHT_IDENTIFIER}")
generate_ipa(needClean,"app-store",options)
pilot
end
desc "打不同identifier Push包"
lane :enterprise do |options|
#cert
#sigh
#pem
needClean = false
prepare_version(options)
update_app_bundle("#{PUSH_IDENTIFIER}")
generate_ipa(needClean,"development",options)
end
after_all do |lane|
# This block is called, only if the executed lane was successful
# slack(
# message: "Successfully deployed new App Update."
# )
end
error do |lane, exception|
# slack(
# message: exception.message,
# success: false
# )
end
end
# More information about multiple platforms in fastlane: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Platforms.md
# All available actions: https://docs.fastlane.tools/actions
# fastlane reports which actions are used
# No personal data is recorded. Learn more at https://github.com/fastlane/enhancer
Snapfile
# Uncomment the lines below you want to change by removing the # in the beginning
# A list of devices you want to take the screenshots from
devices([
#"iPhone 6",
#"iPhone 6 Plus",
#"iPhone 5",
"iPhone 4s"
])
languages([
"en-US",
["pt", "pt_BR"] # Portuguese with Brazilian locale
])
# The name of the scheme which contains the UI Tests
# scheme "SchemeName"
# Where should the resulting screenshots be stored?
output_directory "./screenshots"
clear_previous_screenshots true # remove the '#' to clear all previously generated screenshots before creating new ones
# Choose which project/workspace to use
# project "./Project.xcodeproj"
# workspace "./Project.xcworkspace"
# Arguments to pass to the app on launch. See https://github.com/fastlane/snapshot#launch-arguments
# launch_arguments(["-favColor red"])
# For more information about all available options run
# snapshot --help
執行
fastlane develop version:B1.0 build:1
如需要打多個包可添加如下腳本
#!/bin/sh
#
# usage:
# > sh build.sh 1.0.0 1
#
versionNumber=$1 # 1.0.0
buildNumber=$2 # 1
rm -rf build
basicLanes="develop enterprise beta release"
for laneName in $basicLanes
do
fastlane $laneName version:$versionNumber build:$buildNumber
done
可添加參數 區別渠道
# channelIds="fir 91"
# for channelId in $channelIds
# do
# fastlane Channel version:$versionNumber build:$buildNumber channel_id:$channelId
done
使用homebrew安裝Jenkins
brew install jenkins
執行
sh build.sh 1.0.0 1
參考資料
1.Fastlane官網
2.fastlane Tutorial: Getting Started
3.fastlane 教程: 入門
4.iOS 持續集成之 fastlane + jenkins