本篇主要介绍 ios 如何通过 fastlane 打包发布到所需平台 Android 打包将会在第二篇 Jenkins 集成将会在第三篇
前言
在之前开发 APP 的时候,公司的技术总监一直让我研究 app 自动化打包持续集成部署这种工程化的任务,对于这种技术我肯定是非常感兴趣去折腾的,鉴于当时 app 业务开发任务繁重,和优先级相对较低的原因,等到不怎么忙了才有时间来研究,最近看了一些相关文档,总结如下:
-
持续集成:
大多是采用 jenkins,我们后端的服务也是集成了 jenkins,所以 CI 平台无需考虑;
-
ios 打包:
大多数文章是基于在 jenkins安装 xcode 相关插件,以构建出 xcode 打包环境,以及配置证书、认证文件、钥匙串的相关插件,整体流程比较繁琐,稍有流程顺序和配置出错都会导致打包失败,而且听说 jenkins 的 xcode 插件更新频率极慢,可能不能满足最新版 xcode 的使用,我在参考一些文章的配置之后,打包出了测试版本的 ipa,并且推送到蒲公英,但是发布到 appstore 的构建一直未果;
折腾了两到三天,有点心累了,直到我发现了 fastlane!
正文
关于 fastlane 的介绍我这里就不罗列了,网上介绍它的文章太多了,可以参考中文文档快速了解。
看了文档之后,感觉简直打开了新世界的大门,随便几个功能都足够吸引力。
比如:
- 自动打包测试版的 ipa 发布到 Testflight;
- 自动在 ITC(appstoreconnect.apple.com/)创建应用,以及各种元…
- 自动发布到 appstore;
- 自动生成商店的应用屏幕截图;
- 自动创建证书和认证文件;
就这几个功能就已经省去相当多的时间了,身为一个 app 开发者,相信每个人都有过跟 xcode 的证书、认证文件各种折腾的爱恨情仇!
借用一张图总结 fastlane 的流程和功能:
fastlane 管理许多工具集合,这些工具也都是通过 ruby 开发,每个工具只负责一项任务,可以通过 fastlane 的配置文件灵活的构建这些工具的使用和流程,从而实现
打包 => 自动化测试 => 发布测试包 => 发布正式包
一条龙服务!
就问你妙不妙?
开始折腾.....
经过两天的折腾,顺利完成了
- ipa 文件推送到 TestFlight、蒲公英、firm;
- 自动上传元数据到 ITC;
- 自动上传 ipa 到 ITC 并提交appstore审核;
所以,如果看文章的你想要实现以上需求,可以参考以下我分享的内容。
fastlane安装
1.安装 xcode 命令行工具
xcode-select --install
若提示如下,说明已经安装了Xcode命令行工具;否则会弹出对话框,选择安装即可。
$ xcode-select --install
xcode-select: error: command line tools are already installed, use "Software Update" to install updates
安装界面:
2.通过 RubyGems 安装 fastlane
推荐使用RubyGems
来安装,fastlane就是用 ruby 写的一个工具,所以这种安装方式比较友好,后续不易出错。
如果你是 Mac 用户,其实 Mac 自带 ruby 环境,但是仅仅这样不够,需要2.0及以上版本,检查你的版本:
ruby -v
目前我的版本:
$ ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin19]
ruby 管理推荐使用 rvm,类似 node 的 nvm 的工具,可以提供一个便捷的多版本 Ruby 环境的管理和切换。
检查你当前正在使用系统Ruby
which ruby
如果结果是:
/usr/bin/ruby
说明目前是 Mac 系统自带的 ruby 环境,所以首先安装 rvm,而安装 rvm 又需要 gpg
,还要安装mpapis
公钥
(觉得麻烦不?)
没事,坑都趟完了,跟着走就对了....
3. ruby 环境搭建
- 通过 Homebrew 安装 gpg
brew install gnupg
- 安装完gpg之后,安装mpapis公钥
查看最新公钥: rvm.io/rvm/install
拿过来安装
gpg --keyserver hkp://pgp.mit.edu --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
- 装最新版本的Ruby的RVM
\curl -sSL https://get.rvm.io | bash -s stable --ruby
- 列出可供RVM使用的Ruby版本rvm list
$ rvm list
=* ruby-2.6.3 [ x86_64 ]
# => - current
# =* - current && default
# * - default
- 再次使用
which ruby
查看当前Ruby版本
返回 /Users/xxx/.rvm/rubies/ruby-2.6.3/bin/ruby
则说明当前使用了 rvm 安装的 ruby,ruby 环境至此告一段落!
通过RubyGems
安装:
sudo gem install fastlane -NV
当然你也可以通过homebrew
安装(费了这么大劲你肯定不会选这个):
brew cask install fastlane
4.初始化
在项目目录下执行fastlane init
安装的时候出现了下面的选项
1. 📸 Automate screenshots (自动截屏功能配置)
2. 👩✈️ Automate beta distribution to TestFlight (发布到Testfilght配置)
3. 🚀 Automate App Store distribution (自动发布到 appstore配置)
4. 🛠 Manual setup - manually setup your project to automate your (选择手动配置)
新手在这里肯定会纠结,根据情况,自动截屏功能肯定不是首选的刚需,发布到 appstore 也不是一上来就要搞定的,手动配置刚上来你会配置吗?
所以这里选择 2 ,然后一路回车,会让你输入apple 开发者ID,就是你的邮箱,以及密码,通过命令行登录苹果开发者来下载你的证书等配置,登录 ITC 和从 ITC 拉取项目已存在的信息,如果开启了苹果的双重认证,会提示输入验证码登录
Two-factor Authentication (6 digits code) is enabled for account 'xxx@xxx.com'
为避免每次登录输验证码,官方给出解决方案。
命令中也有相关 issue 以供查阅解决方案,界面和提示非常入门化和友好,这真是我用过最贴心的的命令行工具了!
执行完毕后,会生成fastlane
文件夹、Gemfile
文件,Appfile
、Fastfile
文件。
Appfile: 存储有关开发者账号相关信息
Fastfile: 核心文件,用于命令行调用和处理具体的流程,lane相对于一个action方法或函数
Gemfile 类似于cocopods 的Podfile文件
.env 配置环境变量(在fastlane init进行初始化后并不会自动生成,如果需要可以自己创建
打开项目,看看刚生成的文件,你可能发现都没有代码高亮.... 因为 fastlane 使用 ruby 写的,所以要支持 ruby 啊 我这里用的是 VS Code,搜索 ruby 插件安装。
(webstorm 没有发现 ruby 插件)
5.安装所需插件
Fastlane的插件是一个或者一组action的打包,单独发布在fastlane之外。
你可以通过命令行搜索插件
#查看所有插件
fastlane search_plugins
# 安装方法
fastlane add_plugin [name]
#常用插件
fastlane add_plugin versioning
fastlane add_plugin firim
fastlane add_plugin pgyer
首次安装插件会生成Pluginfile
文件
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!
gem 'fastlane-plugin-firim'
gem 'fastlane-plugin-pgyer'
gem 'fastlane-plugin-versioning'
Gemfile
文件会自动添加一下内容
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
插件说明:
-
fastlane-plugin-versioning
用来修改 build 版本号和 version 版本号,fastlane 内嵌的
actionincrement_build_number
使用的是苹果提供的agvtool
, 在更改Build的时候会改变所有target的版本号。如果你在一个工程里有多个target,每次编译,所有的Build都要加1。 有了fastlane-plugin-versioning
不仅可以指定target增加Build,当然也可以直接设定Version, 并且可以指定版本号的版本(major/miner/patch),这一点非常重要,而且这个插件也可以非常方便的修改 android 的版本号,插件排行榜第一位。 -
fastlane-plugin-pgyer
上传到蒲公英分发平台。
-
fastlane-plugin-firim
上传到 firim。
6. 配置环境变量
新建.env
文件,这里可以配置所有的账号、秘钥、路径等自定义变量,定义之后,然后Fastlane的三个配置文件(Appfile、Deliverfile、Fastfile
)分别从.env
文件中读取配置信息。
你可能又发现 env 文件中没有语法高亮,强迫症又表示不舒服,可以在 VS Code 安装 dotEnv
插件。
给出.env
文件配置做参考:
App_Identifier = "com.xx.xxx"
# Apple email address
Apple_Id = "xxx@sina.com"
# TeamId
Team_Id = "xxx"
# target scheme
Scheme = "xxx"
# xcodeproj
Xcodeproj ="xxx.xcodeproj"
# xcworkspace
Workspace="xxx.xcworkspace"
# ipa输出路径-- Appstore
Appstore_Output_Path = "builds/appstore"
# ipa输出路径-- 蒲公英
Pgy_Output_Path = "builds/pgy"
# ipa输出路径-- TestFlight
TF_Output_Path = "./builds/testflight"
# 蒲公英 的api_key
Pgy_Api_Key = "xxx"
# 蒲公英 的user_key
Pgy_User_Key = "xxx"
使用方式:ENV['Apple_Id']
apple_id("xxx@xxx.com") ===> apple_id ENV['Apple_Id']
Fastfile 配置:
default_platform(:ios)
platform :ios do
# 所有lane执行之前
# 使用环境变量提供这个密码给fastlane,解决双重认证生成的特殊密码
before_all do
ENV["FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"] = "xxx"
end
desc "发布 测试版本到 TestFlight"
lane :beta_tf do
increment_build_number_in_plist(target: [target_name])
increment_version_number_in_plist(
target: [target_name],
)
get_certificates( # Create or get certificate, and install it
output_path: "./builds" # Download certificate in the build folder (you don't need to create the folder)
)
get_provisioning_profile( # Create or get provisioning profile
output_path: "./builds", # Download provisioning profile in the build folder
filename: "provisioning.mobileprovision" # Rename the local provisioning profile
)
update_project_provisioning( # Set the project provisioning profile (related in Xcode to the General > Signing Release section)
xcodeproj: ENV['Xcodeproj'],
target_filter: ENV['Scheme'], # Name of your project
profile: "./builds/provisioning.mobileprovision",
build_configuration: "Release"
)
update_project_team( # Set the right team on your project
teamid: CredentialsManager::AppfileConfig.try_fetch_value(:team_id)
)
gym(
workspace: ENV['Workspace'],
scheme: ENV['Scheme'],
clean: true,
export_method: "ad-hoc", # Valid values are: app-store, ad-hoc, package, enterprise, development
export_options: {
provisioningProfiles: {
"com.xxx.xxx" => "xxx",
}
},
build_path: "./builds/testflight",
output_directory: "./builds/testflight",
output_name: logDirectory
)
upload_to_testflight
end
# 发布到蒲公英
lane :beta_pgy do
desc "发布 测试版本 到 蒲公英"
# 这里是打的 Debug 版本
gym(
scheme: ENV['Scheme'],
clean:true, #打包前clean项目
export_method: "development", #导出方式 Defaults to 'Release'
configuration: "Debug",#环境
output_directory: ENV['Pgy_Output_Path'],
output_name: logDirectory,
export_options: {
provisioningProfiles: {
"com.xxx.xxx" => "xxx",
}
}
)
# 上传版本到蒲公英平台
pgyer(
api_key: ENV["Pgy_Api_Key"],
user_key: ENV["Pgy_User_Key"],
update_description: "update by fastlane"
)
# 在macOS 通知栏发送通知
notification(subtitle: "上传完成", message: "最新测试包已经上传至蒲公英平台")
end
lane :release do
desc "发布正式版到 appstore"
# 下载证书
get_certificates(
output_path: "./builds"
);
# 下载认证文件
get_provisioning_profile(
output_path: "./builds"
);
#get_push_certificate # 获取正式推送证书
# 拉取最新代码
git_pull
# 增加 version number
# 使用文档 https://github.com/SiarheiFedartsou/fastlane-plugin-versioning
# eg: version_number: '2.1.1' # Set a specific version number
# eg: bump_type: 'minor' # Automatically increment minor version number
increment_version_number_in_plist(
bump_type: 'patch', # Automatically increment patch version number(patch/minor/major)
target: ENV['Scheme']
);
# 从最新 release 分支名中获取 version.
# `pattern` is pattern by which version number will be found, `#` is place where action must find version number.
# Default value is 'release-#'(for instance for branch name 'releases/release-1.5.0' will extract '1.5.0')
# version = get_version_number_from_git_branch(pattern: 'release-#')
# 增加 build number
latest_build_number = lane_context[SharedValues::LATEST_BUILD_NUMBER];
# Automatically increments the last part of the build number.
increment_build_number_in_plist(
target: ENV['Scheme']
);
# see code signing guide for more information
#sync_code_signing(type: "appstore");
# 打包
gym(
scheme: ENV['Scheme'],
configuration: "Release",
clean: true,
export_method:"app-store",
export_options: {
provisioningProfiles: {
"com.xxx.lph" => "xxx",
}
}
);
# 上传到 ITC
deliver
# 在macOS 通知栏发送通知
notification(subtitle: "上传完成", message: "最新正式包已经上传至 appstore")
end
end
7. 配置 metadata
执行以下命令初始化 metadata,几分钟过后,会将 ITC 的所有元数据,截图等信息下载到本地
fastlane deliver init
metadata 文件夹中的文件比较多且杂,仅一个属性的配置却使用一个单独的.txt 文件来存储,官方文档表示可以创Deliverfile
文件来配置,且优先级高于 matadata 中.txt 文件中的配置,所以我们要创建Deliverfile
。
下边给出我的配置:
# The Deliverfile allows you to store various App Store Connect metadata
# For more information, check out the docs
# https://docs.fastlane.tools/actions/deliver/
############################# 基本信息 ####################################
# 1 bundle identifier
app_identifier "xxx"
# 2 用户名,Apple ID电子邮件地址
username "xx@xx.com"
# 3 版权声明
copyright "xxx"
# 4 支持的语言
supportedLanguages = {
"cmn-Hans" => "zh-Hans"
}
# 5 App应用名称
name(
'zh-Hans' => "xxx"
)
# 6 副标题
subtitle(
'zh-Hans' => "xxx"
)
# 应用说明
description({
'zh-Hans' => "
xxx
"
})
################################### 类别配置 ###################################
# 类别列表设置参见 https://github.com/fastlane/fastlane/blob/master/deliver/Reference.md
# 设置 App 的类别.这里可以设置一个主要类别,一个次要类别.
# 主要类别
primary_category "xxx"
# 主要类别第一个子类别
primary_first_sub_category
# 主要类别第二个子类别
primary_second_sub_category
# 要设置的次要类别 无
secondary_category
# 设置的次要第一个子类别 无
secondary_first_sub_category
# 设置的次要第二个子类别 无
secondary_second_sub_category
################################## 关键字\描述等信息 ###################################
# 关键字
keywords(
'zh-Hans' => "xxx,xxx,xxx"
)
# 营销地址
marketing_url({
'zh-Hans' => "http://xxx.cn"
})
# 隐私地址
privacy_url({
'zh-Hans' => "http://xxx.cn"
})
# 支持网址
support_url({
'zh-Hans' => "http://xxx.cn"
})
# 发行说明,版本更新内容
release_notes({
'zh-Hans' => "修复了一些 bug,提高使用体验,推荐更新!"
})
################################# 提交信息等 #########################################
# 1 提交审核信息:加密, 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
})
# 2 应用审核小组的联系信息 app 审核信息
app_review_information(
first_name: "x",
last_name: "x",
phone_number: "+86xxx",
email_address: "xxx@sina.com",
demo_user: "xxx",
demo_password: "xxx",
notes: ""
)
# 出口合规证明文件
trade_representative_contact_information(
first_name: "",
last_name: "",
address_line1: "xxx",
address_line2: "",
address_line3: "",
city_name: "Beijing",
state: "",
country: "China",
postal_code: "xxx",
phone_number: "",
email_address: "",
trad_name: "xxx",
is_displayed_on_app_store: false
)
####################################### 其他信息 ###################################
# 1 App价格
price_tier 0
# 2 自动发布 app: false,则需要手动发布
automatic_release false
# 3 图标路径
app_icon './fastlane/metadata/app_icon.jpg'
# 跳过HTML报告文件验证
force true
# 上传完成后提交以供审核
submit_for_review false
配置好Deliverfile
后,可以删除 metadata 文件夹中的文本配置。