发现问题
如题,在 Podfile 里将 pod 指向了某个版本,但 pod install 却无法正确的下载该版本号的代码。这个问题是最近在打包机上遇到的,Jenkins 打包提交给测试的时候偶尔会出现提交的代码未应用,或者在本地确认无误的代码在打包过程中 xcodebuild 直接编译失败。
因为场景比较特殊所以很快发现了问题。一开始我们在 Podfile 里将 PodA
指向了版本号 1.0.0
,如:
1 | Pod 'PodA', '1.0.0' |
提测过程中,我们从 1.0.0
tag 处拉取了分支 branch_a
,并且在该分支上提交了 commitA
。然后在 Podfile 里将 PodA
从版本号 1.0.0
改向了分支 branch_a
,如:
1 | Pod 'PodA', :git => 'https://xyz.com/PodA.git', :branch => 'branch_a' |
打包提交给测试之后,因为需求变更,在分支 branch_a
上的改动不需要了,所以又将 Podfile 里的 PodA
从分支改回了版本号 1.0.0
,如:
1 | Pod 'PodA', '1.0.0' |
这时再打包就发现 Pods 文件夹下的 PodA
的代码依然包含了分支 branch_a
上的 commitA
,并未更改为 1.0.0
tag 的代码。
通过查看 CocoaPods 源码发现,在 pod install 过程中,CocoaPods 会根据 Pods/Manifest.lock
文件比对 pod 是否需要重新下载。从版本号改向分支的时候,CocoaPods 有个 predownloaded
的判读,重新下载了 pod 代码;然而从分支改向版本号的时候,对比了 pod 版本号、名称、checksum 都没变所以代码未更新。以下是 CocoaPods 1.10.2 版本的判断代码:
1 | def pod_changed?(pod) |
源码解读:pod install 对 pod sources 更新逻辑
现在,从头捋一遍:pod install 过程中这么判断哪些 pod 该更新,哪些 pod 不更新的呢?
首先打开 installer.rb
文件,找到 def install!
方法,便是 pod install
的入口方法了
1 | def install! |
其中调用了 download_dependencies
方法:
1 | def download_dependencies |
其中调用了 install_pod_sources
方法:
1 | # Downloads, installs the documentation and cleans the sources of the Pods |
发现 sandbox_state.added
和 sandbox_state.changed
应该是分别对应着 新增、变更 的 pod。全局搜索 sandbox_state
会发现方法:
1 | # @return [SpecsState] The state of the sandbox returned by the analyzer. |
全局搜索 analysis_result
,看看它是哪里来的,会发现方法:
1 | # @!group Installation steps |
analysis_result
是执行了 analyzer
的 analyze
方法得到的。搜索当前这个 analyze(
方法的调用是在方法:
1 | # @return [Analyzer] The analyzer used to resolve dependencies |
以上 resolve_dependencies
方法也是在 install 方法里被调用的,而且是在 download_dependencies
方法之前。根据方法名也不难看出这里的代码逻辑:首先调用 resolve_dependencies
方法进行依赖分析拿到结果,再执行 download_dependencies
方法下载依赖。
接着搜索 create_analyzer
方法:
1 | def create_analyzer(plugin_sources = nil) |
Analyzer
应该就是具体的分析类,这个类里拥有的 analyze
方法正是返回了上述提到的 @analysis_result
。打开 analyzer.rb
文件搜索 analyze
试试:
1 | # Performs the analysis. |
至此,就找到了前文提到的 def sandbox_state
方法返回的 analysis_result.sandbox_state
正是以上方法里 AnalysisResult
这个类的 sandbox_state
属性,而该属性又是来自 generate_sandbox_state
方法生成的 SandboxAnalyzer
对象,执行 analyze
方法返回的。
找到 SandboxAnalyzer
类就找到我们此行的最终答案,打开 sandbox_analyzer.rb
文件:
1 | # Performs the analysis to the detect the state of the sandbox respect |
这段代码就是 pod install 过程中判断得到的哪些 pod 是新增的、哪些 pod 是删除的、哪些 pod 是改变的,而新增的 和 改变的则需要下载新的代码。
关注 def pod_changed
方法,spec 相当于目标 pod 的 podspec,sandbox_ 相关的则是进行比对的 Pods/Manifest.lock
文件包含的 pod 信息。
在 Podfile 里将 pod 从分支指向改为 版本号 时,这里的判断的 version
、checksum
、name
等信息都是相同的,所以该 pod 并未出现在 changed 数组里,Pods 文件夹里该 pod 的代码也就并未更新。
修复问题: PR #10825
首先要搞明白在 Podfile 里将 pod 指向分支和指向版本号在 lock 记录里究竟有什么不一样,才好处理究竟要怎么判断。打开 Podfile.lock
文件(和 Pods/Manifest.lock
文件内容一致),搜索 PodA
相关的内容:
1 | # Pod 'PodA', :git => 'https://xyz.com/PodA.git', :branch => 'branch_a' |
由此,只要判断 Podfile 里生成的 Dependency
和 Pods/Manifest.lock
文件里记录的 Dependency
不一致则代表 pod changed,需要被更新。
通过查看 cocoapods-core 代码,Lockfile
类里已经有的 dependencies
数组,收集好了 Dependency
对象,Podfile
类里也已经提供好了 dependencies
数组。只要根据 pod name 从两个数组里分别匹配出 Dependency
对象进行比对,如果不同则代表 pod changed,需要被更新。
思路大概就这样,具体改动可参考 PR 里的 files changed
private hofix
因为 PR 里 @dnkoutso 标记了 1.11.0
发布。感觉日常开发中遇到这个 bug 还挺严重的。如果 Jenkins 打包失败了还好,还能发现问题,万一不影响代码编译很有可能将错误代码提交给测试,甚至带到线上去。所以在组内发布了 private hofix 版本。基于当前的最新版本(1.10.2
)cherry pick 了相关的 commit,发布了 1.10.2.9.private.hotfix
版本。作为在 1.11.0
出来之前的临时使用版本吧。