前言
Fastlane是一套使用Ruby
写的自动化工具集,目的是为了简化Android
和iOS
的部署过程,自动化你的工作流。它可以简化一些乏味、单调、重复的工作,像截图、代码签名以及发布App
。这里主要是为了记录一次自己Mac
环境下配置fastlane
的过程。下图是Fastlane
的主要流程和功能
Fastlane文档
安装最新Xcode命令行工具
xcode-select --install
如果你没有安装,会弹出对话框,点击安装即可。
如果你已经安装过了,会有如下提示
xcode-select: error: command line tools are already installed, use "Software Update" to install updates
Fastlane安装
sudo gem install fastlane -NV
或者
brew cask install fastlane
推荐使用RubyGems
安装。
注意⚠️:安装过程中可能会报错You don't have write permissions for the /usr/bin directory.
,使用如下代码即可
sudo gem install fastlane -n /usr/local/bin
安装完成后可以执行fastlane -v
检查是否安装成功.
fastlane -v
初始化Fastlane
-
cd
到你的项目根目录下fastlane init
注意⚠️:如果你的工程是用cocoapods
的那么可能会提示让你勾选工程的Scheme
;步骤就是打开你的xcode
-->Manage Schemes
-->Scheme
-->后面的多选框中进行勾选,然后可以手动删除 fastlane
文件夹,重新fastlane init
一下。
1. 帮我们自动截取APP中的截图,并添加手机边框(如果需要的话),我们这里不选择这个选项,因为我们的项目已经有图片了,不需要这里截屏。
2. 自动发布beta版本用于TestFlight
3. 自动的App Store发布包。
4. 手动设置。
- 选择3即可,后续会提示你输入开发者账号和密码,输入账号双重认证的验证码。
- 然后输入你的
appName
即可。 - 如果验证码不行,需要短信,则输入
sms
,选择固定手机即可
- 选择允许
fastlane
管理ITC
上的元数据后,会将ITC
上的所有元数据与截图下载到metadata
和screenshots
文件夹。
- 后面会出现卡住
bundle update
步骤中,这是被墙的结果。
- 只需要关闭
终端
,打开项目文件夹
,找到fastlane init
过程中生成的Gemfile
文件,并打开,将https://rubygems.org
,换成https://gems.ruby-china.com
,然后打开终端
,cd
到当前项目,输入bundle update
。
注意⚠️:过程当中会有很多填写密码或者询问(y/n)
。
-
Fastlane
初始化完成后,工程根目录下会生成fastlane
文件夹,结构如下:fastlane ├── Appfile ├── Deliverfile ├── Fastfile ├── metadata │ ├── app_icon.jpg │ ├── copyright.txt │ ├── primary_category.txt │ ├── primary_first_sub_category.txt │ ├── primary_second_sub_category.txt │ ├── review_information │ │ ├── demo_password.txt │ │ ├── demo_user.txt │ │ ├── email_address.txt │ │ ├── first_name.txt │ │ ├── last_name.txt │ │ ├── notes.txt │ │ └── phone_number.txt │ ├── secondary_category.txt │ ├── secondary_first_sub_category.txt │ ├── secondary_second_sub_category.txt │ ├── trade_representative_contact_information │ │ ├── address_line1.txt │ │ ├── address_line2.txt │ │ ├── address_line3.txt │ │ ├── city_name.txt │ │ ├── country.txt │ │ ├── email_address.txt │ │ ├── first_name.txt │ │ ├── is_displayed_on_app_store.txt │ │ ├── last_name.txt │ │ ├── phone_number.txt │ │ ├── postal_code.txt │ │ ├── state.txt │ │ └── trade_name.txt │ └── zh-Hans │ ├── description.txt │ ├── keywords.txt │ ├── marketing_url.txt │ ├── name.txt │ ├── privacy_url.txt │ ├── promotional_text.txt │ ├── release_notes.txt │ ├── subtitle.txt │ └── support_url.txt └── screenshots ├── README.txt └── zh-Hans ├── 1_iphone6Plus_1.Simulator\ Screen\ Shot\ -\ iPhone\ 8\ Plus\ -\ 2018-06-01\ at\ 17.21.05.png ├── 2_iphone6Plus_2.Simulator\ Screen\ Shot\ -\ iPhone\ 8\ Plus\ -\ 2018-06-01\ at\ 17.21.29.png ├── 3_iphone6Plus_3.Simulator\ Screen\ Shot\ -\ iPhone\ 8\ Plus\ -\ 2018-06-01\ at\ 17.29.41.png ├── 4_iphone6Plus_4.Simulator\ Screen\ Shot\ -\ iPhone\ 8\ Plus\ -\ 2018-06-01\ at\ 17.29.46.png └── 5_iphone6Plus_5.Simulator\ Screen\ Shot\ -\ iPhone\ 8\ Plus\ -\ 2018-06-01\ at\ 17.29.51.png 6 directories, 46 files
安装所需插件
Fastlane
的插件是一个或者一组action
的打包,单独发布在fastlane
之外。首次安装插件后会生成一个Pluginfile
文件
#查看所有插件
fastlane search_plugins
# 安装方法
fastlane add_plugin [name]
#常用插件
fastlane add_plugin versioning
fastlane add_plugin firim
fastlane add_plugin fir_cli # 官方版
fastlane add_plugin pgyer
#更新插件
fastlane update_plugins
插件说明
fastlane add_plugin versioning:用来修改 build
版本号和 version
版本号,fastlane
内嵌的actionincrement_build_number
使用的是苹果提供的agvtool
, 在更改Build
的时候会改变所有target
的版本号。如果你在一个工程里有多个target
,每次编译,所有的Build
都要加1。 有了fastlane-plugin-versioning
不仅可以指定target
增加Build
,当然也可以直接设定Version
, 并且可以指定版本号的版本(major/miner/patch)
。
fastlane-plugin-pgyer:上传到蒲公英分发平台。
fastlane-plugin-firim:上传到firim
。
注意⚠️:如果你需要发布到蒲公英或者firim
必须提前安装插件,否则后面会操作失败。
Fastlane的文件配置
注意⚠️: Fastlane
是使用Ruby
写的,配置文件修改可以使用VS Code
,然后下个Ruby
插件这样方便修改。
Gemfile
类似于cocopods
的Podfile
文件。
Metadata
元数据文件夹,截图文件夹,一般fastlane init
的时候会自动下载下来的。
.env
配置环境变量(在fastlane init
进行初始化后并不会自动生成,如果需要可以自己创建)。用于把账号信息、更新描述、工程相关信息、证书文件等单独放在一个配置文件,方便集中更改。
cd
到工程目录fastlane
文件夹下:
touch .env
注意⚠️: .env
是一个隐藏文件,我们可以是command+shift+.
显示隐藏文件。另外.env
可以使用VS Code
编辑,但是需要安装dotEnv
插件。
#APP唯一标识符
APP_IDENTIFIER = ""
#苹果开发者账号
APPLE_ID = ""
#apple developer Team ID
TEAM_ID = ""
#itunes connect Team ID
ITC_TEAM_ID = ""
#工程名称
SCHEME_NAME = ""
#App 元数据及截图存放路径
METADATA_PATH = "./metadata"
SCREENSHOTS_PATH = "./screenshots"
#appicon文件夹路径
APPICON_PATH = './fastlane/metadata/app_icon.jpg'
#ipa输出文件夹路径
OUTPUT_DIRECTORY = "/Users/hfk/Desktop/AlliPA/ipa"
#App 元数据及截图下载时 直接覆盖 不询问
DELIER_FORCE_OVERWRITE = false
#provisioning profile配置(如果有三方登录或者通知必须)
PP_DEVELOPMENT = ""
PP_ADHOC = ""
PP_APPSTORE = ""
#更新描述
UPDATE_DESCRIPTION = "fastlane自动打包上传测试"
# 蒲公英 api_key user_key
PGYER_API_KEY = ''
PGYER_USER_KEY = ''
PGYER_INSTALL_TYPE = '2' #1:公开,2:密码安装,3:邀请安装,4:回答问题安装。默认为1公开
PGYER_INSTALL_PASSWORD = '123456'
# Firm token
Firm_API_Token = ''
使用方法:例如获取工程名称ENV['SCHEME_NAME']
Appfile
存储有关开发者账号相关信息,app_identifier
, apple_id
, team_id
, itc_team_id
等一些fastlane
需要用到的信息。一般在fastlane init的时候会初始化出来,如果没有就需要自己添加。
app_identifier ENV['APP_IDENTIFIER'] # The bundle identifier of your app
apple_id ENV['APPLE_ID'] # Your Apple email address
itc_team_id ENV['ITC_TEAM_ID'] # App Store Connect Team ID
team_id ENV['TEAM_ID'] # Developer Portal Team ID
# For more information about the Appfile, see:
# https://docs.fastlane.tools/advanced/#appfile
Deliverfile
工具的配置文件,metadata
文件夹中的文件比较多且杂,仅一个属性的配置却使用一个单独的.txt
文件来存储,官方文档表示可以使用Deliverfile
文件来配置且优先级高于matadata
中.txt
文件中的配置。
# The Deliverfile allows you to store various App Store Connect metadata
# For more information, check out the docs
# https://docs.fastlane.tools/actions/deliver/
############################# 基本信息 ####################################
# bundle identifier
app_identifier ENV['APP_IDENTIFIER']
# Apple ID用户名
username ENV['APPLE_ID']
# 版权声明
# copyright "2020 xxx Inc"
# 支持语言
supportedLanguages = {
"cmn-Hans" => "zh-Hans"
}
# app 名称
# name({
# 'zh-Hans' => "APPName"
# })
# 副标题
# subtitle(
# 'zh-Hans' => "副标题"
# )
# App价格
#price_tier 0
# 应用程序图标的路径 1024*1024
app_icon './fastlane/metadata/app_icon.jpg'
# 屏幕截图的文件夹的路径
screenshots_path ENV['SCREENSHOTS_PATH']
# 元数据的路径
metadata_path ENV['METADATA_PATH']
################################### 类别配置 ###################################
# 参考网站https://docs.fastlane.tools/actions/upload_to_app_store/#reference
# 设置 App 的类别.这里可以设置一个主要类别,一个次要类别.
# 主要类别
primary_category "MZGenre.Lifestyle"
# 主要类别第一个子类别 无
# primary_first_sub_category
# 主要类别第二个子类别 无
# primary_second_sub_category
# 要设置的次要类别
secondary_category "MZGenre.Utilities"
# 设置的次要第一个子类别 无
# secondary_first_sub_category
# 设置的次要第二个子类别 无
# secondary_second_sub_category
################################## 关键字\描述等信息 ###################################
# 搜索关键字
# keywords(
# "zh-Hans" => "FastLane"
# )
# 技术支持网址
# support_url({
# 'zh-Hans' => "http://www.baidu.com"
# })
# 营销网址
#marketing_url({
# 'zh-Hans' => "http://www.baidu.com"
#})
# 隐私政策网址
#privacy_url({
# 'zh-Hans' => "http://www.baidu.com"
#})
# 本地化宣传文本信息介绍
#promotional_text(
# "zh-Hans" => "本地化宣传文本信息介绍",
#)
# app描述信息
#description({
# 'zh-Hans' => "APP的描述信息,用于APP功能的描述和介绍不能少于10个字符"
#})
# 版本描述
#release_notes({
# 'zh-Hans' => "这是第一个版本哦"
#})
################################## 分级 ########################################
#年龄分级配置
#app_rating_config_path "./fastlane/metadata/itunes_rating_config.json"
################################# 提交审核信息等 #########################################
# 提交审核信息:加密, idfa 等
submission_information({
export_compliance_encryption_updated: false,
export_compliance_uses_encryption: false,
content_rights_contains_third_party_content: false,
add_id_info_uses_idfa: false,
add_id_info_serves_ads: true,
add_id_info_limits_tracking: true
})
# # 应用审核小组的联系信息 app 审核信息
# app_review_information(
# first_name: "name",
# last_name: "name",
# phone_number: "手机号",
# email_address: "email",
# demo_user: "测试账号用户名",
# demo_password: "测试账号密码",
# notes: "noting"
# )
# run_precheck_before_submit true
# 在上传新截图之前,先清除所有之前上传的截图
overwrite_screenshots true
# 下载 metadata 及 screenshots 时直接覆盖,不询问
force true
# 不上传截图
skip_screenshots true
# 是否自动提交审核,true表示立马提交审核
submit_for_review false
# 审核通过后是否立刻发布,false表示需要手动发布
automatic_release true
Fastfile
核心文件,主要用于命令行调用和处理具体的流程,lane
相对于一个方法或者函数。
# 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)
# 定义全局参数 大写开头为常数,小写、_开头为变量,$开头为全局变量(直接用#访问)
# 超时时间
ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "180"
# 尝试次数
ENV["FASTLANE_XCODEBUILD_SETTINGS_RETRIES"] = "10"
# scheme名
SCHEME_NAME = ENV['SCHEME_NAME']
# workspace名
WORKSPACE_NAME = "#{SCHEME_NAME}.xcworkspace"
# plist文件路径
INFO_PLIST_PATH = "#{SCHEME_NAME}/Info.plist"
# 版本号
$VERSION_NUMBER = ""
# 构建版本号
$BUILD_NUMBER = ""
# ipa导出路径
$OUTPUT_DIRECTORY = ENV['OUTPUT_DIRECTORY']
# ipa安装包路径
$IPA_PATH = ""
# 是否准备完成
$PREPARE_COMPLETED = false
platform :ios do
before_all do
# 所有lane执行之前
# 使用环境变量提供这个密码给fastlane,解决双重认证生成的特殊密码
ENV["FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"] = "xxx"
# ENV["SLACK_URL"] = "https://hooks.slack.com/services/..."
# 拉取远程最新代码
# git_pull
# 执行 pod instasll
# cocoapods
# carthage
end
desc '打包前的准备工作'
lane :prepare do |options|
if !$PREPARE_COMPLETED
puts "\033[32m====================即将开始打包====================\033[0m\n"
puts '您好,即将为你自动打包'
end
export_method = options[:export_method]
select_method = '-1'
# => 如果没有选择打包方式,提示选择打包方式
if export_method.nil? || export_method.empty?
puts "请选择打包方式 \033[33m1:打包上传AppStore(默认) 2:打包Development上传蒲公英Firm 3:打包AdHoc上传蒲公英Firm 4:打包Inhouse上传蒲公英Firm 0:结束打包 回车表示使用默认打包\033[0m"
select_method = STDIN.gets.chomp
if select_method!='1' && select_method!='2' && select_method!='3' && select_method!='4' && !select_method.empty?
# supported [:select, :message, :verbose, :error, :password, :input, :success, :important, :command, :user_error!, :command_output, :deprecated, :header, :content_error, :interactive?, :confirm, :crash!, :not_implemented, :shell_error!, :build_failure!, :test_failure!, :abort_with_message!]
UI.user_error!("您已取消打包 ")
end
end
if !$PREPARE_COMPLETED
currentVersion = get_info_plist_value(path: "#{INFO_PLIST_PATH}", key: "CFBundleShortVersionString")
currentBuild = get_info_plist_value(path: "#{INFO_PLIST_PATH}", key: "CFBundleVersion")
puts "当前工程的版本号是:\033[33m#{currentVersion}\033[0m 构建版本号是:\033[33m#{currentBuild}\033[0m"
version = options[:version]
build = options[:build]
output_directory = options[:output_directory]
# => 如果没有选择版本号,提示是否需要输入版本号
if version.nil? || version.empty?
puts "请输入版本号,回车表示使用当前版本号\033[33m#{currentVersion}\033[0m"
version = STDIN.gets.chomp
if version == '' # 回车
$VERSION_NUMBER = currentVersion
else
$VERSION_NUMBER = version
end
else
$VERSION_NUMBER = version
end
# => 如果没有选择构建版本号,提示是否需要输入构建版本号
if build.nil? || build.empty?
puts "请输入构建版本号,回车表示使用默认自动生成构建版本号"
build = STDIN.gets.chomp
if build == '' # 回车
# $BUILD_NUMBER = AUTO_BUILD_NUMBER
else
$BUILD_NUMBER = build
end
else
$BUILD_NUMBER = build
end
# => 如果没有选择ipa输出目录,提示是否需要输入打包路径
if output_directory.nil? || output_directory.empty?
puts "请指定ipa包输出路径,回车表示使用默认输出路径:\033[33m#$OUTPUT_DIRECTORY\033[0m"
output_directory = STDIN.gets.chomp
if output_directory == '' # 回车
else
$OUTPUT_DIRECTORY = output_directory
end
else
$OUTPUT_DIRECTORY = output_directory
end
end
$PREPARE_COMPLETED = true
if select_method != '-1' # 已选择
case select_method
when '1'
# 发布到appstore
release_appstore(options)
when '2'
# 打包Development或Adhoc发布到蒲公英Firm
release_development(options)
when '3'
# 打包Adhoc发布到蒲公英Firm
release_adhoc(options)
when '4'
# 打包inhouse发布到蒲公英
release_enterprise(options)
end
next
end
# => 详细信息
summary(options)
end
desc "信息确认"
lane :summary do |options|
puts "\033[32m====================信息确认====================\033[0m\n"
puts "您设置的包输出路径为:"
# supported [:select, :message, :verbose, :error, :password, :input, :success, :important, :command, :user_error!, :command_output, :deprecated, :header, :content_error, :interactive?, :confirm, :crash!, :not_implemented, :shell_error!, :build_failure!, :test_failure!, :abort_with_message!]
UI.important "#$OUTPUT_DIRECTORY"
puts "您选择的打包方式为:"
UI.important "#{options[:export_method]}"
puts "指定的发布版本号为:"
UI.important "#$VERSION_NUMBER"
confirm = UI.confirm "确认信息是否正确,输入y继续打包"
if !confirm
UI.user_error!("您已取消打包 ")
end
puts "\033[32m====================信息确认====================\033[0m\n"
puts "3s后开始自动打包..."
sleep(3)
end
desc "更新版本号"
lane :update_version do
puts("*************| 更新version #$VERSION_NUMBER |*************")
increment_version_number_in_plist(
target: SCHEME_NAME,
version_number: $VERSION_NUMBER
)
puts("*************| 更新build #$BUILD_NUMBER |*************")
increment_build_number_in_plist(
target: SCHEME_NAME,
build_number: $BUILD_NUMBER
)
end
desc "打包发布"
lane :release do |options|
prepare(options)
end
desc "发布到appstore"
lane :release_appstore do |options|
options[:export_method] = "app-store"
prepare(options)
build(options)
deliver_appstore
end
desc "发布development"
lane :release_development do |options|
options[:export_method] = "development"
prepare(options)
build(options)
deliver_pgyer
deliver_firm
end
desc "发布ad-hoc"
lane :release_adhoc do |options|
options[:export_method] = "ad-hoc"
prepare(options)
build(options)
deliver_pgyer
deliver_firm
end
desc "发布企业Inhouse"
lane :release_enterprise do |options|
options[:export_method] = "enterprise"
prepare(options)
build(options)
deliver_pgyer
deliver_firm
end
desc "上传到蒲公英"
lane :deliver_pgyer do |options|
pgyer(
api_key: ENV['PGYER_API_KEY'], # 从蒲公英项目详情中获取的apikey
user_key: ENV['PGYER_USER_KEY'], # 从蒲公英项目详情中获取的 userkey
#apk: $APK_PATH, #apk包路径
ipa: $IPA_PATH, #ipa包路径
install_type: ENV['PGYER_INSTALL_TYPE'], #1:公开,2:密码安装,3:邀请安装,4:回答问题安装。默认为1公开
password: ENV['PGYER_INSTALL_PASSWORD'], #设置安装密码
update_description: ENV['UPDATE_DESCRIPTION'] #更新描述
)
end
desc "上传到Firm"
lane :deliver_firm do |options|
firim(
firim_api_token: ENV['Firm_API_Token'], # 从Firm项目详情中获取的apitoken
ipa: $IPA_PATH, #ipa包路径
icon: ENV['APPICON_PATH'] #icon
)
# fir_cli( #官方版
# api_token: ENV['Firm_API_Token'], # 从Firm项目详情中获取的apitoken
# specify_file_path: $IPA_PATH, #ipa包路径
# specify_icon_file: ENV['APPICON_PATH'] #icon
# changelog: ENV['UPDATE_DESCRIPTION'] #更新描述
# )
end
desc "上传到appstore"
lane :deliver_appstore do |options|
deliver(
username: ENV['APPLE_ID'], # 开发者账号
team_id: ENV['ITC_TEAM_ID'], # ITC Team ID
dev_portal_team_id: ENV['TEAM_ID'], # ADC Team ID
app_identifier: ENV['APP_IDENTIFIER'], # bundle ID
ipa: $IPA_PATH, # ipa包路径
app_version: $VERSION_NUMBER, # 更新版本号
release_notes: {
'zh-Hans' => "这是第一个版本哦"
},
submit_for_review: false,# 是否自动提交审核,true表示立马提交审核
automatic_release: true,# 审核通过后是否立刻发布,false表示需要手动发布
force: true, # 设置true,会跳过预览页面,直接上架
skip_screenshots: true, # 不上传截图
skip_metadata: true, # 不上传元数据
)
end
desc "打包"
lane :build do |options|
# gym用来编译ipa
# 编译时间
build_time = Time.now.strftime("%Y-%m-%d %H-%M-%S")
# 自动生成的build版本号
auto_build_number = Time.now.strftime("%Y%m%d%H%M%S")
if $BUILD_NUMBER.empty?
$BUILD_NUMBER = auto_build_number
end
# 更新版本号
update_version
# 获取打包方式
export_method = options[:export_method]
# 配置项
configuration = 'Release'
# pp文件
provisioningProfiles = ENV['PP_APPSTORE']
# 输出目录
outputDir = ''
# 输出文件名
outputName = "#{SCHEME_NAME}_#$VERSION_NUMBER_#$BUILD_NUMBER_#{export_method}.ipa"
case export_method
when 'development'
configuration = 'Debug'
outputDir = "#$OUTPUT_DIRECTORY/Development/#{SCHEME_NAME}-#{build_time}"
when 'app-store'
outputDir = "#$OUTPUT_DIRECTORY/Appstore/#{SCHEME_NAME}-#{build_time}"
when 'ad-hoc'
provisioningProfiles = ENV['PP_ADHOC']
outputDir = "#$OUTPUT_DIRECTORY/ADHOC/#{SCHEME_NAME}-#{build_time}"
when 'enterprise'
provisioningProfiles = ENV['PP_ENTERPRISE']
outputDir = "#$OUTPUT_DIRECTORY/ADHOC/#{SCHEME_NAME}-#{build_time}"
end
$IPA_PATH = gym(
clean: 'true', # 在打包前是否先执行clean。
scheme: "#{SCHEME_NAME}", # 指定项目的scheme名称
workspace: "#{WORKSPACE_NAME}", # 指定.xcworkspace文件的路径。
configuration: "#{configuration}", # 指定打包时的配置项,默认为Release
output_name: "#{outputName}", # 指定生成的.ipa文件的名称,应包含文件扩展名。
output_directory: "#{outputDir}", # 指定.ipa文件的输出目录
include_symbols: 'true', # 是否导出符号表
# include_bitcode: 'false', # 是否使用bitcode打包
export_xcargs: "-allowProvisioningUpdates", #访问钥匙串
silent: true, # 是否隐藏打包时不需要的信息。
buildlog_path: "#{outputDir}", # 指定编译日志文件的输出目录
export_options: {
method: "#{export_method}", # 指定导出.ipa时使用的方法,可用选项:app-store,ad-hoc,enterprise,development
thinning: "", # 是否瘦身
#provisioningProfiles: { //如果有三方登录或者通知必须放开
# "#{ENV['APP_IDENTIFIER']}" => "#{provisioningProfiles}"
# }
}
)
end
after_all do |lane|
# 在macOS 通知栏发送通知
notification(subtitle: "Successfully", message: "Successfully deployed new App Update")
#slack(
# message: "Successfully deployed new App Update."
#)
end
error do |lane, exception|
#slack(
# message: exception.message,
# success: false
#)
end
# 钉钉机器人
desc "dingdingTalk"
lane :dingdingTalk do |options|
app_url = "https://www.pgyer.com/xxxx"
app_qrurl = "https://www.pgyer.com/app/qrcode/xxxx"
if $UPDATE_DESCRIPTION == "测试版"
app_url = "https://www.pgyer.com/xxxx"
app_qrurl = "https://www.pgyer.com/app/qrcode/xxxx"
end
curl = %Q{
curl 'Webhook' \
-H 'Content-Type:application/json' \
-d '{
"msgtype":"markdown",
"markdown":{
"title":"#{$UPDATE_DESCRIPTION}更新成功!",
"text":"### #{$UPDATE_DESCRIPTION}更新成功! \n [@xxxx](#{app_url}) \n\n [#{app_url}](#{app_url})\n\n **扫码安装↓↓↓**\n\n [图片上传失败...(image-471666-1662695255072)]\n"
},
"at": {
"atMobiles": [
"xxxx",
],
"isAtAll": false
},
}'
}
system curl
end
end
注意⚠️: 在text
里添加@人的手机号
,且只有在群内的成员才可被@
,非群内成员手机号会被脱敏。
Fastlane发布
cd
到工程目录下,调用fastlane release
,之后根据提示选择需要的过程即可完成发布。
注意⚠️: 如果出现如下错误,就去工程中Edit Scheme --> 勾中Shared
Couldn't find specified scheme 'YourScheme'. Please make sure that the scheme is shared, see ...
两步验证问题
现在大部分Apple Id
都开启了两步验证,当我们提交TestFilght
或者AppStore
的时候会提示我们输入6位code
,这样就违背全自动发布的初衷了。
Two Factor Authentication for account '[email protected]' is enabled
If you're running this in a non-interactive session (e.g. server or CI)
check out https://github.com/fastlane/fastlane/tree/master/spaceship#2-step-verification
Please enter the 6 digit code:
解决办法:
步骤:
1.访问 https://appleid.apple.com/account/manage
2.生成一个 APP-SPECIFIC PASSWORDS,保留生成的特殊密码,记作 A
3.使用环境变量提供这个密码给fastlane: FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD
4.执行 fastlane spaceauth -u [email protected],生成 session cookie,记作 B。
5.通过环境变量 FASTLANE_SESSION 提供 session cookies。
配置:
方式1:
前往文件 ~/.bash_profile 中,新增并配置配置 FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD 和 FASTLANE_SESSION
例:(A和B就是上面生成的)
export FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD = A
export FASTLANE_SESSION = B
/*----------------------------------------------------*/
方式2:
ENV["FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"] = "xxxxxx"
这个和环境变量配置是一个意思,但是涉及密码写在配置文件里面
注意⚠️: .bash_profile
文件是隐藏文件,可以使用command+shift+.
控制显示
生命周期
执行顺序 | 方法名 | 说明 |
---|---|---|
1 | before_all | 在执行lane 之前操作,只执行一次,比如使用cocopods 更新 pod 库 |
2 | before_each | 每次执行lane 之前都会执行一次 |
3 | lane | 用户自定义的任务流程,例如打包,上传等 |
4 | after_each | 每次执行lane 之后都会执行一次 |
5 | after_all | 在执行所有lane 成功结束之后执行一次,例发送邮件,通知 |
6 | error | 在执行上述情况任意环境报错都会中止并执行一次 |
fastlane常用工具
工具 | 描述 |
---|---|
scan | 自动运行测试工具,并且可以生成漂亮的HTML 报告 |
cert | 自动创建和管理iOS 签名证书Certificates |
sign | 创建、更新、下载、修复Provisioning Profiles 的工具 |
pem | 自动生成、更新推送配置文件 |
match | 一个新的自动创建和管理iOS 签名证书和Provisioning Profiles 工具(一般团队使用) |
snapshot | 用UI test 功能实现自动化截图 |
frameit | 给截屏套上一层外边框 |
gym | 编译打包生成ipa 文件,又名build_ios_app 或build_app |
produce | 如果你的产品还没在iTC 或者ADC 建立,produce 可以自动帮你完成这些工作 |
deliver | 自动上传截图,APP 的元数据,二进制ipa 文件到iTC |
notification | 在Mac 上面显示通知 |
多Target
或者多个Scheme
的可以参照一下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)
# 定义全局参数 大写开头为常数,小写、_开头为变量,$开头为全局变量(直接用#访问)
# 超时时间
ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "180"
# 尝试次数
ENV["FASTLANE_XCODEBUILD_SETTINGS_RETRIES"] = "10"
# target名
TARGET_NAME = ENV['TARGET_NAME']
# workspace名
WORKSPACE_NAME = "#{ENV['WORKSPACE_NAME']}.xcworkspace"
# plist文件路径
INFO_PLIST_PATH = "#{TARGET_NAME}/Info.plist"
# bundleid
$APP_BUNDLEID = ENV["APP_BUNDLEID"]
# provisioningProfiles
$PROVISIONINGPROFILE = ""
# scheme名
$SCHEME_NAME = ""
# configuration
$CONFIGURATION = 'Release'
# APPICON_PATH
$APPICON_PATH = ENV["APPICON_PATH"]
# update description
$UPDATE_DESCRIPTION = "正式"
# 版本号
$VERSION_NUMBER = ""
# 构建版本号
$BUILD_NUMBER = ""
# ipa导出路径
$OUTPUT_DIRECTORY = ENV['OUTPUT_DIRECTORY']
# ipa安装包路径
$IPA_PATH = ""
# 是否准备完成
$PREPARE_COMPLETED = false
platform :ios do
before_all do
# 所有lane执行之前
# 使用环境变量提供这个密码给fastlane,解决双重认证生成的特殊密码
ENV["FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"] = "xxx"
# ENV["SLACK_URL"] = "https://hooks.slack.com/services/..."
# 拉取远程最新代码
# git_pull
# 执行 pod instasll
# cocoapods
# carthage
end
desc '打包前的准备工作'
lane :prepare do |options|
if !$PREPARE_COMPLETED
puts "\033[32m====================即将开始打包====================\033[0m\n"
puts '您好,即将为你自动打包'
end
if $SCHEME_NAME == ""
puts "请选择打包SCHEME \033[33m1:打包test 2:打包preOnline 3:online 0:结束打包 \033[0m"
select_method = STDIN.gets.chomp
if select_method == '1'
$APP_BUNDLEID = ENV["APP_BUNDLEIDTEST"]
$SCHEME_NAME = ENV['SCHEME_NAME_TEST']
$APPICON_PATH = ENV["APPICON_PATH_TEST"]
$CONFIGURATION = 'Test_Debug'
$UPDATE_DESCRIPTION = "测试"
elsif select_method == '2'
$APP_BUNDLEID = ENV["APP_BUNDLEID"]
$SCHEME_NAME = ENV['SCHEME_NAME_PRE']
$APPICON_PATH = ENV["APPICON_PATH_PRE"]
$CONFIGURATION = 'PreOnline_Release'
$UPDATE_DESCRIPTION = "预发"
elsif select_method == '3'
$APP_BUNDLEID = ENV["APP_BUNDLEID"]
$SCHEME_NAME = ENV['SCHEME_NAME']
$APPICON_PATH = ENV["APPICON_PATH"]
$CONFIGURATION = 'Release'
$UPDATE_DESCRIPTION = "正式"
else
UI.user_error!("您已取消打包 ")
end
end
export_method = options[:export_method]
select_method = '-1'
# => 如果没有选择打包方式,提示选择打包方式
if export_method.nil? || export_method.empty?
if $SCHEME_NAME == ENV['SCHEME_NAME']
puts "请选择打包方式 \033[33m1:打包上传AppStore(默认) 2:打包AdHoc上传蒲公英Firm 0:结束打包 \033[0m"
select_method = STDIN.gets.chomp
if select_method!='1' && select_method!='2'
# supported [:select, :message, :verbose, :error, :password, :input, :success, :important, :command, :user_error!, :command_output, :deprecated, :header, :content_error, :interactive?, :confirm, :crash!, :not_implemented, :shell_error!, :build_failure!, :test_failure!, :abort_with_message!]
UI.user_error!("您已取消打包 ")
end
else
puts "请选择打包方式 \033[33m1:打包AdHoc上传蒲公英Firm 0:结束打包 \033[0m"
select_method = STDIN.gets.chomp
if select_method == '1'
select_method = '2'
else
UI.user_error!("您已取消打包 ")
end
end
end
if !$PREPARE_COMPLETED
currentVersion = get_info_plist_value(path: "#{INFO_PLIST_PATH}", key: "CFBundleShortVersionString")
currentBuild = get_info_plist_value(path: "#{INFO_PLIST_PATH}", key: "CFBundleVersion")
puts "当前工程的版本号是:\033[33m#{currentVersion}\033[0m 构建版本号是:\033[33m#{currentBuild}\033[0m"
version = options[:version]
build = options[:build]
output_directory = options[:output_directory]
# => 如果没有选择版本号,提示是否需要输入版本号
if version.nil? || version.empty?
puts "请输入版本号,回车表示使用当前版本号\033[33m#{currentVersion}\033[0m"
version = STDIN.gets.chomp
if version == '' # 回车
$VERSION_NUMBER = currentVersion
else
$VERSION_NUMBER = version
end
else
$VERSION_NUMBER = version
end
# => 如果没有选择构建版本号,提示是否需要输入构建版本号
if build.nil? || build.empty?
puts "请输入构建版本号,回车表示使用默认自动生成构建版本号"
build = STDIN.gets.chomp
if build == '' # 回车
# $BUILD_NUMBER = AUTO_BUILD_NUMBER
else
$BUILD_NUMBER = build
end
else
$BUILD_NUMBER = build
end
# => 如果没有选择ipa输出目录,提示是否需要输入打包路径
if output_directory.nil? || output_directory.empty?
puts "请指定ipa包输出路径,回车表示使用默认输出路径:\033[33m#$OUTPUT_DIRECTORY\033[0m"
output_directory = STDIN.gets.chomp
if output_directory == '' # 回车
else
$OUTPUT_DIRECTORY = output_directory
end
else
$OUTPUT_DIRECTORY = output_directory
end
end
$PREPARE_COMPLETED = true
if select_method != '-1' # 已选择
case select_method
when '1'
# 发布到appstore
release_appstore(options)
when '2'
# 打包Adhoc发布到蒲公英Firm
release_adhoc(options)
end
next
end
# => 详细信息
summary(options)
end
desc "信息确认"
lane :summary do |options|
puts "\033[32m====================信息确认====================\033[0m\n"
puts "您设置的包输出路径为:"
# supported [:select, :message, :verbose, :error, :password, :input, :success, :important, :command, :user_error!, :command_output, :deprecated, :header, :content_error, :interactive?, :confirm, :crash!, :not_implemented, :shell_error!, :build_failure!, :test_failure!, :abort_with_message!]
UI.important "#$OUTPUT_DIRECTORY"
puts "您选择的打包SCHEME为:"
UI.important "#$SCHEME_NAME"
puts "您选择的打包方式为:"
UI.important "#{options[:export_method]}"
puts "指定的发布版本号为:"
UI.important "#$VERSION_NUMBER"
confirm = UI.confirm "确认信息是否正确,输入y继续打包"
if !confirm
UI.user_error!("您已取消打包 ")
end
puts "\033[32m====================信息确认====================\033[0m\n"
puts "3s后开始自动打包..."
sleep(3)
end
desc "更新版本号"
lane :update_version do
puts("*************| 更新version #$VERSION_NUMBER |*************")
increment_version_number_in_plist(
target: TARGET_NAME,
version_number: $VERSION_NUMBER
)
puts("*************| 更新build #$BUILD_NUMBER |*************")
increment_build_number_in_plist(
target: TARGET_NAME,
build_number: $BUILD_NUMBER
)
end
desc "打包发布"
lane :release do |options|
prepare(options)
end
desc "发布到appstore"
lane :release_appstore do |options|
options[:export_method] = "app-store"
prepare(options)
build(options)
deliver_appstore
end
desc "发布ad-hoc"
lane :release_adhoc do |options|
options[:export_method] = "ad-hoc"
prepare(options)
build(options)
deliver_pgyer
deliver_firm
end
desc "上传到蒲公英"
lane :deliver_pgyer do |options|
pgyer(
api_key: ENV['PGYER_API_KEY'], # 从蒲公英项目详情中获取的apikey
#apk: $APK_PATH, #apk包路径
ipa: $IPA_PATH, #ipa包路径
install_type: ENV['PGYER_INSTALL_TYPE'], #1:公开,2:密码安装,3:邀请安装,4:回答问题安装。默认为1公开
password: ENV['PGYER_INSTALL_PASSWORD'], #设置安装密码
update_description: $UPDATE_DESCRIPTION #更新描述
)
end
desc "上传到Firm"
lane :deliver_firm do |options|
# firim(
# firim_api_token: ENV['Firm_API_Token'], # 从Firm项目详情中获取的apitoken
# ipa: $IPA_PATH, #ipa包路径
# #icon: ENV['APPICON_PATH'], #icon
# app_desc: $UPDATE_DESCRIPTION #更新描述
# )
fir_cli(
api_token: ENV['Firm_API_Token'], # 从Firm项目详情中获取的apitoken
specify_file_path: $IPA_PATH, #ipa包路径
specify_icon_file: $APPICON_PATH, #icon
changelog: $UPDATE_DESCRIPTION #更新描述
)
end
desc "上传到appstore"
lane :deliver_appstore do |options|
deliver(
username: ENV['APPLE_ID'], # 开发者账号
team_id: ENV['ITC_TEAM_ID'], # ITC Team ID
dev_portal_team_id: ENV['TEAM_ID'], # ADC Team ID
app_identifier: $APP_BUNDLEID, # bundle ID
ipa: $IPA_PATH, # ipa包路径
app_version: $VERSION_NUMBER, # 更新版本号
release_notes: {
'zh-Hans' => "这是第一个版本哦"
},
submit_for_review: false,# 是否自动提交审核,true表示立马提交审核
automatic_release: true,# 审核通过后是否立刻发布,false表示需要手动发布
force: true, # 设置true,会跳过预览页面,直接上架
skip_screenshots: true, # 不上传截图
skip_metadata: true, # 不上传元数据
)
end
desc "打包"
lane :build do |options|
# gym用来编译ipa
# 编译时间
build_time = Time.now.strftime("%Y-%m-%d %H-%M-%S")
# 自动生成的build版本号
auto_build_number = Time.now.strftime("%Y%m%d%H%M%S")
if $BUILD_NUMBER.empty?
$BUILD_NUMBER = auto_build_number
end
# 更新版本号
update_version
# 获取打包方式
export_method = options[:export_method]
# pp文件
provisioningProfiles = ENV['PP_APPSTORE']
# 输出目录
outputDir = ''
# 输出文件名
outputName = "#{$SCHEME_NAME}_#$VERSION_NUMBER_#$BUILD_NUMBER_#{export_method}.ipa"
case export_method
when 'app-store'
provisioningProfiles = ENV['PP_APPSTORE']
outputDir = "#$OUTPUT_DIRECTORY/Appstore/#{$SCHEME_NAME}-#{build_time}"
when 'ad-hoc'
provisioningProfiles = ENV['PP_ADHOC']
outputDir = "#$OUTPUT_DIRECTORY/ADHOC/#{$SCHEME_NAME}-#{build_time}"
end
$IPA_PATH = gym(
clean: 'true', # 在打包前是否先执行clean。
scheme: "#{$SCHEME_NAME}", # 指定项目的scheme名称
workspace: "#{WORKSPACE_NAME}", # 指定.xcworkspace文件的路径。
configuration: "#{$CONFIGURATION}", # 指定打包时的配置项,默认为Release
output_name: "#{outputName}", # 指定生成的.ipa文件的名称,应包含文件扩展名。
output_directory: "#{outputDir}", # 指定.ipa文件的输出目录
include_symbols: 'true', # 是否导出符号表
# include_bitcode: 'false', # 是否使用bitcode打包
export_xcargs: "-allowProvisioningUpdates", #访问钥匙串
silent: true, # 是否隐藏打包时不需要的信息。
buildlog_path: "#{outputDir}", # 指定编译日志文件的输出目录
export_options: {
method: "#{export_method}", # 指定导出.ipa时使用的方法,可用选项:app-store,ad-hoc
thinning: "", # 是否瘦身
# provisioningProfiles: {
# "#{$APP_BUNDLEID}" => "#{"yxt_adhoc_test"}"
# }
}
)
end
after_all do |lane|
# 在macOS 通知栏发送通知
notification(subtitle: "Successfully", message: "Successfully deployed new App Update")
dingdingTalk
#slack(
# message: "Successfully deployed new App Update."
#)
end
error do |lane, exception|
#slack(
# message: exception.message,
# success: false
#)
end
# 钉钉机器人
desc "dingdingTalk"
lane :dingdingTalk do |options|
app_url = "https://www.pgyer.com/xxxx"
app_qrurl = "https://www.pgyer.com/app/qrcode/xxxx"
if $UPDATE_DESCRIPTION == "测试版"
app_url = "https://www.pgyer.com/xxxx"
app_qrurl = "https://www.pgyer.com/app/qrcode/xxxx"
end
curl = %Q{
curl 'Webhook' \
-H 'Content-Type:application/json' \
-d '{
"msgtype":"markdown",
"markdown":{
"title":"#{$UPDATE_DESCRIPTION}更新成功!",
"text":"### #{$UPDATE_DESCRIPTION}更新成功! \n [@xxxx](#{app_url}) \n\n [#{app_url}](#{app_url})\n\n **扫码安装↓↓↓**\n\n [图片上传失败...(image-84a177-1662695255072)]\n"
},
"at": {
"atMobiles": [
"xxxx",
],
"isAtAll": false
},
}'
}
system curl
end
end
问题:xcodebuild
超时导致gym
报错修复
错误信息
[17:49:08]: xcodebuild -showBuildSettings timed out after 4 retries with a base timeout of 3. You can override the base timeout value with the environment variable FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT, and the number of retries with the environment variable FASTLANE_XCODEBUILD_SETTINGS_RETRIES
解决办法
通过日志描述,知道是超时了,这时候我们只需要在fastfile
文件顶部加上设置超时时长和超时尝试次数的环境变量即可;
ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "180"
ENV["FASTLANE_XCODEBUILD_SETTINGS_RETRIES"] = "10"