在一款 App 从开发到上架都会经历 编译 -> 打包 -> 签名 -> 推送 App Store Connect -> 提审 -> 上架 的过程。其中编译打包这种重复且繁琐的工作公司内都会有一个 CI 平台去负责,我们今天就来了解一下 CI 的编译打包流程是怎样的。
Table of contents
Open Table of contents
fastlane 简介
fastlane 是自动为 iOS 和 Android 应用程序进行测试版部署和发布的最简单方式。它会为你处理所有繁琐的任务,如生成屏幕截图、处理代码签名和发布应用程序。
下面是一个简单的编译发布的例子:
lane :beta do
increment_build_number
build_app
upload_to_testflight
end
lane :release do
capture_screenshots
build_app
upload_to_app_store # 将屏幕截图和二进制文件上传到 iTunes
slack # 让你的队友知道新版本上线了
end
上面的代码定义了两个不同的打包流程,如果你想在 App Store 发布你的应用,你只需要执行如下操作:
$ fastlane release
使用 fastlane 执行打包流程
Ruby 安装环境
首先我们需要安装一套开发用的 Ruby 环境,macOS 中不推荐直接使用系统的 Ruby。我们可以用多种方式来管理我们的 Ruby 环境,例如 rvm,rebnv,homebrew。由于本人是通过 homebrew 安装的 Ruby 环境,所以本篇只介绍 homebrew 的管理方式,其他方式请自行查阅资料。
对于使用 homebrew 安装 Ruby 可以看我之前的这片文章,这里有详细的说明:使用 Homebrew 管理 Ruby
fastlane 的安装
Ruby 环境设置完毕后我们就可以开始安装 fastlane,同样使用 homebrew 的方式:
$ brew install fastlane
fastlane 的配置
fastlane 安装完成后我们 cd 到我们的工程目录,执行如下命令进行初始化:
$ fastlane init
在此期间,fastlane 将自动检测项目,并询问任何丢失的信息。例如:开发者账号,密码等。
安装完成之后,在我们的工程中会生成一个 fastlane 文件夹,里面有两个重要的文件:Appfile 和 Fastfile。
Appfile 记录了一些开发账号信息,例如:Apple ID、Bundle Identifier等。
Fastfile 则是你打包流程的具体实现,一个简单的例子如下所示:
lane :my_lane do
# Whatever actions you like go in here.
end
这时我们在终端中,cd 到我们的工程,然后执行 fastlane my_lane 即会执行我们定义的打包流程。你可以定义很多个 lane 用于执行不同的流程。
fastlane 相关功能简介
非常有用的 block
fastlane 还提供了两个非常有用的 block:before_all 和 after_all。
before_all 此块将在运行 lane 流程之前执行。它支持与 lane 相同的操作。在这个 block 中我们就可以执行一些打包之前需要进行的操作,比如执行 cocoapods。
before_all do |lane|
cocoapods
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
fastlane 中的错误处理由 error block 处理,当任何块(before_all、lane 本身或 after_all)中发生错误时将执行该 block。我们可以使用 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
导入其他 Fastfile 文件
如果我们的 Fastfile 比较复杂我们可以拆分成多个文件然后进行 import 操作,就像我们开发时候的头文件导入一般。 对于 import 功能 fastlane 提供了两种方式。
import
导入本地文件路径
import "../GeneralFastfile"
override_lane :from_general do
# ...
end
import_from_git
从另一个 git 资源库导入,我们可以使用该资源库为所有项目创建一个带有默认 Fastfile 的 git 资源库:
import_from_git(url: 'https://github.com/fastlane/fastlane')
# or
import_from_git(url: 'git@github.com:MyAwesomeRepo/MyAwesomeFastlaneStandardSetup.git',
path: 'fastlane/Fastfile')
lane :new_main_lane do
# ...
end
这还将自动的从 repo 中导入所有的 action。
如果你需要覆盖现有的
lane声明,可以使用override_lane关键字
想更多的了解 Fastfile 请参阅文档: Fastfile
使用环境变量
如果我们的打包环境比较复杂,比如需要适应多个账户打包需求,此时我们可以定义环境变量来管理相关信息。我们可以在与 Fastfile 相同的目录中创建 .env 或 .env.default 文件,然后定义环境变量。使用 dotenv 加载环境变量。下面是一个例子:
WORKSPACE=YourApp.xcworkspace
HOCKEYAPP_API_TOKEN=your-hockey-api-token
在 Fastfile 中我们就可以使用如下方式获取相关环境变量:
bundle_identifier = ENV['APP_IDENTIFIER']
更多详细的讲解请看官方文档:Environment Variables
众多的 actions
fastlane 最终要的功能就是提供了众多的 actions 供我们使用,最常用的有:gym (build_app),cocoapods,match,upload_to_testflight,upload_to_testflight等。这里不再做过多的讲解,官方文档已经说的非常清楚,详情请查阅 fastlane actions。
最后附上一个完整的流程示例:
require './xcode_build_settings'
default_platform(:ios)
platform :ios do
bundle_identifier = ENV['APP_IDENTIFIER']
scheme = ENV['SCHEME']
xcworkspace = ENV['XCWORKSPACE']
xcodeproj = ENV['XCODEPROJ']
apple_id = ENV['APPLE_ID']
adhoc_output_path = "./fastlane/adhoc"
product_output_path = "./fastlane/release"
# branch_name = "xxxxx"
desc "Push a new beta build to TestFlight"
api_key = app_store_connect_api_key(
key_id: "xxxx",
issuer_id: "xxx-xxx-xxx",
key_filepath: './fastlane/AuthKey_xxxx.p8',
duration: 1200,
in_house: false
)
before_all do |lane|
BuildSettings.configTarget
end
lane :beta do
increment_build_number(xcodeproj: xcodeproj)
match(
api_key: api_key,
app_identifier: bundle_identifier,
type: "adhoc"
# git_branch: branch_name
)
cocoapods(
repo_update: true,
clean_install: true,
use_bundle_exec: false,
podfile: "./Podfile"
)
build_app(
clean: true,
output_directory: adhoc_output_path,
output_name: "xxxx",
workspace: xcworkspace,
scheme: scheme,
xcargs: "-allowProvisioningUpdates",
export_options: {
manifest: {
appURL: "https://server.local/apps/xxx.ipa",
displayImageURL: "https://server.local/apps/xxx.png",
fullSizeImageURL: "https://server.local/apps/xxx.png"
},
thinning: "<none>"
}
)
verify_build(
provisioning_type: "distribution",
bundle_identifier: bundle_identifier
)
upload_to_testflight(
api_key: api_key,
app_identifier: bundle_identifier,
ipa: "./fastlane/release/xxxx.ipa"
)
end
lane :product do
increment_build_number(xcodeproj: xcodeproj)
match(
api_key: api_key,
app_identifier: bundle_identifier,
type: "appstore"
# git_branch: branch_name
)
build_app(
clean: true,
output_directory: product_output_path,
output_name: "xxxx",
workspace: xcworkspace,
scheme: scheme,
xcargs: "-allowProvisioningUpdates"
)
verify_build(
provisioning_type: "distribution",
bundle_identifier: bundle_identifier
)
end
error do |lane, exception|
end
end