fastlane resign 源码浅析与填坑
fastlane 作为移动端的自动发布工具,称得上是相当出名了。相信绝大多数公司的自动化流程,多少都会依赖 fastlane。我司也从 match 用到了 pilot。虽然使用范围很广,但是该坑的还是坑,特别是 resign 这部分,显得和 fastlane 的其他模块格格不入。所以一起深入源码来看看吧,如果你也遇到的类似的坑,欢迎探讨。
环境信息
fastlane: 2.72.0
在我看来,fastlane 是一个非常好的命令行工具,但是从 Ruby 库的角度来看,它称不上优秀。开发者众多,导致的实现方式各有千秋,有 Ruby 的,有 Bash 的,各种各样,这并不利于模块化集成。比如 match 模块,结果存储在环境变量里面,没有对应的 model,再比如今天谈到的 resign,外面套了一个 Ruby 的壳,内部是用 Bash 实现的。
重签流程
重签利用的是系统命令 /usr/bin/codesign
。一个 ipa 中,需要重签的有:dylib
, so
,0
,vis
,pvr
,framework
,appex
,app
。基本上可以归为两类:Framework 二进制和 app 二进制。
fastlane 采用了两种方式分别对 framework 和 app 进行签名:
# .framework 与 .dylib 签名 |
相较于 framework,app 会多出 entitlements 参数,这个文件中,包含的是当前 app 有哪些权限,比如:推送、关联域名、钥匙串共享等等。
Entitlements
查看 app 的 entitlements 同样可以通过 codesign
命令做到:
/usr/bin/codesign -d --entitlements :- <二进制路径> |
Entitlements 控制着程序的访问权限,这个权限和 AppID 对应,如果不匹配,会导致无法安装。来看一下常见的 entitlements 都包含哪些信息:
fastlane 的源码中,很大一部分都是在处理 Entitlements,而最坑的地方,也在这里。接下来一起看下源码。
源码浅析
Resign 部分分为 Ruby 和 Bash 两块。Ruby 部分是处理传入参数,然后 call bash,所以,源码直接看 Bash 的部分就可以了。这部分代码对应着 fastlane/sigh/lib/assets/resign.sh。
前 300 行都可以略过,是在对传入参数进行赋值和校验。
参数
resign 支持的参数可以调用 fastlane action resign
进行查看。一一介绍一下。
参数名 | 含义 |
---|---|
ipa | 需要重签的 ipa,结果会直接覆盖传入 ipa |
signing_identity | 用于重签的证书,这个参数会在传入 resign.rb 中被处理成 id |
entitlements | 指定重签 app 的 entitlement。注意,这是 app 的,不是 appex,与 use_app_entitlements 参数互斥 |
provisioning_profile | 重签所用的描述文件。主要是为了在处理 entitlements 的时候,读取当前 provision 的 entitlements |
version | 写入指定的值到 app 和 appex 的 version 和 build |
display_name | 写入 display name |
short_version | 写入 version |
bundle_version | 写入 build |
bundle_id | 写入 bundle id |
use_app_entitlements | 如果当前 entitlements 中不包含 provision 的 entitlements 的部分 key,会进行写入或修改。与 entitlements 参数互斥。 |
keychain_path | codesign 的 keychain path |
源码到 500 行的样子,都是在处理 info.plist
相关的参数,比如 version
,build
,bundle_id
这些。除此之外,还对 provision 进行了解析,读取 TeamID 等信息。
之后的代码做了两件事:
- 重签 Framework。
- 根据
entitlements
和use_app_entitlements
两个选项,处理应该用于 app 签名的 entitlements 文件。
这两步都有不同程度的问题。
重签 Framework
重签 Framework 的部分从 542 行开始,也就是 FRAMEWORKS_DIR="$APP_PATH/Frameworks"
这行开始。核心代码片段如下:
# 指定遍历 Framework 的文件夹 |
可能是出于效率考虑?不太清楚原因,但是源码很清晰的表示,fastlane 只会对 APP_PATH/Frameworks 文件夹中的 framewrok 进行签名。那如果有 framework 不在这个文件夹中呢?很简单,就无法签名。这并不影响安装,只是启动闪退…而已。
通常,framework 都在 APP_PATH/Frameworks 中,但也有例外,比如西瓜视频。
处理 Entitlements
根据 entitlements
和 use_app_entitlements
两个参数,分为三种不同的处理方式,分别是:
- 传入
entitlements
- 传入
use_app_entitlements
- 两个都不传(因为这两个值互斥,所以不存在两个都传的情况)
指定 entitlements
其中指定 entitlements
的处理很简单:
# 如果给出 entitlements 参数 |
代码就这些,所以,app 和 appex 的签名都是用的同一个 entitlements,一旦 app 和 appex 的 entitlements 不一致,就 gg。
指定 use_app_entitlements
那么再来看 use_app_entitlements
,如果指定为 true
,就会修改以前二进制签名中相同的 key。
这部分代码很多,首先需要看的是它的合并规则,用一张图应该能看明白。主要有三个量:
- 当前二进制所用 entitlements:
/usr/bin/codesign -d --entitlements :"导出路径" "二进制路径"
- 用于重签的 provision 的 entitlements:
security cms -D -i "provision 路径" > "导出路径"
,导出的是整个 provision 信息,entitlements 对应的是其中的<entitlements>
节点 - 合并之后的结果
所以,处理规则是:
- 如果已存在相同的
key:string
对应关系,则直接覆盖。 - 如果重签的 provision entitlements 不支持某个
key
,则移除。 - 如果存在相同的
key:array
对应关系,则遍历进行替换。
fastlane 中,列举了 entitlements
的所有 key,然后通过不同的格式来确认替换方式:
其中需要注意
TEAM_ID
和APP_ID
两个字段。这两个字段在绝大多数情况下是一样的,但如果出现了 app 转入其他账号这种情况,值就不一样了。在进行keychain-access-groups
的时候,要注意,它用的是 APP_ID,不是 TEAM_ID。fastlane 这部分注释非常重要,建议认真读三遍!!!
接下来是删除 Xcode 中不支持的 key,如果不移除,会导致审核出问题。这部分 key 会在使用 facebook 开源的 Buck 上编译时出现。这部分代码中的注释非常清楚,可以参考 Buck 对应的提交,以及 fastlane issue。
到此,整个 use_app_entitlements
的处理就结束了。还剩下最后一种情况。
entitlements
与 use_app_entitlements
都不指定
如果两个值都不指定,则默认将 provision 中的 entitlements 导出,然后用于重签。这样会丢失 keychain
等信息,但是基本安装还是没问题的。iOS App Signer 也是采用的这种方式。
最后来看一下 entitlements 处理的坑吧,当然,我可没说这是个 bug。
That’s not a bug, it’s a feature request. 🙃
那么,问题出在哪呢?之前介绍的是,权限多的 entitlements 处理到权限少的 entitlements 中。所以,use_app_entitlements
很适合做这件事,因为用到的更多的是删除操作。如果从权限少的,签名到权限多的呢?
因为 fastlane 的参数中,并没有给出:如果需要自定义参数,应该怎么传入,这种情况,所以,这不是 bug,是新的需求!
填坑
所以,在用 fastlane 重签的时候,需要完善两个需求。
- Framework 没有全部签名。
- Entitlements 不支持自定义字段。
在 fastlane 未修复,且没有新的 pr 的情况下,这部分被遗漏的需求,可以在调用 fastlane 重签之前,手动修复。
Frameworks 签名问题
这个解决起来很简单,手动遍历一下 APP_PATH
中的 .framework
和 .dylib
,然后手动签名(当前,framework 还可能会存在于各种各样的文件夹中,可以遇到再修复,整个文件夹进行查找感觉不太必要)。
Dir[File.join(APP_PATH, "*.framework")].each do |f| |
Entitlements 合并问题
由于。。。 entitlements
参数不支持根据不同二进制指定 Entitlements 文件;use_app_entitlements
参数不支持自定义字段,所以,最终的解决方案是:根据 fastlane 的逻辑,手动合并 entitlements,然后手动签名。(其实,到这一步,我们几乎已经自己做完了签名的功能,完全用不着 fastlane 了 🤭
逻辑和 fastlane 逻辑一样,只是自己实现的时候,可以给出自定义的字段而已,这里就不赘述了,如果还是不清楚,可以再看下 fastlane 的合并规则。大致说一下流程就行:
# 合并 entitlements |
处理完这两个问题后,再调用 fastlane 进行重签。use_app_entitlements=true
就行,因为已经手动合并过一次,所以 fastlane 的合并不会造成影响。
最后
最后,欢迎反馈问题,等着我的 pr 吧,哈哈哈哈,我还没开始写呢 😑。