简介
iOS 构建步骤较为繁琐,需要处理证书签名等问题。另外由于需要同时并行构建多个工程,整个构建流程需要将支持并行构建作为重点处理。
本文主要处理 Unity 输出的 iOS 工程,使用 fastlane 管理证书以及构建,简化工作流程。
上篇 fastlane 介绍 - 狂飙 到现在已经过去了两年多,fastlane 也有了不少变化:
- fastlane 现在已经被 Google 收购,可以用于 Android 构建了,但是 fastlane 主要支持的平台依然是 macOS。
- fastlane 中的 match 组件原来不支持管理企业证书,理由是安全问题,泄露的话影响巨大,但是现在居然改为支持管理企业证书了。
环境
- Unity 5.6.6f2
- macOS 10.14.4
- Xcode 10.2
- Python 3.7.3
- fastlane 2.121.1
软件
Homebrew
使用 Homebrew 安装、管理、升级 Python 等软件。
1
|
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
|
Python
1
2
|
brew update
brew install python
|
fastlane
推荐使用 Ruby 包管理器 Gem 进行安装,因为后续可以使用 Gem 进行 fastlane
更新、依赖版本管理等等功能。
1
2
|
xcode-select --install
sudo gem install fastlane -NV
|
签名
iOS 签名容易出现以下问题:
- 多台机器上需要同步签名
- 多个签名同时存在时无法找到正确签名
- 签名过期
- 如果开发者有撤消证书的权限,证书易被误撤消,比如误操作、Xcode 自动进行的错误操作
业界最佳实践如下:
通用账号
创建账号
建议使用一个通用账号来管理证书,可以限制此账号的权限,而不是必须用公司的管理员账号。
管理员账号登录后前往 用户和访问
新用户
,职能
选择 App 管理
,开发人员资源
勾选 证书、 标识符和描述文件的访问权限。
注意:开发人员
职能无法创建 Mobile Provision
,必须选择 App 管理
职能。否则 match
在初始化时会提示当前账号权限不足:
1
2
|
[!] Apple provided the following error info:
You are not allowed to perform this operation. Please check with one of your Team Admins, or, if you need further assistance, please contact Apple Developer Program Support. https://developer.apple.com/support
|
保存信息
在注册通用账号时,需要将所有信息记录下来,包括不限于账号、密码、出生日期、提示问题及答案、手机号等等,方便日后管理账号。
同意协议
需要前往 https://appleid.apple.com 登录并接受隐私协议。否则 match
会提示:
1
2
|
[!] The request could not be completed because:
Need to acknowledge to Apple's Apple ID and Privacy statement. Please manually log into https://appleid.apple.com (or https://appstoreconnect.apple.com) to acknowledge the statement.
|
match
初始化
需要提前在 GitLab 之类的 Git 服务器上创建好需要的证书存储仓库,将访问权限设为私有,只对需要的人开放权限。
建议提前配置好访问 Git 服务器使用的 SSH 公钥,这样可以在拉取仓库时不用输入账号与密码。
1
2
|
fastlane match init
# 选择 Git 然后输入 Git 仓库地址即可
|
使用
1
2
|
bundle exec fastlane match development
# 输入需要管理的 App ID 与证书仓库的加密密码
|
注意:如果提示:
1
|
[!] Could not create another Development certificate, reached the maximum number of available Development certificates.
|
说明中途有步骤出错,那么需要在苹果证书管理网站上删除刚刚创建的证书,然后重新再来一次。
构建
目标
- 支持并行构建,一台机器上可能会同时进行多个不同工程的 iOS 版本构建,要求相互之间使用的证书等不能互相影响。
- 确定性的参数配置,要求不同时间构建出的包是一致的,也就是说不能使用任何会变化的参数。
安装依赖
1
2
|
Could not find os-1.0.0 in any of the sources
Run `bundle install` to install missing gems.
|
需要先安装 fastlane 组件,否则无法启动
1
2
|
bundle install
# 在需要时输入密码即可
|
注意:不要使用 sudo bundle install
,否则会影响非 root 用户使用。可以直接使用 bundle install
进行安装,在需要密码时程序会向你询问密码。
Xcode 版本
在 Fastfile
文件中增加 xcversion(version: "10.2")
会提示需要额外安装 xcode-install
组件。
1
2
3
4
5
|
Missing gem 'xcode-install', please add the following to your local Gemfile:
▸ gem "xcode-install"
Add 'gem "xcode-install"' to your Gemfile and restart fastlane
|
将 gem "xcode-install"
添加到 Gemfile
中并重新执行 fastlane 命令。
Team ID
1
|
error: Signing for "Unity-iPhone" requires a development team. Select a development team in the build settings editor that matches the selected profile "match Development test.example.client". (in target 'Unity-iPhone')
|
需要禁用 Xcode 自动签名机制,同时设置 TeamID
信息。
1
2
3
4
|
disable_automatic_code_signing(
path: "Unity-iPhone.xcodeproj",
team_id: ENV["sigh_test.example.client_development_team-id"]
)
|
Mobile Provision
1
|
error: "Unity-iPhone" requires a provisioning profile. Select a provisioning profile for the "Release" build configuration in the project editor. (in target 'Unity-iPhone')
|
需要手动指定 Mobile Provision
以及 App ID
与 Mobile Provision
之间的关系。
证书
如果将证书导入到默认 login.keychain
中,如果存在同名过期证书则有可能会使用过期的错误证书,因此正确方法是创建新的 Keychain,然后将其搜索顺序放在搜索列表最前面。
codesign
请求访问 Keychain
弹窗问题:
1
|
▸ Command CodeSign failed with a nonzero exit code
|
解决方法就是执行 security
命令:
1
|
security set-key-partition-list -S apple-tool:,apple: -s -k keychainPass keychainName
|
xcodebuild 配置
1
2
3
4
5
|
[!] 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
|
将等待 Xcode 修改设置的超时时间加长:
1
|
ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120"
|
结果
步骤
- 设置
Keychain
名字为项目名字+频道,这里可以使用任意方法组合,只要保证不同构建间唯一即可。
- 强制指定
Xcode
版本,防止构建旧版本时错误地使用了最新的 Xcode
。
- 创建
Keychain
,默认解锁、不超时锁定、不添加到搜索列表中。
- 使用
match
同步签名相关证书与 Mobile Provision
信息。
- 设置刚刚创建的
Keychain
为搜索列表中第一项,防止搜索到默认环境中过期无效的证书信息。
- 允许
codesign
直接无密码访问 Keychain
,否则构建在这一步失败退出。
- 禁用自动签名并设置 Team ID。
- 更新
Mobile Provision
信息。
- 设置 xcodebuild 超时时间。
- 构建 iOS 工程,指定
App ID
与 Mobile Provision
关系。
- 删除刚刚创建的
Keychain
。
文件
Fastfile
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
# 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)
platform :ios do
desc "iOS Development Build"
lane :development do |options|
keyname = options[:name] + "-" + options[:channel] + ".keychain"
keypass = "your-password"
app_identifier = "test.example.client"
xcversion(version: "10.2")
create_keychain(
name: "#{keyname}",
password: "#{keypass}",
default_keychain: false,
unlock: true,
timeout: false,
lock_when_sleeps: false,
lock_after_timeout: false,
add_to_search_list: false
)
sync_code_signing(
type: "development",
app_identifier: "#{app_identifier}",
readonly: true,
keychain_name: "#{keyname}",
keychain_password: "#{keypass}"
)
# Keychain we created must be the first one in search list
sh "security list-keychains -d user -s '#{keyname}' $(security list-keychains -d user | sed s/\\\"//g)"
# Disable /usr/bin/codesign access request
sh "security set-key-partition-list -S apple-tool:,apple: -s -k '#{keypass}' '#{keyname}'"
disable_automatic_code_signing(
path: "Unity-iPhone.xcodeproj",
team_id: ENV["sigh_test.example.client_development_team-id"]
)
update_project_provisioning(
xcodeproj: "Unity-iPhone.xcodeproj",
target_filter: "Unity-iPhone",
profile: ENV["sigh_test.example.client_development_profile-path"],
build_configuration: "Release"
)
# Extend wait times for command to avoid timeout failure:
# xcodebuild -showBuildSettings -scheme Unity-iPhone -project ./Unity-iPhone.xcodeproj
ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120"
build_ios_app(
export_options: {
method: "development",
provisioningProfiles: {
"#{app_identifier}" => ENV["sigh_test.example.client_development_profile-name"],
}
},
include_bitcode: false,
output_directory: options[:output_directory],
output_name: options[:output_name]
)
end
after_all do |lane, options|
keyname = options[:name] + "-" + options[:channel] + ".keychain"
if File.exists?(File.expand_path("~/Library/Keychains/#{keyname}-db"))
delete_keychain(name: "#{keyname}")
end
end
end
|