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