使用 GitHub Actions 给 iOS 项目作持续交付

fastlane 已经能很好完成 iOS 项目的证书管理和打包发布了,但都是在本地进行的,但本地编译发布还是或多或少都影响干活,就算公司有专门的发布,但如果自己想搞些玩意,先不说编译对机器造成卡顿,单是发布到 Testflight 或 App Store 这步,晚上又是海外流量高峰期,三大运营商的家庭宽带都限速出海,还是策略性丢包,不小心就上传失败,一晃就是大半小时过去了。

Github Actions 就能比较完美解决这个痛点。不能不说,巨硬收购 github 后,私有仓库能随便建了,现在又(约)送(几)台 2C7G 的 vps 给用户作持续交付,还是非常良心的。

初始化 GitHub Actions

可以到项目的 GitHub 页面开通,选择一个空白 Action,提交,然后在本地 pull 下来修改。

或者直接在本地项目的根目录创建一个 .github 隐藏文件夹,在其下再创建一个 workflows 文件夹,再在 workflows 创建master.yml 文件,叫什么无所谓,是yml文件就行。

编排

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
name: CI

on:
push:
branches:
- master

jobs:
build:
runs-on: macOS-latest
steps:
- name: Login Github User
run: echo -e "machine github.com\n login $PERSONAL_ACCESS_TOKEN" >> ~/.netrc
env:
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- uses: actions/checkout@v2
- name: Cache cocoapods
uses: actions/cache@v2
with:
path: Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: install cocoapods dependencies
run: pod install
- name: build and publish to testflight
run: bundle exec fastlane release
env:
ISSUER_ID: ${{ secrets.ISSUER_ID }}
KEY_ID: ${{ secrets.KEY_ID }}
KEY_CONTENT: ${{ secrets.KEY_CONTENT }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}

以上就是在 push master 分支时,将项目打包发布到 testflight 的编排。大体上每一步作用一目了然,以下解析其中的细节

当 master分支有推送时触发

1
2
3
4
on: 
push:
branches:
- master

jobs 中,name 相当于打印一行字符,run 是执行一段命令,env是设置环境变量,uses,是执行一段设置好的操作。

其中 secrets.PERSONAL_ACCESS_TOKEN ,是获取预先设定的一些敏感信息,这里是获取 github 的专用密码,用以在这个虚拟机里登录 GitHub 拉去私有仓库的代码,在项目的 Settings -> Secrets 下设置。

以下是拉取代码,默认是事件分支或主分支,可以指定分支,详情请自行查阅

1
- uses: actions/checkout@v2

接着是缓存,因为每次运行的虚拟机都是一个干净的环境,这步可以每次运行完都会保存 cocoapods 的文件,下次运行时恢复

1
2
3
4
5
6
7
- name: Cache cocoapods
uses: actions/cache@v2
with:
path: Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-

接着是执行一次 pod install

接着执行 fastlane release,但由于这个虚拟机跑这些流程是全自动的,没法输入fastlane模拟登录时需要的密码或双重认证码,所以按大部分fastlane中文文章操作,是无法很好地在GitHub完成发布到 testflight 这一步。FASTLANE_SESSION 的有效期是很短的,估计很多人兴冲冲地跑了一遍GitHub Action很顺利,第二天就跑不通了。

这里只能使用18年发布的 App Store Connect API, 首先到 https://appstoreconnect.apple.com/access/api 生成密钥,这个密钥的权限选择 app管理,利用这个密钥的相关信息可以生成一个 JWTtoken,有效期是 20 分钟,但密钥是不会过期的。事实上把密钥相关信息提供给 fastlane 即可,它已经实现了对接 App Store Connect API

以下是上篇文章的Fastfile中的改动,创建一个钥匙串让match安装证书和描述文件,避免要交互输入密码,以及创建 api_key 这个变量,提供了密钥相关信息。如果要在本地使用,也需要配置好 KEY_IDISSUER_IDKEY_CONTENT 三个环境变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
default_platform(:ios)

platform :ios do
desc "Push a new TF build"
lane :release do
time = Time.new.strftime("%Y-%m-%d-%H:%M:%S")
output_path = "fastlane/archive/adhoc/" + time
create_keychain(
name: "keychain-sign",
password: "password",
default_keychain: false,
unlock: true,
timeout: 3600,
add_to_search_list: true,
)
match(
git_url: "https://github.com/username/cer",
type: "appstore",
app_identifier: "com.domian.bundleid",
readonly: true
)
increment_build_number
build_app(
workspace: "bundleid.xcworkspace",
scheme: "bundleid",
output_directory: output_path
)
api_key = app_store_connect_api_key(
key_id: ENV["KEY_ID"],
issuer_id: ENV["ISSUER_ID"],
key_content: ENV["KEY_CONTENT"],
duration: 1200, # optional
in_house: false, # optional but may be required if using match/sigh
)
upload_to_testflight(api_key: api_key, skip_waiting_for_build_processing: true)
# 清理build的产物
clean_build_artifacts
delete_keychain(name: "keychain-sign")
end
end