fastlane介绍
fastlane
是为您的iOS
和Android
应用程序自动化测试部署和发布的最简单方法。🚀 它可以处理所有繁琐的任务,例如生成屏幕截图、处理代码签名和发布您的应用程序。(来自官网docs.fastlane.tools/)。
iOS打包流程
在使用fastlane
进行部署打包前需要了解iOS的打包流程和一些基础知识。这里和以往手动打包流程不同,我们需要知道手动打包背后的操作流程,知道我们之前为什么要这样做。
常见的打包流程如下:
虽然不是每个步骤每次都会遇上,但整体还是非常繁琐的。接下来将通过fastlane
来实现这个流程,尽可能的解放双手。
安装
fastlane
的安装方式建议参照官网,这里也贴了三种方式,官方推荐使用工具Bundler安装。
通过Bundler安装
- 安装Bundler:
gem install bundler
复制代码
如果出现You don't have write permissions for the...
的错误,是因为没有权限,请在命令前加上sudo
。
sudo gem install bundler
复制代码
2.添加依赖
在项目的根目录创建./Gemfile
文件,然后在里面添加如下内容。
如果找不到Gemfile
文件,可以尝试在需要创建的目录执行:bundle init
。
source "https://rubygems.org"
gem "fastlane"
复制代码
3.更新bundler
bundle update
复制代码
通过Homebrew安装(macOS)
brew install fastlane
复制代码
系统Ruby + RubyGems (macOS/Linux/Windows)
不建议在您的本地环境中使用此方法,但您仍然可以将fastlane
安装到系统Ruby
的环境中。
sudo gem install fastlane
复制代码
fastlane的工作方式
简单来说,fastlane
就是将打包的各个流程连接起来,然后为我们提供了很多非常方便的方法。
初始化项目
cd到项目目录
fastlane init
复制代码
这个时候项目根目录下会出现fastlane
文件夹,里面会有Appfile
和Fastfile
文件。
Appfile
Appfile
像一个配置文件,用来记录一些基础信息,如Apple ID
、Bundle Identifier
等。常见的内容如下,更详细的介绍和使用请前往官网:docs.fastlane.tools/advanced/Ap…
app_identifier "net.sunapps.1" # The bundle identifier of your app apple_id
"felix@krausefx.com" # Your Apple email address
复制代码
从Fastfile
使用中访问这些值
identifier = CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
team_id = CredentialsManager::AppfileConfig.try_fetch_value(:team_id)
复制代码
Fastfile
Fastfile
文件则是整个工作流程的具体实现了。官网介绍:docs.fastlane.tools/advanced/Fa…
一个最简单的流程定义:
lane :my_lane do
# Whatever actions you like go in here.
end
复制代码
fastlane
就是由这样的一个个简单流程加上常量的定义组成的。下面会介绍一些特殊的流程。
before_all
before_all do |lane|
cocoapods #这里会执行pod install
end
复制代码
顾名思义可知该流程是在其他流程开始之前执行的。
after_all
after_all do |lane|
say("Successfully finished deployment (#{lane})!")
slack(
message: "Successfully submitted new App Update"
)
sh("./send_screenshots_to_team.sh") # Example
end
复制代码
该流程则是在将成功执行所选通道之后调用。
error
在任何流程(before_all、lane 或 after_all)中发生错误时,都将执行此流程。您可以使用该error_info
属性获取有关错误的更多信息。
error do |lane, exception|
slack(
message: "Something went wrong with the deployment.",
success: false,
payload: { "Error Info" => exception.error_info.to_s }
)
end
复制代码
.env环境配置
官方文档:docs.fastlane.tools/advanced/ot…
为了更灵活使用Appfile
的内容,可以引入.env
文件来进行环境配置。这样在执行命令的时候可以在后面加上环境变量,以达到使用不同配置的目的,在复杂的项目环境下非常好用。
文件名命名规则是.env.<environment>
,例如:.env.development
、.env.default
。
在命令行中使用:
fastlane <lane-name> --env development
复制代码
文件内容栗子:
WORKSPACE=YourApp.xcworkspace
HOCKEYAPP_API_TOKEN=your-hockey-api-token
复制代码
.env
文件的使用:
Appfile
文件中使用.env
方式为直接读取变量即可:
xcworkspace ENV['WORKSPACE']
hockey-api-token ENV['HOCKEYAPP_API_TOKEN']
复制代码
注意:因为是.env
文件是.开头文件,默认是在finder
中隐藏的,需要通过执行一下命令来显示
defaults write com.apple.finder AppleShowAllFiles TRUE
killall Finder
复制代码
Lanes
命令行参数传递
官网介绍:docs.fastlane.tools/advanced/la…
要将参数从命令行传递到您的lane
,请使用以下语法:
fastlane [lane] key:value key2:value2
fastlane deploy submit:false build_number:24
复制代码
在lanes
中接受传入的值是通过options
实现的,栗子:
lane :deploy do |options|
# ...
if options[:submit]
# Only when submit is true
end
# ...
increment_build_number(build_number: options[:build_number])
# ...
end
复制代码
lane
之间的调用
请参考以下代码:
lane :deploy do |options|
# ...
build(release: true) # that's the important bit
# ...
end
lane :build do |options|
build_config = (options[:release] ? "Release" : "Staging")
build_ios_app(configuration: build_config)
end
复制代码
正式使用
在了解基本流程和工作方式之后,我们就可以逐步来实现我们的流程了。这里我们将的目标定义为将打包上传到蒲公英。
首先创建基本的框架:fastlane init
,命令行就会出现如下界面。
这里选择的是4,然后会出现fastlane
文件夹了。Fastfile
文件的内容如下:
# 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
#
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
default_platform(:ios)
platform :ios do
desc "Description of what the lane does"
lane :custom_lane do
# add actions here: https://docs.fastlane.tools/actions
end
end
复制代码
这个时候可以试一下执行fastlane
:
fastlane custom_lane
复制代码
因为还未写入任何东西所以执行过程非常迅速。
我们将custom_lane
修改为beta_lane
,在添加上before_all
、after_all
、error
的lane。
#...
default_platform(:ios)
platform :ios do
desc "Description of what the lane does"
lane :custom_lane do
# add actions here: https://docs.fastlane.tools/actions
end
# 所有lane之前,可以适用参数lane来区分
before_all do |lane, options|
end
# 所有lane完成之后,可以适用参数lane来区分
after_all do |lane|
end
# 所有lane失败之后,可以适用参数lane来区分
error do |lane, exception|
end
end
复制代码
拉取分支
我们希望可以切换到我们打包的分支。如果当前目录还有未提交的内容,需要抛出异常或者直接提交。我们可以看一下官方给了我们哪些操作控件:docs.fastlane.tools/actions/ 中Source Control
部分展示了我们可以操作的命令,我们可以根据不同的需求去实现。根据我们的需求,没有找到切换分支的命令,但是有ensure_git_status_clean
、ensure_git_branch
的命令,可以确保打包时分支的正确。
这里我们可以设置默认分支,这样就不需要每次都传入参数了,所以我们新建文件:.env.default
,输入内容:
BRANCH:release
复制代码
在Appfile
文件中输入:
branch ENV['BRANCH']
复制代码
- 新建
lane
:source_control
加入我们逻辑之后,fastFile
的内容如下:
default_platform(:ios)
platform :ios do
desc "Description of what the lane does"
lane :beta_lane do
# add actions here: https://docs.fastlane.tools/actions
end
before_all do |lane, options|
source_control
end
#after_all lane ...
#error lane ...
lane :source_control do |lane, options|
branch = CredentialsManager::AppfileConfig.try_fetch_value(:branch)
ensure_git_status_clean # 如果当前工作空间还有未提交的内容就会抛出异常
#如果当前分支不是 指定的分支 将会抛出异常
ensure_git_branch(
branch: branch
)
end
end
复制代码
这里可以使用fastlane beta
测试一下了。
如果当前工作区间还有未提交的内容将会卡在这里:
如果当前分支不是我们设置的分支,流程将会卡在这里:
更新pod
fastlane
提供您命令:cocoapods
这里相当于执行pod install
。
选择Target
这个将会配置scheme
参数,这里会在.env
文件和Appfile
文件中加入配置,以便后续使用。
版本号修改
这里的版本号包含Version
和Build
,在XCode
中展示为:
首先制定好版本变更的规则,这里
Version
一般不会变动,可以作为一个可选参数传入。Build
是一个默认每次打包都会增1,这里的规则为:yyyyMMdd
+ number
, yyyyMMdd
为年月日,number
每次新增。例如2021年9月9日第一次打包,Build
为2021090901
。与此同时会给当前的版本打上tag
。
#修改version number
lane :version_number_lane do |lane, options|
version_number = options[:version_number]
if version_number
increment_version_number(
version_number: version_number # Set a specific version number
)
end
end
#修改build
lane :build_number_lane do |lane, options|
build_number = get_build_number#获取项目的build_number
timeString = build_number[0,8]#取前8位时间相关的字符串
build = build_number[8,2]#获取最后两位字符串
today = Time.new; #获取当前时间
todayString = today.strftime("%Y%m%d")
#如果时间一样则表示是同一天,build+1, 否则build = 1
if timeString == todayString
b = Integer(build)
b = b + 1
if b < 10
build = "0" + b.to_s
else
build = b.to_s
end
else
build = "01"
end
build_number = todayString + build
#官方文档里并没有选择scheme的入参,所以当一个xcworkspace下有两个targets时,他们的build全部都会被更新。
increment_build_number(
build_number: build_number # set a specific number
)
# 打上git标签
add_git_tag(
tag: build_number
)
end
复制代码
clean项目 清除缓存
xcclean
这里放在befor_all
下
before_all do |lane, options|
source_control
cocoapods(use_bundle_exec: false)
xcclean
end
复制代码
验证
iOS
打包前都需要进行身份验证,官方推荐的方式是App Store Connect API key
,我们这里就使用这种方式。
配置App Store Connect API key
- 首先需要使用有管理权限的AppleId登录
AppStoreConnect
:
- 在创建完成之后,需要下载p8文件,然后在放到项目里使用。
- 创建环境配置参数,主要包括
key_id
、issuer_id
、key_filepath
。 准备完毕就可以使用App Store Connect API key
了:
key_id = CredentialsManager::AppfileConfig.try_fetch_value(:key_id)
issuer_id = CredentialsManager::AppfileConfig.try_fetch_value(:issuer_id)
key_filepath = CredentialsManager::AppfileConfig.try_fetch_value(:key_filepath)
lane :beta_lane do
api_key = app_store_connect_api_key(
key_id: key_id,
issuer_id: issuer_id,
key_filepath: key_filepath,
duration: 1200, # optional (maximum 1200)
in_house: false # optional but may be required if using match/sigh
)
end
复制代码
证书下载
这里用到的action
是cert,在使用之前需要先配置文件输出目录,这里放到了根目录的build
文件夹下。
cert(output_path: output_path, api_key: api_key)
复制代码
签名
这里用到的action
是sigh。
sigh(output_path: output_path, readonly: false, adhoc: true, app_identifier: app_identifier)
复制代码
构建
这里用到的action
是gym。通过文档可以了解到详细的参数配置。
gym(
scheme: scheme,
export_method: "ad-hoc",
buildlog_path: "fastlanelog",
output_directory: output_path,
include_bitcode: false,
configuration: "Release",
)
复制代码
上传包到蒲公英
安装方法可见蒲公英文档
pgyer(api_key: pgy_apikey, user_key: pgy_userkey, password: "123456", install_type: "2")
复制代码
到这里打包上传到蒲公英的整个流程就走完了。
完整Fastfile文件内容
default_platform(:ios)
platform :ios do
scheme = ENV['SCHEME']
xcworkspace = ENV['XCODEPROJ']
key_id = ENV['KEYID']
issuer_id = ENV['ISSUERID']
key_filepath = ENV['KEYFILEPATH']
output_path = './build'
app_identifier = ENV['APPIDENTIFIER']
branch = ENV['BRANCH']
pgy_apikey = ENV['PGYAPIKEY']
pgy_userkey = ENV['PGYUSERKEY']
lane :beta_lane do |options|
# add actions here: https://docs.fastlane.tools/actions
if options
if options.key? :version_number
version_number_lane(version_number:options[:version_number])
end
end
api_key = app_store_connect_api_key(
key_id: key_id,
issuer_id: issuer_id,
key_filepath: key_filepath,
duration: 1200, # optional (maximum 1200)
in_house: false # optional but may be required if using match/sigh
)
cert(output_path: output_path, api_key: api_key)
sigh(output_path: output_path, readonly: false, adhoc: true, app_identifier: app_identifier)
gym(
scheme: scheme,
export_method: "ad-hoc",
buildlog_path: "fastlanelog",
output_directory: output_path,
include_bitcode: false,
configuration: "Release",
)
pgyer(api_key: pgy_apikey, user_key: pgy_userkey, password: "123456", install_type: "2")
end
before_all do |lane, options|
source_control
cocoapods(use_bundle_exec: false)
build_number_lane
xcclean(scheme: scheme)
end
# 所有lane完成之后,可以适用参数lane来区分
after_all do |lane|
end
# 所有lane失败之后,可以适用参数lane来区分
error do |lane, exception|
end
lane :source_control do |lane, options|
ensure_git_status_clean # 如果当前工作空间还有未提交的内容就会抛出异常
#如果当前分支不是 指定的分支 将会抛出异常
ensure_git_branch(
branch: branch
)
end
lane :add_tag_lane do |lane, options|
tag = options[:git_tag]
puts tag
if tag
add_git_tag(
tag: git_tag
)
end
end
#修改version number
lane :version_number_lane do |options|
increment_version_number(
version_number: options[:version_number]
)
end
#修改build
lane :build_number_lane do |lane, options|
build_number = get_build_number#获取项目的build_number
timeString = build_number[0,8]#取前8位时间相关的字符串
build = build_number[8,2]#获取最后两位字符串
today = Time.new; #获取当前时间
todayString = today.strftime("%Y%m%d")
#如果时间一样则表示是同一天,build+1, 否则build = 1
if timeString == todayString
if build[0,1] == "0"
build = build_number[9,1]
end
b = Integer(build)
b = b + 1
if b < 10
build = "0" + b.to_s
else
build = b.to_s
end
else
build = "01"
end
build_number = todayString + build
#官方文档里并没有选择scheme的入参,所以当一个xcworkspace下有两个targets时,他们的build全部都会被更新。
increment_build_number(
build_number: build_number # set a specific number
)
# 打上git标签
add_git_tag(
tag: build_number
)
# git_commit(message: "update build_number")
end
end
复制代码
上传包至TestFlight
原理和前面的一样,这里直接贴lane
部分
lane :upload_testFlight_lane do |options|
if options
if options.key? :version_number
version_number_lane(version_number:options[:version_number])
end
end
api_key = app_store_connect_api_key(
key_id: key_id,
issuer_id: issuer_id,
key_filepath: key_filepath,
duration: 1200, # optional (maximum 1200)
in_house: false # optional but may be required if using match/sigh
)
cert(output_path: output_path, api_key: api_key)
sigh(output_path: output_path, readonly: false, adhoc: false, app_identifier: app_identifier)
gym(
scheme: scheme,
export_method: "app-store",
buildlog_path: "fastlanelog",
output_directory: output_path,
include_bitcode: false,
configuration: "Release",
)
#这里只上传,不分发
upload_to_testflight(
api_key:api_key,
app_identifier:app_identifier,
skip_submission: true ,
team_id:team_id,
)
end
复制代码