1、安装
- 安装ruby
// 查看 Ruby 版本 % ruby -v // 查看 gem 的source % gem sources // 安装RVM工具 % RVMcurl -L get.rvm.io | bash -s stable // 列出可安装的ruby版本(有原始的ruby也有其他版本的版本) % rvm list known // 根据刚才列出的ruby版本,安装一个ruby版本 % rvm install ruby-xxxxx(xxx为版本号) - 安装Xcode命令行工具
% xcode-select --install - 安装fastlane
// 常规安装 % sudo gem install fastlane --verbose // 如果安装时出现错误无法安装,就使用以下命令安装 % sudo gem install -n /usr/local/bin fastlane - 验证安装结果
% fastlane --version
2、使用
-
cd到 项目的根目录,初始化fastlane配置:
% fastlane init -
出现选项,常用可选择打包至
TestFlight、Apple Store、自定义平台(蒲公英)
-
还需要选择要打包的scheme

-
过程中还需要输入 Apple账号与密码,这个信息会存储在钥匙串中,后续使用无需再输入密码(过程中还会检测当前项目的 App 与 App Identifier 是否已经在 iTunes Connect 和 Apple Developer 中)

-
初始化完成后,项目工程目录下会生成 fastlane文件夹,里面是 Fastlane 的一些配置文件
.env文件:根据需要手动创建,作用是 配置环境变量,这是个隐藏文件,需要Command + Shift + .查看隐藏文件% touch .env# // bundle id App_Identifier = "com.xxx.xxx" # // 开发者账号 Apple_Id = "xxx@126.com" # // 开发者App Store Connect Team ID(如果找不到,fastlane init选3能直接拉下来) Itc_Team_Id = "xxxx33366" # // 开发者TeamId Team_Id = "xxxxxYYA67" # // project的target scheme名称 Scheme = "D&P patient" # // ipa的名称 ipa = "LZProject.ipa" # // 项目xcodeproj Xcodeproj = "D&P patient.xcodeproj" # // 项目xcworkspace Workspace = "D&P patient.xcworkspace" # // ipa输出路径 -- Appstore Appstore_Output_Path = "build/appstore" # // ipa输出路径 -- Firim Firim_Output_Path = "build/firim" # // ipa输出路径 -- 蒲公英 Pgy_Output_Path = "build/pgy" # // firim 的token firim_api_token = "d58c5754d2aff40b4d883ddfefa0d079" # // 蒲公英 的api_key api_key = "c00de532996b9c848b40fb602d3xxxxx" # // 蒲公英 的user_key user_Key = "273c8527d579ef18d7939a2fd76xxxxx"Appfile:存放了 App 的基本信息,主要包括App_Identifier、AppID、Team_ID等:(通过ENV['Key']使用 .env文件)
Fastfile:编写和定制我们打包脚本的一个文件,所有自定义的功能都写在这里
2.1、Fastfile
2.1.1、action、lane
-
fastfile中通过
lane任务实现逻辑功能,其中包含多个action(还可以调用别的lane),诸如cer()、sigh()、gym()都是 action查看所有Action
fastlane actions
查看某个Action的参数说明
fastlane action [action_name]如(fastlane action gym) -
fastfile中的action:(详细参阅fastlane官方文档)
action 作用 cert创建和维护证书签名 sigh配置文件( force: true每次运行时重新生成配置文件,这将导致总是使用安装在本地机器上的正确签名证书)gym(build_app命令的别名)构建打包应用程序,会自动处理打包、签名等操作 deliver(upload_to_app_store的别名)上传应用程序和屏幕截图到App Store Connect pilot(upload_to_testflight的别名)TestFlight上传构建并处理其管理 scan 自动化测试 match 团队中同步证书和配置文件 pen 管理推送配置文件 produce 在App Store Connect创建新的App -
fastfile中固有一些lane:
固有lane 作用 before_all执行一次脚本之前首先执行的代码,我们可以在这里面执行一些公共的东西,比如git_pull,cocoapods after_all执行结束后,处理共有的后置逻辑 before_each 每次执行 lane 之前都会执行一次 after_each 每次执行 lane 之后都会执行一次 error在执行上述情况任意环境报错都会中止并执行一次
2.1.2、打包至蒲公英
首先需要安装蒲公英的 Fastlane 插件:
% fastlane add_plugin pgyer
修改fastlane文件:
default_platform(:ios)
platform :ios do
desc "以 ad-hoc 方式打包并上传到蒲公英"
lane :ad_pgy do
configuration = "Debug"
puts "自动生成 Provisioning Profiles 文件"
sigh(
# // 每次运行时重新生成配置文件
force: true,
# // 指定输出的文件夹地址
output_path: "./fastlane/archive/sign",
# // 是否为 AdHoc 证书(设为 false 或不写默认为 AppStore 证书)
adhoc: true
)
puts "以 ad-hoc 方式打包"
gym(
clean: true,
# // 指定打包所使用的输出方式 (可选: app-store, package, ad-hoc, enterprise, development)
export_method: "ad-hoc",
# // 指定打包方式 (可选: Release, Debug)
configuration: configuration,
# // 归档文件路径
archive_path:"./fastlane/Archive",
# // 指定输出ipa包的文件夹地址
output_directory: "./fastlane/archive/" + Time.new.strftime("%Y-%m-%d-%H:%M:%S"),
# // 输出ipa的文件名为当前的build号
output_name:get_build_number(),
# // 指定项目的 scheme 名称
scheme: ENV['Scheme'],
# // ipa文件是否包含symbols
include_symbols:true
# sdk:"iOS 12.0",
# export_options: {
# provisioningProfiles: {
# "com.example.bundleid" => "Provisioning Profile Name",
# "com.example.bundleid2" => "Provisioning Profile Name 2"
# }
# }
)
puts "上传 ipa 包到蒲公英"
pgyer(
# 蒲公英 API KEY
api_key: ENV['Api_key'],
# 蒲公英 USER KEY
# user_key: ENV['user_Key']
)
end
end
| gym参数 | 作用 |
|---|---|
puts | 在终端中打印 |
| 配置项 clean | 每次执行前是否清空工程 |
export_method | 用于导出archive的方法,有效值为:app-store、ad-hoc、development、validation、package、enterprise、developer-id和mac-application |
| configuration | 构建应用程序时要使用的配置,默认为Release |
| output_directory | 输出ipa文件目录 |
| output_name | 输出的ipa名字 |
| scheme | project的Scheme,确保它被Mark为Share |
| export_options | 输出配置项、Plist,及发布证书相关(如果使用了fastlane的match,就不需要配置该项,除非你传递一个plist文件到export_options:export_options("./ExportOptions.plist")) |
-
记得到Apple账户中创建并下载安装ad-hoc证书
-
命令行执行
% fastlane ad_pgy即可完成上传至蒲公英(ad_pgy是我们自定的lane名称) -
蒲公英支持
Webhook机制,可以将事件消息通知分发出去;所以当我们发布新版本时可以给钉钉或微信发送消息,通知App版本更新 -
如果一直卡在
% bundle update,那么可以尝试更换RubyGems镜像// 替换镜像 % gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ // 执行查看当前镜像,是否替换成功 $ gem sources -l https://gems.ruby-china.com
2.1.3、打包至TestFlight
- 打包至TestFlight,主要需要4步:
gym:打包app_store_connect_api_key:API密钥配置、认证和使用一个或多个 Apple 服务,其中就包括TestFlight- 注:设置了这个就不能再设置 username ,app_identifier两个参数了,否则打包时报错
upload_to_testflight:上传TestFlightnotification:通知
完整配置示例:
default_platform(:ios)
platform :ios do
desc "Push a new release build to the App Store"
# // 生产环境打包
lane :release do |options|
desc "Release>>>>>>>App开始打包..."
// 打包
gym(
clean: true,
output_directory: './fastlane/release',
output_name:"dp.ipa",
scheme: 'D&P patient',
configuration: 'Release',
# // include_bitcode: true,
include_symbols: true,
codesigning_identity:"iPhone Distribution: Harmonious Home (Beijing) xxxxx xxxxxxx Co., Ltd. (xxxxxYYA67)",
# // export_xcargs: "-allowProvisioningUpdates"
export_options: {
method: 'app-store',
provisioningProfiles: {
"com.xxx.xxxxx" => "QZKProvisioning"
},
}
)
notification(app_icon:"./fastlane/icon.png",title:"LZManager",subtitle: "打包成功,已导出安装包>>>>>>>>", message: "准备发布中....")
// API密钥
api_key = app_store_connect_api_key(
key_id: "xxxxx99597",
issuer_id: "xxxxxxxx-xxxx-xxxx-e053-5b8c7c11a4d1",
# // key_filepath: "./fastlane/xxxxx99597.p8",
key_content: "-----BEGIN PRIVATE KEY-----\nxxxxxxxxxxxxxxxxxxxxAgEGCCqGSM49AwEHBHkwdwIBAQQggF/JW87NuzDW0FeLGU3FE/PboJO5I660PdyFdViZm8SgCgYIKoZIzj0DAQehRANCAARnglciq//2VNJDel9gfhqIzE4+q/quQ8xeHFcVt0NiYYYh7iSHhNM+DHJ2I/LFVfUDazS/P9Cl06+oeZsBxu7j\n-----END PRIVATE KEY-----",
duration: 1200, # optional (maximum 1200)
in_house: false # optional but may be required if using match/sigh
)
// 将打包的ipa上传至TestFlight
upload_to_testflight(
api_key: api_key,
ipa: "./fastlane/release/dp.ipa",
skip_waiting_for_build_processing: true,
skip_submission:true
// 设置了app_store_connect_api_key,因此这两个参数注释掉
# // username: "xxxxx@126.com",
# // app_identifier: "com.xxx.xxxxxxx",
)
notification(app_icon:"icon.png",title:"LZManager",subtitle: "IPA上传成功", message: "自动打包完成!")
end
end
等待一段时间后成功显示上传成功:

| gym参数 | 作用 |
|---|---|
| export_xcargs | 自动管理证书、PP文件(我们不进行自动管理,要去掉Automatically manage signing的勾选) |
codesigning_identity | 代码签名标识名称,必须和名字完全匹配 |
export_options | provisioningProfiles中存放手动创建并下载安装的PP文件(不带.mobileprovision的后缀) |


| app_store_connect_api_key参数 | 作用 |
|---|---|
key_id | 密钥ID,在 AppStore Connect -> 用户和访问 -> 密钥 中创建并获取 |
issuer_id | 标识创建认证令牌的发放者 |
| key_filePath | p8文件的路径 |
key_content | p8文件的内容,未编码直接提供,换行处需要加入\n(只提供一次查看密钥的机会,会弹出网页展示密钥,注意保存!,-----BEGIN PRIVATE KEY-----\nxxxxx\n-----END PRIVATE KEY-----) |
| is_key_content_base64 | 是否key的内容经过base64编码 |
| in_house | 是app store还是 enterprise |

| upload_to_testflight参数 | 作用 |
|---|---|
api_key | 传入 app_store_connect_api_key |
ipa | 上传至TestFlight的ipa包路径,与gym中的output对应上 |
| skip_waiting_for_build_processing | 如果设置为true, distribute_external选项将不起作用,也不会向测试人员分发任何构建 |
| skip_submission | 跳过pilot的分发动作,只上传ipa文件 |
2.1.4、打包至App Store
default_platform(:ios)
platform :ios do
# ======================== 执行lane前的操作 ========================
# // 所有lane执行之前,可以做如执行cocoapods的pod install
before_all do |lane, options|
# // 更新pod
# cocoapods(use_bundle_exec: FALSE)
# // 清理缓存
xcclean(scheme: "D&P patient")
end
# // 将正式应用打包并上传到App Store,release是自己取的名字,因为是发布正式版,所以取名叫 release
desc "Push a new release build to the App Store"
lane :release do
# // 打包之前,先将build号递增
increment_build_number(xcodeproj: "D&P patient.xcodeproj")
# // 对应用进行打包
build_app(
clean: true,
output_directory: './fastlane/upload',
output_name:"dp.ipa",
workspace: "D&P patient.xcworkspace",
scheme: "D&P patient",
configuration: 'Release',
include_symbols: true,
export_method: "app-store",
codesigning_identity:"iPhone Distribution: Harmonious Home (Beijing) xxxxx xxxxxxx Co., Ltd. (xxxxxYYA67)",
export_options: {
method: 'app-store',
provisioningProfiles: {
"com.qzk.patient" => "QZKProvisioning"
},
}
)
# // 将打包完的应用上传到AppStore
upload_to_app_store(
ipa: "./fastlane/upload/dp.ipa",
username: "xxxxx@126.com",
app_identifier: "com.xxx.xxxxxxx",
app_version: "1.0.5",
# build_number: "10",
force: true, #跳过HTML报告验证
submit_for_review: true, # 上传完所有内容后,提交新版本以供审核
automatic_release: true, # 应用程序通过审核后是否应该自动发布?(不能与auto_release_date一起使用)
skip_metadata: true,
skip_screenshots: true,
submission_information: {
add_id_info_uses_idfa: false,
add_id_info_serves_ads: false,
add_id_info_tracks_install: false,
add_id_info_tracks_action: false,
add_id_info_limits_tracking: false
}
)
end
# ======================== 执行lane成功后的操作 ========================
# // 所有lane完成之后,可以使用参数lane来区分
after_all do |lane, options|
puts "所有lane执行完毕"
end
# ======================== 执行lane失败后的操作 ========================
# // 所有lane失败之后,可以使用参数lane来区分
error do |lane, options|
puts "lane执行失败"
end
end
| upload_to_app_store参数 | 作用 |
|---|---|
| force | 跳过HTML报告验证 |
submit_for_review | 上传完所有内容后,提交新版本以供审核 |
automatic_release | 应用程序通过审核后是否应该自动发布?(不能与auto_release_date一起使用) |
submission_information | 设置提交时的一连串询问,包括是否使用IDFA、广告之类的 |
- 不同于TestFlight使用了API密钥而必须省略username和app_identifier,我们这里写上能避免一些错误
2.2、报错、警告
2.2.1、Cannot set build number with plist path containing $(SRCROOT)

报错原因:
自增build号increment_build_number方法不能包含 $(SRCROOT)
解决方案:
在 Build Settings 中将info.plist file的路径由原来的 $(SRCROOT)/Info.plist 改为相对路径(如果改错了Build Settings左边的Info就没有东西)

2.2.2、warning: The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0
/Users/lz/Downloads/DPSourceTree/QZ patient /Pods/Pods.xcodeproj: warning: The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 11.0 to 16.0.99. (in target 'IQKeyboardManager' from project 'Pods')
报错原因:
项目部署版本与Pod库部署版本不匹配,可能会有非常多的库报这个问题

解决方案:
podfile末端添加以下代码:
post_install do |installer|
installer.pods_project.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
end
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
end
end
end
2.2.3、Exit status: 65
报错原因:
归档失败,明显是gym步骤中出现问题,查看图中提示的日志可以帮助我们分析报错
解决方案:
借助日志查看gym中配置问题;我犯的错误是:
- codesigning_identity 配置错误
- 使用了自动证书管理 Automatically manage signing
// 改后的
gym(
......
codesigning_identity:"iPhone Distribution: Harmonious Home (Beijing) xxxxx xxxxxxx Co., Ltd. (xxxxxYYA67)",
# // export_xcargs: "-allowProvisioningUpdates"
export_options: {
method: 'app-store',
provisioningProfiles: {
"com.xxx.xxxxx" => "QZKProvisioning"
},
}
)
2.2.4、Exit status: 70
报错原因:
找不到PP文件,因为多写入了 .mobileprovision 后缀
2.2.5、invalid curve name(上传TestFlight过程报错)
报错原因:
上传 TestFlight过程中配置API密钥 的 key_filepath 读取出错
解决方案:
改用 key_content 直接存放 API密钥
// API密钥
api_key = app_store_connect_api_key(
......
# // key_filepath: "./fastlane/xxxxx99597.p8",
key_content: "-----BEGIN PRIVATE KEY-----\nxxxxxxxxxxxxxxxxxxxxAgEGCCqGSM49AwEHBHkwdwIBAQQggF/JW87NuzDW0FeLGU3FE/PboJO5I660PdyFdViZm8SgCgYIKoZIzj0DAQehRANCAARnglciq//2VNJDel9gfhqIzE4+q/quQ8xeHFcVt0NiYYYh7iSHhNM+DHJ2I/LFVfUDazS/P9Cl06+oeZsBxu7j\n-----END PRIVATE KEY-----",
)
2.2.6、Use of Advertising Identifier (IDFA) is required to submit(打包发布值Apple Store过程报错)
报错原因:
手动发布时会询问的那些是否使用IDFA之类的问题没设置
解决方案:
使用submission_information将这些问题统一设置(报错已经给出了提示)
submission_information: {
add_id_info_uses_idfa: false,
add_id_info_serves_ads: false,
add_id_info_tracks_install: false,
add_id_info_tracks_action: false,
add_id_info_limits_tracking: false
}
2.2.7、Error: Unable to validate your application
Error: Unable to validate your application. Sign in with the app-specific password you generated.
If you forgot the app-specific password or need to create a new one, go to appleid.apple.com
报错原因:
苹果开发者账号开启了双重认证,所以仅有账号和密码还是不能够成功登录账号的
解决方案:
使用App专用密码,并把 keychain下开发者账号对应的密码 改为该 application specific password

- 执行命令,生成FASTLANE_SESSION,将其备份
% fastlane spaceauth -u xxxxxxx@126.com .bash_profile中将 专用密码 与 FASTLANE_SESSION 配置为环境变量:
export FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD=xxxx-xxxx-xxxx-ezag export FASTLANE_SESSION=value: DAWTKNV323952cf8084a204fb20ab2508441a07d02d3f1dd928eb250cc23560916dcd3b2552522601f91a8f412f4aa7efcc50d86d4ce59767c7f8a905f984825f851abedd8905106e156fc61823ae3795e32d85fad02324d33a7ac3a907b4c66faa512180b0f9bddbe88ac50e8cfa3ec5481a2224210d33561e51ddba3dabeae5cd2d25cac2142a745a8077cc4f822dfdb335bf94a58835c73024eba1c0d9188c45532d699036051bffde871a0fb167d984aeb333cef0ea90e9d2ba52284df2e2e72def2cfa997fcebdfb3702b0798621284f87db1881391ff6014acbf097622429e5a0dc591d32cb7d49ad799e5a3d482024457995abf7cab5d90292fd92fd57f093ef6a0af006a0d8aac42aa9a1d680c01a69d0c6771b4636581f4dfb393e5b90a4dc5b5e2807d759505ffd4d2b753c936c5dd06602dc9207d2f31946cf8cce8459b3ac6ad939e0f737eeda771b1c8e6238a1cac1ee0529b02c4d9487426cfb15d74dc4b1f141c12d085b12e3cff62ef72e521578c68c3f5507f3e11cda4bd8bb93cacb4afac28b8ff8eb9fae0cdde4e8445374305d843abd10aa09ca2b38a7966ab3a8c6a45066632f6dce724fdd0714b1edda686bf2e1cc352c6f520f5f00eab7ec5668ad49e8214cdc2a101c2fecc541992d8d95cf6a01e3e5f4f2d03fe2d5cda106768914a74e4101e100a57c028dade97fc2fab9ae9ee641c3b95d5bfxxxxxxxxxxxxxxxxxxx\n- 执行下边命令使新增的环境变量生效,分别执行以下命令查看环境变量是否设置成功
// 使环境变量配置生效 % source ~/.bash_profile // 检测配置能否成功打印出来 % echo $FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD % echo $FASTLANE_SESSION注意:
- 生成的FASTLANE_SESSION,真正的实体要从
第一个value处开始到第一个value结束,不要全部一股脑全部配置到环境变量文件中,否则会因为格式问题导致变量设置失败,=号左右不留空格,否则也会出问题 - 如官网提示,使用application specific password的情况下,deliver不要做上传二进制之外的其他操作,比如设置元数据等,否则可能会导致方案失效
- application specific password和FASTLANE_SESSION的有效期为一个月左右,所以切记要及时对环境变量和钥匙串对应的密码进行更新
- 生成的FASTLANE_SESSION,真正的实体要从
- 使用CredentialsManager重设钥匙串对应的开发者账号密码,密码是上边的 App专用密码
如果设置后又要执行
% fastlane spaceauth -u xxxxxxx@126.com获取FASTLANE_SESSION,会报错获取不到,但系统会提示要你先执行% unset FASTLANE_SESSION
3、进阶使用
3.1、options参数传递
在执行shell脚本之类的都是可以传递一些参数的,fastlane也是有的,options就是存储了我们在命令行中执行lane时传递的参数的字典,在befor_all、after_all、各种lane里都可以使用这个options
# 使用key:value来传递一组对应的参数
fastlane laneName key:value key2:value2
3.2、接收options参数
platform :ios do
before_all do |lane, options|
#options参数
value = options[:key]
value2 = options[:key2]
end
lane :laneName do |options|
...
end
end
3.3、private_lane
私有lane,命令行无法调用,只能fastfile中使用
platform :ios do
# 相当于全局变量
build_config = "debug"
before_all do |lane, options|
# 调用私有lane deal_param 并将options传递过去
deal_param options
end
lane :laneName do |options|
...
end
# 私有lane,比如把传入的build参数进行一下处理
private_lane :deal_param do |options|
build_config = build_config ? build_config : "debug"
build_config.capitalize!
end
end
结语
本文尽量列出了笔者使用中遇到的各种问题,但这只是fastlane丰富功能中的冰山一角,使用中还需要查看 Fastlane官网,它真的太好用了!下篇再结合Jenkins出篇文章
童鞋们也可以参考下面的文章丰富了解:自动化打包之fastlane