回顾
在 CocoaPods 历险 - 总览 中已经分析了 CocoaPods 的整个组件构成和职责。这篇博客单说在 Install 过程中的版本仲裁算法,其中包含了 Resolver
的入口过程以及 Molinillo 仲裁算法,这一篇我们来关注入口方案。
本文以 1.6.1
版本作为分析。后面从之前文章 CocoaPods 历险 - 总览 说到的 resolve_dependencies
依赖解析流程流程说起。
def resolve_dependencies
# 拿到 plugin sources
plugin_sources = run_source_provider_hooks
# 创建一个 Analyzer
# Analyzer 是通过分析, Podfile.lock, plugin_sources 返回一个 AnalysisResult
# AnalysisResult 包括了 CocoaPods 依赖环境、CocoaPods 处理后在 xcodeproj 中的部分信息等。
analyzer = create_analyzer(plugin_sources)
# 如果标记了 repo_update (在 pod update 或者 pod install --repo-update 时生效)
UI.section 'Updating local specs repositories' do
# 更新 $HOME 目录下 CocoaPods 的本地 Specs 仓库
analyzer.update_repositories
end if repo_update?
UI.section 'Analyzing dependencies' do
# 调用 Analyzer 等 analyze 方法入口,并把部分结果拿回 install 类中
analyze(analyzer)
# 拼写错误降级识别,白名单过滤
validate_build_configurations
# 清理 sandbox 目录
clean_sandbox
end
UI.section 'Verifying no changes' do
verify_no_podfile_changes!
verify_no_lockfile_changes!
end if deployment?
analyzer
end
在第一次构建依赖图的时候,由于 Molinillo 需要拿到所有的依赖信息,所以要在 Analyzer
分析出所有依赖节点才可以构建初始状态的依赖图。
Resolver 的依赖仲裁
Ruby 的项目中,往往最头疼的就是梳理各种各样的 Class
和 Module
,在看 Molinillo
这个组件之前,笔者先整体浏览了一下这个库的代码结构。在 CocoaPods
项目中的算法入口其实是在 /lib/cocoapods/resolver.rb
这个文件中。你从它第一行的 require 'molinillo'
就可以意识到这个问题😂。
所以我在其 initialize
方法处,打印了运行堆栈,并且 raise
掉整个流程来调试 pod install
的过程,其 Log 如下:
lib/cocoapods/installer/analyzer.rb:908:in `new'
lib/cocoapods/installer/analyzer.rb:908:in `block in resolve_dependencies'
lib/cocoapods/user_interface.rb:64:in `section'
lib/cocoapods/installer/analyzer.rb:907:in `resolve_dependencies'
lib/cocoapods/installer/analyzer.rb:114:in `analyze'
lib/cocoapods/installer.rb:266:in `analyze'
lib/cocoapods/installer.rb:174:in `block in resolve_dependencies'
lib/cocoapods/user_interface.rb:64:in `section'
lib/cocoapods/installer.rb:173:in `resolve_dependencies'
我们从 resolve_dependencies
方法进入后,在实例化 Analyze
之后,其对象的 analyze
方法会在文件的 907 行(这个不是绝对的)调用 resolve_dependencies
从而触发了实例化 Resolver
对象的构造方法。
下面来看 analyze
是为什么要实例化 Resolver
对象。
def analyze(allow_fetches = true)
# 如果已经有分析结果,直接返回
return @result if @result
# 校验 Podfile
validate_podfile!
# 校验 Podfile.lock 文件中 CocoaPods 的版本
validate_lockfile_version!
if installation_options.integrate_targets?
# 预处理 Podfile 中每个 target 信息
target_inspections = inspect_targets_to_integrate
else
# 校验声明的 platform 信息
verify_platforms_specified!
target_inspections = {}
end
# 调用 generate_podfile_state 来记录 sandbox 内每一个 Pod 的变化
podfile_state = generate_podfile_state
# 这里要存储非 Source Spec 下的 Options
# 即 Podfile.lock 中的 CHECKOUT OPTIONS 字段
# 通过 KV 方式进行预存,Key 为 Pod 的 Root Name
store_existing_checkout_options
if allow_fetches == :outdated
# special-cased -- we're only really resolving for outdated, rather than doing a full analysis
# 特殊边界情况,如果 outdated 则不参与全依赖分析
elsif allow_fetches == true
# 被告知需要 fetch,则说明我们需要 fetch 到远程仓库到 spec 文件
fetch_external_sources(podfile_state)
elsif !dependencies_to_fetch(podfile_state).all?(&:local?)
# 需要检查如果需要更新到 spec 本地已经有对应版本,则通过
# 否则跑出异常,告知 sandbox 不是最新版本
raise Informative, 'Cannot analyze without fetching dependencies since the sandbox is not up-to-date. Run `pod install` to ensure all dependencies have been fetched.' \
"\n The missing dependencies are:\n \t#{dependencies_to_fetch(podfile_state).reject(&:local?).join("\n \t")}"
end
# 要点一:取出 Podfile.lock 中所有的依赖
# 这个方法会返回一个初始化的依赖图,带有 Podfile.lock 中已经 lock 的所有依赖构成的多 root 图
locked_dependencies = generate_version_locking_dependencies(podfile_state)
# locked_dependencies.each do |item|
# puts item.name
# puts item.payload
# puts item.root
# end
# 要点二:根据 Target 对 Podfile 中的描述来对 spec 进行分组
resolver_specs_by_target = resolve_dependencies(locked_dependencies)
# 校验 platform 合法性
validate_platforms(resolver_specs_by_target)
# Spec 对实例数组
specifications = generate_specifications(resolver_specs_by_target)
# 原工程中对 Targets 实例数组
targets = generate_targets(resolver_specs_by_target, target_inspections)
# Pod Project 的 Targets 实例数组
pod_targets = calculate_pod_targets(targets)
# Sandbox 中 Spec 数组,可理解成与 Manifest 状态同步
sandbox_state = generate_sandbox_state(specifications)
# 根据 target 对 Spec 做分组操作
specs_by_target = resolver_specs_by_target.each_with_object({}) do |rspecs_by_target, hash|
hash[rspecs_by_target[0]] = rspecs_by_target[1].map(&:spec)
end
# 根据 sources 对 Spec 做分组操作
specs_by_source = Hash[resolver_specs_by_target.values.flatten(1).group_by(&:source).map do |source, specs|
[source, specs.map(&:spec).uniq]
end]
sources.each { |s| specs_by_source[s] ||= [] }
# 要点三:返回一个 AnalysisResult 实例,得到最终的仲裁结果
@result = AnalysisResult.new(podfile_state, specs_by_target, specs_by_source, specifications, sandbox_state,
targets, pod_targets, @podfile_dependency_cache)
end
要点一:SpecsState
溯源
analysis
方法是进入 Molinillo
的入口,generate_version_locking_dependencies
方法是用来生成 Podfile.lock
中 Pods
字段。追本溯源,可以先看下 generate_podfile_state
这个方法:
def generate_podfile_state
if lockfile
pods_state = nil
UI.section 'Finding Podfile changes' do
# diff podfile 解析结果
pods_by_state = lockfile.detect_changes_with_podfile(podfile)
# 通过 diff 结果生成 SpecsState 实例
pods_state = SpecsState.new(pods_by_state)
# verbose 下输出 log
pods_state.print if config.verbose?
end
pods_state
else
state = SpecsState.new
# 如果没有 lock 文件取出
state.added.merge(podfile_dependencies.map(&:root_name))
state
end
end
源码层面上来看,在获取 SpecsState
的初始状态,会参考 Podfile
和 Podfile.lock
文件中记录的信息。一次的 Analysis 过程会维护一个 SpecsState
对象,这个对象记录了多个状态数组,里面记录了增、删、改、不改四个状态。
class SpecsState
# @return [Set<String>] 需要进行增操作的 Pod Name 集合
#
attr_reader :added
# @return [Set<String>] 需要进行改操作的 Pod Name 集合
#
attr_reader :changed
# @return [Set<String>] 需要进行删操作的 Pod Name 集合
#
attr_reader :deleted
# @return [Set<String>] 无需修改的 Pod Name 集合
#
attr_reader :unchanged
end
在二次 pod install
的时候输出初始化比对后返回的 SpecsState
中的数据:
增加 #<Set: {}>
删除 #<Set: {}>
不改 #<Set: {"AcknowList", "ActiveLabel", "Alamofire", "AlamofireObjectMapper", "CocoaLumberjack", "CollectionKit", "Crashlytics", "Curiouscat", "DeviceKit", "DTCoreText", "DWAnimatedLabel", "EFQRCode", "Fabric", "FaveButton", "Firebase", "FLEX", "FTPopOverMenu_Swift", "GDMDebugger", "Highlightr", "KVOController", "lottie-ios", "LTMorphingLabel", "MJRefresh", "MMMarkdown", "NVActivityIndicatorView", "ObjectMapper", "pop", "PySwiftyRegex", "ReachabilitySwift", "SDWebImage", "SGQRCode", "SkeletonView", "SnapKit", "Straycat", "SwiftMessages", "SwiftyUserDefaults", "SwipeCellKit", "ViewAnimator", "WCDB.swift", "WCLShineButton", "WechatOpenSDK", "XLPagerTabStrip"}>
修改 #<Set: {}>
发现 Podfile
中声明的 Pod 依赖全部进入了 unchanged
集合,符合我们的预期。在这之后,将 SpecsState
传入 generate_version_locking_dependencies
中来继续处理。当然这一切都依托于我们当前目录下有 Podfile.lock
文件。
def generate_version_locking_dependencies(podfile_state)
if update_mode == :all || !lockfile
LockingDependencyAnalyzer.unlocked_dependency_graph
else
# 从 state 中获取出 deleted 和 changed 的 Pod
deleted_and_changed = podfile_state.changed + podfile_state.deleted
# 已经更新的所有 Pods 或者是应该更新的 Pods 加入数组,在非 lock 状态下
deleted_and_changed += pods_to_update[:pods] if update_mode == :selected
# 获取出所有 path 指向的 Pod 名,这里只关心 root_name
local_pod_names = podfile_dependencies.select(&:local?).map(&:root_name)
# local pods 不进行 lock
pods_to_unlock = local_pod_names.to_set.delete_if do |pod_name|
next unless sandbox_specification = sandbox.specification(pod_name)
sandbox_specification.checksum == lockfile.checksum(pod_name)
end
# 根据 lock 文件以及相关信息生成依赖图,并返回
LockingDependencyAnalyzer.generate_version_locking_dependencies(lockfile, deleted_and_changed, pods_to_unlock)
end
end
有 Podfile.lock 条件 - 生成版本 Locking 图
LockingDependencyAnalyzer
这个类用来生成未修改的 Pod 依赖项,这些依赖是存在于 Podfile.lock
中且标记成非更新状态的。如果是在非更新模式,pods_by_state
通过 LockingDependencyAnalyzer
初始化一个结果传递给 Resolver
来固化这些 Pod 版本。
# 自己添加的查询图节点的 log 方法
def self.check_dependency_graph(graph)
puts "check dependency graph"
res = []
graph.each do |vertex|
res << "#{vertex.payload}"
end
print res
puts ""
end
# module Pod => class Installer => class Analysis => module LockingDependencyAnalyzer
def self.generate_version_locking_dependencies(lockfile, pods_to_update, pods_to_unlock = [])
# lockfile - lock 文件
# pods_to_update - 标记需要更新的 Pod 集合
# pods_to_unlock - 不需 lock 的 Pod 集合
puts("lockfile => ", lockfile.pod_names)
puts("pods_to_update => ", pods_to_update)
puts("pods_to_unlock => ", pods_to_unlock)
dependency_graph = Molinillo::DependencyGraph.new
if lockfile
added_dependency_strings = Set.new
# 拿出所有的显式依赖
explicit_dependencies = lockfile.dependencies
explicit_dependencies.each do |dependency|
check_dependency_graph(dependency_graph)
# 对依赖图进行加顶点,并且确定为 root 节点
dependency_graph.add_vertex(dependency.name, dependency, true)
end
# 取出 lockfile 中 PODS 字段内容
# 这个字段包含了上次记录的所有依赖
pods = lockfile.to_hash['PODS'] || []
# 遍历所有 Pods 进行增加 root 点操作
pods.each do |pod|
check_dependency_graph(dependency_graph)
# 继续在图中加点,由于取的是 PODS 字段,所有依赖全部加入
add_to_dependency_graph(pod, [], dependency_graph, pods_to_unlock, added_dependency_strings)
end
# 将 update 数组中的依赖通过 root name 进行转换
pods_to_update = pods_to_update.flat_map do |u|
root_name = Specification.root_name(u).downcase
dependency_graph.vertices.each_key.select { |n| Specification.root_name(n).downcase == root_name }
end
# 将待更新节点做拆点操作
pods_to_update.each do |u|
dependency_graph.detach_vertex_named(u)
end
# 遍历图所有节点,增加 spec 仓的 git 地址
dependency_graph.each do |vertex|
next unless dep = vertex.payload
dep.podspec_repo ||= lockfile.spec_repo(dep.root_name)
end
end
dependency_graph
end
def self.add_child_vertex_to_graph(dependency_string, parents, dependency_graph, pods_to_unlock, added_dependency_strings)
# 这里的去重用了 Set 的特性,如果是重复的元素,在 Set.add? 之后会返回 null
# 所以这里如果是重复的节点,就直接 return
return unless added_dependency_strings.add?(dependency_string)
# 通过描述字符串获取出 Dependency 实例
dependency = Dependency.from_string(dependency_string)
# 如果在 pods_to_unlock 这个未加锁数组中,则只使用 Pods 名来实例化 Dependency
if pods_to_unlock.include?(dependency.root_name)
dependency = Dependency.new(dependency.name)
end
# 加边操作
vertex = dependency_graph.add_child_vertex(dependency.name, nil, parents, nil)
# 如果有 payload 信息,则合并版本约束
dependency = vertex.payload.merge(dependency) if vertex.payload
puts "合并版本约束 payload ", dependency
# 修改节点的 payload 信息
vertex.payload = dependency
dependency
end
def self.add_to_dependency_graph(object, parents, dependency_graph, pods_to_unlock, added_dependency_strings)
# 如果是 String 类型,代表是单一依赖,只有一个节点
# Hash 会带上一组依赖关系
case object
when String
add_child_vertex_to_graph(object, parents, dependency_graph, pods_to_unlock, added_dependency_strings)
when Hash
object.each do |key, value|
dependency = add_child_vertex_to_graph(key, parents, dependency_graph, pods_to_unlock, added_dependency_strings)
# 由于 PODS 信息中只有二级依赖关系,所有只遍历一层隐式依赖即可
value.each { |v| add_to_dependency_graph(v, [dependency.name], dependency_graph, pods_to_unlock, added_dependency_strings) }
end
end
end
在我们拥有 Podfile.lock
的情况下,构建这个初始图就是比较容易的事情。重新梳理一下这个流程。首先我们解析完 Podfile.lock
和 Podfile
之后,构造了一个 SpecsState
实例,这里面会分析出增、删、改、未改这四个 Pod 数组。由于已经存在 Podfile.lock
文件,将其中的 Dependency
字段,也就是 Podfile
里的显示依赖拿出来构造多个 root 节点。在以下 Log 中,我用(依赖比较少的)新项目来做测试:
check dependency graph
[]
加点 Alamofire Alamofire (~> 4.7.3) 根节点
check dependency graph
["Alamofire (~> 4.7.3)"]
加点 AlamofireObjectMapper AlamofireObjectMapper (~> 5.0) 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)"]
加点 CocoaLumberjack/Swift CocoaLumberjack/Swift 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift"]
加点 DeviceKit DeviceKit (~> 2.0) 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)"]
加点 ESTabBarController-swift ESTabBarController-swift 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift"]
加点 FLEX FLEX (~> 2.0) 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)"]
加点 IQKeyboardManagerSwift IQKeyboardManagerSwift (~> 6.2.0) 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)"]
加点 KVOController KVOController 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController"]
加点 ObjectMapper ObjectMapper (~> 3.1) 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1)"]
加点 pop pop (~> 1.0) 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1)", "pop (~> 1.0)"]
加点 Reveal-SDK Reveal-SDK (from `Reveal`) 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)"]
加点 SDWebImage SDWebImage (~> 4.4.6) 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)"]
加点 SnapKit SnapKit (~> 5.0.0) 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)"]
加点 SwifterSwift SwifterSwift (~> 5.0.0) 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)"]
加点 SwiftMessages SwiftMessages (~> 6.0.2) 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)"]
加点 SwiftyUserDefaults SwiftyUserDefaults 根节点
check dependency graph
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults"]
可以看到在依赖图中不断的增加根节点。这些节点是 Podfile.lock
中的 Dependency
字段所有依赖。然后根据 Podfile.lock
的 Pods
进行完整图的构建。以下是输出测试 Log:
["Alamofire (~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults"]
加点 Alamofire 非根节点
合并版本约束 payload
Alamofire (= 4.7.3, ~> 4.7.3)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7.3)", "AlamofireObjectMapper (~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults"]
加点 AlamofireObjectMapper 非根节点
合并版本约束 payload
AlamofireObjectMapper (= 5.2.0, ~> 5.0)
加点 Alamofire 非根节点
加边 AlamofireObjectMapper => Alamofire
requirement
合并版本约束 payload
Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)
加点 ObjectMapper 非根节点
加边 AlamofireObjectMapper => ObjectMapper
requirement
合并版本约束 payload
ObjectMapper (~> 3.1, ~> 3.4)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1, ~> 3.4)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults"]
加点 CocoaLumberjack/Core 非根节点
合并版本约束 payload
CocoaLumberjack/Core (= 3.5.3)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1, ~> 3.4)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)"]
加点 CocoaLumberjack/Swift 非根节点
合并版本约束 payload
CocoaLumberjack/Swift (= 3.5.3)
加点 CocoaLumberjack/Core 非根节点
加边 CocoaLumberjack/Swift => CocoaLumberjack/Core
requirement
合并版本约束 payload
CocoaLumberjack/Core (= 3.5.3)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1, ~> 3.4)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)"]
加点 DeviceKit 非根节点
合并版本约束 payload
DeviceKit (= 2.0.0, ~> 2.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1, ~> 3.4)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)"]
加点 ESTabBarController-swift 非根节点
合并版本约束 payload
ESTabBarController-swift (= 2.7)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1, ~> 3.4)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)"]
加点 FLEX 非根节点
合并版本约束 payload
FLEX (= 2.4.0, ~> 2.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1, ~> 3.4)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)"]
加点 IQKeyboardManagerSwift 非根节点
合并版本约束 payload
IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController", "ObjectMapper (~> 3.1, ~> 3.4)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)"]
加点 KVOController 非根节点
合并版本约束 payload
KVOController (= 1.2.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (~> 3.1, ~> 3.4)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)"]
加点 ObjectMapper 非根节点
合并版本约束 payload
ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)"]
加点 pop 非根节点
合并版本约束 payload
pop (= 1.0.12, ~> 1.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)"]
加点 Reveal-SDK 非根节点
合并版本约束 payload
Reveal-SDK (from `Reveal`)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)"]
加点 SDWebImage 非根节点
合并版本约束 payload
SDWebImage (= 4.4.6, ~> 4.4.6)
加点 SDWebImage/Core 非根节点
加边 SDWebImage => SDWebImage/Core
requirement
合并版本约束 payload
SDWebImage/Core (= 4.4.6)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)"]
加点 SDWebImage/Core 非根节点
合并版本约束 payload
SDWebImage/Core (= 4.4.6)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)"]
加点 SnapKit 非根节点
合并版本约束 payload
SnapKit (= 5.0.0, ~> 5.0.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (= 5.0.0, ~> 5.0.0)", "SwifterSwift (~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)"]
加点 SwifterSwift 非根节点
合并版本约束 payload
SwifterSwift (= 5.0.0, ~> 5.0.0)
加点 SwifterSwift/AppKit 非根节点
加边 SwifterSwift => SwifterSwift/AppKit
requirement
合并版本约束 payload
SwifterSwift/AppKit (= 5.0.0)
加点 SwifterSwift/CoreGraphics 非根节点
加边 SwifterSwift => SwifterSwift/CoreGraphics
requirement
合并版本约束 payload
SwifterSwift/CoreGraphics (= 5.0.0)
加点 SwifterSwift/CoreLocation 非根节点
加边 SwifterSwift => SwifterSwift/CoreLocation
requirement
合并版本约束 payload
SwifterSwift/CoreLocation (= 5.0.0)
加点 SwifterSwift/Dispatch 非根节点
加边 SwifterSwift => SwifterSwift/Dispatch
requirement
合并版本约束 payload
SwifterSwift/Dispatch (= 5.0.0)
加点 SwifterSwift/Foundation 非根节点
加边 SwifterSwift => SwifterSwift/Foundation
requirement
合并版本约束 payload
SwifterSwift/Foundation (= 5.0.0)
加点 SwifterSwift/MapKit 非根节点
加边 SwifterSwift => SwifterSwift/MapKit
requirement
合并版本约束 payload
SwifterSwift/MapKit (= 5.0.0)
加点 SwifterSwift/SpriteKit 非根节点
加边 SwifterSwift => SwifterSwift/SpriteKit
requirement
合并版本约束 payload
SwifterSwift/SpriteKit (= 5.0.0)
加点 SwifterSwift/SwiftStdlib 非根节点
加边 SwifterSwift => SwifterSwift/SwiftStdlib
requirement
合并版本约束 payload
SwifterSwift/SwiftStdlib (= 5.0.0)
加点 SwifterSwift/UIKit 非根节点
加边 SwifterSwift => SwifterSwift/UIKit
requirement
合并版本约束 payload
SwifterSwift/UIKit (= 5.0.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (= 5.0.0, ~> 5.0.0)", "SwifterSwift (= 5.0.0, ~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)", "SwifterSwift/AppKit (= 5.0.0)", "SwifterSwift/CoreGraphics (= 5.0.0)", "SwifterSwift/CoreLocation (= 5.0.0)", "SwifterSwift/Dispatch (= 5.0.0)", "SwifterSwift/Foundation (= 5.0.0)", "SwifterSwift/MapKit (= 5.0.0)", "SwifterSwift/SpriteKit (= 5.0.0)", "SwifterSwift/SwiftStdlib (= 5.0.0)", "SwifterSwift/UIKit (= 5.0.0)"]
加点 SwifterSwift/AppKit 非根节点
合并版本约束 payload
SwifterSwift/AppKit (= 5.0.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (= 5.0.0, ~> 5.0.0)", "SwifterSwift (= 5.0.0, ~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)", "SwifterSwift/AppKit (= 5.0.0)", "SwifterSwift/CoreGraphics (= 5.0.0)", "SwifterSwift/CoreLocation (= 5.0.0)", "SwifterSwift/Dispatch (= 5.0.0)", "SwifterSwift/Foundation (= 5.0.0)", "SwifterSwift/MapKit (= 5.0.0)", "SwifterSwift/SpriteKit (= 5.0.0)", "SwifterSwift/SwiftStdlib (= 5.0.0)", "SwifterSwift/UIKit (= 5.0.0)"]
加点 SwifterSwift/CoreGraphics 非根节点
合并版本约束 payload
SwifterSwift/CoreGraphics (= 5.0.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (= 5.0.0, ~> 5.0.0)", "SwifterSwift (= 5.0.0, ~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)", "SwifterSwift/AppKit (= 5.0.0)", "SwifterSwift/CoreGraphics (= 5.0.0)", "SwifterSwift/CoreLocation (= 5.0.0)", "SwifterSwift/Dispatch (= 5.0.0)", "SwifterSwift/Foundation (= 5.0.0)", "SwifterSwift/MapKit (= 5.0.0)", "SwifterSwift/SpriteKit (= 5.0.0)", "SwifterSwift/SwiftStdlib (= 5.0.0)", "SwifterSwift/UIKit (= 5.0.0)"]
加点 SwifterSwift/CoreLocation 非根节点
合并版本约束 payload
SwifterSwift/CoreLocation (= 5.0.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (= 5.0.0, ~> 5.0.0)", "SwifterSwift (= 5.0.0, ~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)", "SwifterSwift/AppKit (= 5.0.0)", "SwifterSwift/CoreGraphics (= 5.0.0)", "SwifterSwift/CoreLocation (= 5.0.0)", "SwifterSwift/Dispatch (= 5.0.0)", "SwifterSwift/Foundation (= 5.0.0)", "SwifterSwift/MapKit (= 5.0.0)", "SwifterSwift/SpriteKit (= 5.0.0)", "SwifterSwift/SwiftStdlib (= 5.0.0)", "SwifterSwift/UIKit (= 5.0.0)"]
加点 SwifterSwift/Dispatch 非根节点
合并版本约束 payload
SwifterSwift/Dispatch (= 5.0.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (= 5.0.0, ~> 5.0.0)", "SwifterSwift (= 5.0.0, ~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)", "SwifterSwift/AppKit (= 5.0.0)", "SwifterSwift/CoreGraphics (= 5.0.0)", "SwifterSwift/CoreLocation (= 5.0.0)", "SwifterSwift/Dispatch (= 5.0.0)", "SwifterSwift/Foundation (= 5.0.0)", "SwifterSwift/MapKit (= 5.0.0)", "SwifterSwift/SpriteKit (= 5.0.0)", "SwifterSwift/SwiftStdlib (= 5.0.0)", "SwifterSwift/UIKit (= 5.0.0)"]
加点 SwifterSwift/Foundation 非根节点
合并版本约束 payload
SwifterSwift/Foundation (= 5.0.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (= 5.0.0, ~> 5.0.0)", "SwifterSwift (= 5.0.0, ~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)", "SwifterSwift/AppKit (= 5.0.0)", "SwifterSwift/CoreGraphics (= 5.0.0)", "SwifterSwift/CoreLocation (= 5.0.0)", "SwifterSwift/Dispatch (= 5.0.0)", "SwifterSwift/Foundation (= 5.0.0)", "SwifterSwift/MapKit (= 5.0.0)", "SwifterSwift/SpriteKit (= 5.0.0)", "SwifterSwift/SwiftStdlib (= 5.0.0)", "SwifterSwift/UIKit (= 5.0.0)"]
加点 SwifterSwift/MapKit 非根节点
合并版本约束 payload
SwifterSwift/MapKit (= 5.0.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (= 5.0.0, ~> 5.0.0)", "SwifterSwift (= 5.0.0, ~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)", "SwifterSwift/AppKit (= 5.0.0)", "SwifterSwift/CoreGraphics (= 5.0.0)", "SwifterSwift/CoreLocation (= 5.0.0)", "SwifterSwift/Dispatch (= 5.0.0)", "SwifterSwift/Foundation (= 5.0.0)", "SwifterSwift/MapKit (= 5.0.0)", "SwifterSwift/SpriteKit (= 5.0.0)", "SwifterSwift/SwiftStdlib (= 5.0.0)", "SwifterSwift/UIKit (= 5.0.0)"]
加点 SwifterSwift/SpriteKit 非根节点
合并版本约束 payload
SwifterSwift/SpriteKit (= 5.0.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (= 5.0.0, ~> 5.0.0)", "SwifterSwift (= 5.0.0, ~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)", "SwifterSwift/AppKit (= 5.0.0)", "SwifterSwift/CoreGraphics (= 5.0.0)", "SwifterSwift/CoreLocation (= 5.0.0)", "SwifterSwift/Dispatch (= 5.0.0)", "SwifterSwift/Foundation (= 5.0.0)", "SwifterSwift/MapKit (= 5.0.0)", "SwifterSwift/SpriteKit (= 5.0.0)", "SwifterSwift/SwiftStdlib (= 5.0.0)", "SwifterSwift/UIKit (= 5.0.0)"]
加点 SwifterSwift/SwiftStdlib 非根节点
合并版本约束 payload
SwifterSwift/SwiftStdlib (= 5.0.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (= 5.0.0, ~> 5.0.0)", "SwifterSwift (= 5.0.0, ~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)", "SwifterSwift/AppKit (= 5.0.0)", "SwifterSwift/CoreGraphics (= 5.0.0)", "SwifterSwift/CoreLocation (= 5.0.0)", "SwifterSwift/Dispatch (= 5.0.0)", "SwifterSwift/Foundation (= 5.0.0)", "SwifterSwift/MapKit (= 5.0.0)", "SwifterSwift/SpriteKit (= 5.0.0)", "SwifterSwift/SwiftStdlib (= 5.0.0)", "SwifterSwift/UIKit (= 5.0.0)"]
加点 SwifterSwift/UIKit 非根节点
合并版本约束 payload
SwifterSwift/UIKit (= 5.0.0)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (= 5.0.0, ~> 5.0.0)", "SwifterSwift (= 5.0.0, ~> 5.0.0)", "SwiftMessages (~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)", "SwifterSwift/AppKit (= 5.0.0)", "SwifterSwift/CoreGraphics (= 5.0.0)", "SwifterSwift/CoreLocation (= 5.0.0)", "SwifterSwift/Dispatch (= 5.0.0)", "SwifterSwift/Foundation (= 5.0.0)", "SwifterSwift/MapKit (= 5.0.0)", "SwifterSwift/SpriteKit (= 5.0.0)", "SwifterSwift/SwiftStdlib (= 5.0.0)", "SwifterSwift/UIKit (= 5.0.0)"]
加点 SwiftMessages 非根节点
合并版本约束 payload
SwiftMessages (= 6.0.2, ~> 6.0.2)
加点 SwiftMessages/App 非根节点
加边 SwiftMessages => SwiftMessages/App
requirement
合并版本约束 payload
SwiftMessages/App (= 6.0.2)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (= 5.0.0, ~> 5.0.0)", "SwifterSwift (= 5.0.0, ~> 5.0.0)", "SwiftMessages (= 6.0.2, ~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)", "SwifterSwift/AppKit (= 5.0.0)", "SwifterSwift/CoreGraphics (= 5.0.0)", "SwifterSwift/CoreLocation (= 5.0.0)", "SwifterSwift/Dispatch (= 5.0.0)", "SwifterSwift/Foundation (= 5.0.0)", "SwifterSwift/MapKit (= 5.0.0)", "SwifterSwift/SpriteKit (= 5.0.0)", "SwifterSwift/SwiftStdlib (= 5.0.0)", "SwifterSwift/UIKit (= 5.0.0)", "SwiftMessages/App (= 6.0.2)"]
加点 SwiftMessages/App 非根节点
合并版本约束 payload
SwiftMessages/App (= 6.0.2)
check dependency graph
["Alamofire (= 4.7.3, ~> 4.7, ~> 4.7.3)", "AlamofireObjectMapper (= 5.2.0, ~> 5.0)", "CocoaLumberjack/Swift (= 3.5.3)", "DeviceKit (= 2.0.0, ~> 2.0)", "ESTabBarController-swift (= 2.7)", "FLEX (= 2.4.0, ~> 2.0)", "IQKeyboardManagerSwift (= 6.2.1, ~> 6.2.0)", "KVOController (= 1.2.0)", "ObjectMapper (= 3.4.2, ~> 3.1, ~> 3.4)", "pop (= 1.0.12, ~> 1.0)", "Reveal-SDK (from `Reveal`)", "SDWebImage (= 4.4.6, ~> 4.4.6)", "SnapKit (= 5.0.0, ~> 5.0.0)", "SwifterSwift (= 5.0.0, ~> 5.0.0)", "SwiftMessages (= 6.0.2, ~> 6.0.2)", "SwiftyUserDefaults", "CocoaLumberjack/Core (= 3.5.3)", "SDWebImage/Core (= 4.4.6)", "SwifterSwift/AppKit (= 5.0.0)", "SwifterSwift/CoreGraphics (= 5.0.0)", "SwifterSwift/CoreLocation (= 5.0.0)", "SwifterSwift/Dispatch (= 5.0.0)", "SwifterSwift/Foundation (= 5.0.0)", "SwifterSwift/MapKit (= 5.0.0)", "SwifterSwift/SpriteKit (= 5.0.0)", "SwifterSwift/SwiftStdlib (= 5.0.0)", "SwifterSwift/UIKit (= 5.0.0)", "SwiftMessages/App (= 6.0.2)"]
加点 SwiftyUserDefaults 非根节点
合并版本约束 payload
SwiftyUserDefaults (= 4.0.0)
通过解析 Pods
字段,将我们的依赖图扩展成一个非连通图。其中,每个节点都会背有一个 payload
权值,后面我们称为依赖约束条件。另外在取出 Pods
字段的时候,会保留所有依赖的隐式依赖的依赖约束条件,在上文中的加边操作入口中的 add_child_vertex_to_graph
方法,内部有调用对于节点的 payload.merge
方法,它可以将多个约束合入到约束数组中,以便后续的依赖仲裁过程激活使用。
无 Podfile.lock 条件 - 无约束重新创建图
没有任何 Podfile.lock
参考,那么我们就重新去构建整张依赖图。
def self.unlocked_dependency_graph
Molinillo::DependencyGraph.new
end
我们可以尝试将 Podfile.lock
文件干掉,之后会发现没有上述的构建 Locking 图的过程,取而代之的是将一个空图作为返回值。
要点二:根据 Target 对 Podfile 中的描述来对 spec 进行分组
要点一中描述了 generate_version_locking_dependencies
中是如何初始化一个依赖图。我们虽然拿到了依赖图,但是在提交至 Molinillo 的 Resolver
之前还需要拿到更多关于依赖的描述。例如:Sandbox 沙盒信息、Podfile
的解析结果等等。
# file: lib/cocoapods/installer/analyzer.rb
# 通过 Target 对 Podfile 进行分组
# 由于某些依赖可能具有外部源,因此 Resolver 需要识别 {Sandbox} 并去下载他们的 podspec。解析器需要这些来分析依赖关系
# 增加、删除或者修改的需要在仲裁之前从 Sandbox 中删除
# 在 Update 中,resolve 会设置成始终更新外部源
#
def resolve_dependencies(locked_dependencies)
# 获取重复的依赖
duplicate_dependencies = podfile_dependencies.group_by(&:name).
select { |_name, dependencies| dependencies.count > 1 }
puts "duplicate_dependencies", duplicate_dependencies
# 如果有重复声明依赖,抛出警告
duplicate_dependencies.each do |name, dependencies|
UI.warn "There are duplicate dependencies on `#{name}` in #{UI.path podfile.defined_in_file}:\n\n" \
"- #{dependencies.map(&:to_s).join("\n- ")}"
end
resolver_specs_by_target = nil
# 进入 Resolving 的日志
UI.section "Resolving dependencies of #{UI.path(podfile.defined_in_file) || 'Podfile'}" do
# 传入 sandbox, podfile, locked_dependencies, sources 以及是否需要更新的标记,实例化 Resolver
resolver = Pod::Resolver.new(sandbox, podfile, locked_dependencies, sources, @specs_updated)
# 拿到 Molinillo 的仲裁结果
resolver_specs_by_target = resolver.resolve
# 取到 Molinillo 仲裁结果的 values,是一组 #<Pod::Resolver::ResolverSpecification> 对象
# puts "resolver_specs_by_target.values", resolver_specs_by_target.values.flatten(1)[0].spec[]
# 从所有的 #<Pod::Resolver::ResolverSpecification> 中取出
resolver_specs_by_target.values.flatten(1).map(&:spec).each(&:validate_cocoapods_version)
end
# 将处理后的 resolver_specs_by_target 返回
resolver_specs_by_target
end
以上代码通过环境中已经 Lock 住的 locked_dependencies
,将其放入 Pod::Resolver
做进一步处理的过程。locked_dependencies
就是我们已经在 generate_version_locking_dependencies
阶段根据 Podfile.lock
和 Podfile
确定的锁定 Pod,当然如果不存在 Podfile.lock
可能就是个空数组。接着我们用 sandbox
,podfile
,locked_dependencies
,@specs_updated
构造一个 Pod::Resolver
实例,所有的入参含义和实例内容我写在下面:
[1] pry(#<Pod::Resolver>)> sandbox - 沙盒目录,其实就是项目中的 Pods 目录
=> #<Pod::Sandbox> with root /Users/gua/Desktop/git/shawshank/Shawshank/Pods
[2] pry(#<Pod::Resolver>)> podfile - Pod::Podfile 实例:解析的 Podfile,并且已经根据 Target 进行依赖分组,还有很多 Podfile 中定制的 Hook 方法
=> #<Pod::Podfile:0x00007f8e923d72f0
@DEV_PATH="../../",
@SWIFT_4_2_PODS=[],
@current_target_definition=#<Pod::Podfile::TargetDefinition label=Pods>,
@defined_in_file=#<Pathname:/Users/gua/Desktop/git/shawshank/Shawshank/Podfile>,
@internal_hash={},
@post_install_callback=#<Proc:0x00007f8e923e9590@/Users/gua/Desktop/git/shawshank/Shawshank/Podfile:76>,
@root_target_definitions=[#<Pod::Podfile::TargetDefinition label=Pods>]>
[3] pry(#<Pod::Resolver>)> locked_dependencies - 上文分析的 Locked 依赖数组
=> Molinillo::DependencyGraph:[Molinillo::DependencyGraph::Vertex:Alamofire(<Pod::Dependency name=Alamofire requirements== 4.7.3, ~> 4.7, ~> 4.7.3 source=https://github.com/cocoapods/specs.git external_source=nil>), Molinillo::DependencyGraph::Vertex:AlamofireObjectMapper(<Pod::Dependency name=AlamofireObjectMapper requirements== 5.2.0, ~> 5.0 source=https://github.com/cocoapods/specs.git external_source=nil>), Molinillo::DependencyGraph::Vertex:FLEX(<Pod::Dependency name=FLEX requirements== 2.4.0, ~> 2.0 source=https://github.com/cocoapods/specs.git external_source=nil>), Molinillo::DependencyGraph::Vertex:GDMDebugger(<Pod::Dependency name=GDMDebugger requirements== 0.1.0 source=nil external_source={:branch=>"try_virtualized_tree", :git=>"git@github.com:Desgard/oversee.git"}>), Molinillo::DependencyGraph::Vertex:Hero(<Pod::Dependency name=Hero requirements== 1.4.0, ~> 1.4.0 source=https://github.com/cocoapods/specs.git external_source=nil>), Molinillo::DependencyGraph::Vertex:IQKeyboardManagerSwift(<Pod::Dependency name=IQKeyboardManagerSwift requirements== 6.2.1, ~> 6.2.0 source=https://github.com/cocoapods/specs.git external_source=nil>), Molinillo::DependencyGraph::Vertex:KVOController(<Pod::Dependency name=KVOController requirements== 1.2.0 source=https://github.com/cocoapods/specs.git external_source=nil>), Molinillo::DependencyGraph::Vertex:Nimble(<Pod::Dependency name=Nimble requirements== 8.0.1, ~> 8.0.0 source=https://github.com/cocoapods/specs.git external_source=nil>)}
[4] pry(#<Pod::Resolver>)> sources - Source 数组,其实是当前工程使用到的 Source 集合,且描述了在本地 Cache 中的 path,即 `$HOME/.cocoapods/repos/` 目录下的那些仓库
=> [#<Pod::MasterSource name:master type:file system>]
[5] pry(#<Pod::Resolver>)> specs_updated - 前文记录的需要 update 的 specs 集合
=> nil
在构建完 Pod::Resolver
实例后,调用其 resolve
方法就可以获取到根据 Target
分组的依赖解析结果。我们可以通过调试来查看这个 Molinillo 的仲裁结果 resolver_specs_by_target
。
# 通过 keys 获取到下标为 1 的 Shawshank 的 Target
[1] pry(#<Pod::Installer::Analyzer>)> ss_target = resolver_specs_by_target.keys[1]
=> #<Pod::Podfile::TargetDefinition label=Pods-Shawshank>
# 可以获取到对应 Target 下所有 Pod 的解析结果
[2] pry(#<Pod::Installer::Analyzer>)> resolver_specs_by_target[ss_target]
=> [#<Pod::Resolver::ResolverSpecification:0x00007fec97262a10
@source=#<Pod::MasterSource name:master type:file system>,
@spec=#<Pod::Specification name="Alamofire">,
@used_by_tests_only=false>,
#<Pod::Resolver::ResolverSpecification:0x00007fec97262920
@source=#<Pod::MasterSource name:master type:file system>,
@spec=#<Pod::Specification name="AlamofireObjectMapper">,
@used_by_tests_only=false>,
#<Pod::Resolver::ResolverSpecification:0x00007fec97262808
@source=#<Pod::MasterSource name:master type:file system>,
@spec=#<Pod::Specification name="FLEX">,
...
# 取出 Alamofire 的解析结果对应的 Spec 实例
[3] pry(#<Pod::Installer::Analyzer>)> resolver_specs_by_target[ss_target][0]
=> #<Pod::Resolver::ResolverSpecification:0x00007fec97262a10
@source=#<Pod::MasterSource name:master type:file system>,
@spec=#<Pod::Specification name="Alamofire">,
# 例如我们可以查看其最终的决议版本
[4] pry(#<Pod::Installer::Analyzer>)> resolver_specs_by_target[ss_target][0].spec.version
=> Gem::Version.new("4.7.3")
另外提一句,validate_cocoapods_version
是针对于所有的 Spec 进行一次 CocoaPods 版本的校验,如果 CocoaPods 版本有误,则直接抛出异常。
要点三:返回 AnalysisResult 实例,得到仲裁结果
在使用 resolver_dependencies
得到 resolver_specs_by_target
之后,其实所有的仲裁过程已经结束。但是 Pod 会做一些对于 Spec 的校验和分类变换,从而得到最终的 Pod::Installer::Analyzer::AnalysisResult
。我把 analyze
方法在这个阶段的代码拿到下面,并且把其中的方法和变换数组一一拿出来分析一下:
# 校验 platform 合法性
validate_platforms(resolver_specs_by_target)
# Spec 对实例数组
specifications = generate_specifications(resolver_specs_by_target)
# 原工程中对 Targets 实例数组
targets = generate_targets(resolver_specs_by_target, target_inspections)
# Pod Project 的 Targets 实例数组
pod_targets = calculate_pod_targets(targets)
# Sandbox 中 Spec 数组,可理解成与 Manifest 状态同步
sandbox_state = generate_sandbox_state(specifications)
# 根据 target 对 Spec 做分组操作
specs_by_target = resolver_specs_by_target.each_with_object({}) do |rspecs_by_target, hash|
hash[rspecs_by_target[0]] = rspecs_by_target[1].map(&:spec)
end
# 根据 sources 对 Spec 做分组操作
specs_by_source = Hash[resolver_specs_by_target.values.flatten(1).group_by(&:source).map do |source, specs|
[source, specs.map(&:spec).uniq]
end]
sources.each { |s| specs_by_source[s] ||= [] }
# 要点三:返回一个 AnalysisResult 实例,得到最终的仲裁结果
@result = AnalysisResult.new(podfile_state, specs_by_target, specs_by_source, specifications, sandbox_state,
targets, pod_targets, @podfile_dependency_cache)
下面是每个操作的示例输出 Log 以及一些类型解析:
# 打平的 spec 实例
[3] pry(#<Pod::Installer::Analyzer>)> specifications
=> [#<Pod::Specification name="Alamofire">,
#<Pod::Specification name="AlamofireObjectMapper">,
#<Pod::Specification name="FLEX">,
#<Pod::Specification name="FMDB">,
#<Pod::Specification name="FMDB/standard">,
#<Pod::Specification name="GCDWebServer">,
#<Pod::Specification name="GCDWebServer/Core">,
#<Pod::Specification name="GDMDebugger">,
#<Pod::Specification name="Hero">,
#<Pod::Specification name="IQKeyboardManagerSwift">,
#<Pod::Specification name="KVOController">,
#<Pod::Specification name="ObjectMapper">,
#<Pod::Specification name="Reveal-SDK">,
#<Pod::Specification name="SQLiteRepairKit">,
#<Pod::Specification name="SSZipArchive">,
#<Pod::Specification name="SnapKit">,
#<Pod::Specification name="SwiftMessages">,
#<Pod::Specification name="SwiftMessages/App">,
#<Pod::Specification name="SwifterSwift">,
#<Pod::Specification name="SwifterSwift/AppKit">,
#<Pod::Specification name="SwifterSwift/CoreGraphics">,
#<Pod::Specification name="SwifterSwift/CoreLocation">,
#<Pod::Specification name="SwifterSwift/Dispatch">,
#<Pod::Specification name="SwifterSwift/Foundation">,
#<Pod::Specification name="SwifterSwift/MapKit">,
#<Pod::Specification name="SwifterSwift/SpriteKit">,
#<Pod::Specification name="SwifterSwift/SwiftStdlib">,
#<Pod::Specification name="SwifterSwift/UIKit">,
#<Pod::Specification name="SwiftyUserDefaults">,
#<Pod::Specification name="WCDB.swift">,
#<Pod::Specification name="WCDBOptimizedSQLCipher">,
#<Pod::Specification name="pop">,
#<Pod::Specification name="Nimble">]
# 打平的 targets 数组
[5] pry(#<Pod::Installer::Analyzer>)> targets
=> [<Pod::AggregateTarget name=Pods-Shawshank >, <Pod::AggregateTarget name=Pods-ShawshankTunnel >, <Pod::AggregateTarget name=Pods-ShawshankTests >]
# 所有 Pod Targets 实例数组
[6] pry(#<Pod::Installer::Analyzer>)> pod_targets
=> [<Pod::PodTarget name=Alamofire >,
<Pod::PodTarget name=AlamofireObjectMapper >,
<Pod::PodTarget name=FLEX >,
<Pod::PodTarget name=FMDB >,
<Pod::PodTarget name=GCDWebServer >,
<Pod::PodTarget name=GDMDebugger >,
<Pod::PodTarget name=Hero >,
<Pod::PodTarget name=IQKeyboardManagerSwift >,
<Pod::PodTarget name=KVOController >,
<Pod::PodTarget name=ObjectMapper >,
<Pod::PodTarget name=Reveal-SDK >,
<Pod::PodTarget name=SQLiteRepairKit >,
<Pod::PodTarget name=SSZipArchive >,
<Pod::PodTarget name=SnapKit >,
<Pod::PodTarget name=SwiftMessages >,
<Pod::PodTarget name=SwifterSwift >,
<Pod::PodTarget name=SwiftyUserDefaults >,
<Pod::PodTarget name=WCDB.swift >,
<Pod::PodTarget name=WCDBOptimizedSQLCipher >,
<Pod::PodTarget name=pop >,
<Pod::PodTarget name=Nimble >]
# 沙盒 sandbox state
[7] pry(#<Pod::Installer::Analyzer>)> sandbox_state
=> #<Pod::Installer::Analyzer::SpecsState:0x00007ff0011b31b0
@added=#<Set: {}>,
@changed=#<Set: {}>,
@deleted=#<Set: {}>,
@unchanged=
#<Set: {"Alamofire",
"AlamofireObjectMapper",
"FLEX",
"FMDB",
"GCDWebServer",
"GDMDebugger",
"Hero",
"IQKeyboardManagerSwift",
"KVOController",
"Nimble",
"ObjectMapper",
"Reveal-SDK",
"SQLiteRepairKit",
"SSZipArchive",
"SnapKit",
"SwiftMessages",
"SwifterSwift",
"SwiftyUserDefaults",
"WCDB.swift",
"WCDBOptimizedSQLCipher",
"pop"}>>
# 根据 Target 对 Spec 做分类
[9] pry(#<Pod::Installer::Analyzer>)> specs_by_target
=> {#<Pod::Podfile::TargetDefinition label=Pods>=>[],
#<Pod::Podfile::TargetDefinition label=Pods-Shawshank>=>
[#<Pod::Specification name="Alamofire">,
#<Pod::Specification name="AlamofireObjectMapper">,
#<Pod::Specification name="FLEX">,
#<Pod::Specification name="FMDB">,
#<Pod::Specification name="FMDB/standard">,
#<Pod::Specification name="GCDWebServer">,
#<Pod::Specification name="GCDWebServer/Core">,
#<Pod::Specification name="GDMDebugger">,
#<Pod::Specification name="Hero">,
#<Pod::Specification name="IQKeyboardManagerSwift">,
#<Pod::Specification name="KVOController">,
#<Pod::Specification name="ObjectMapper">,
#<Pod::Specification name="Reveal-SDK">,
#<Pod::Specification name="SQLiteRepairKit">,
#<Pod::Specification name="SSZipArchive">,
#<Pod::Specification name="SnapKit">,
#<Pod::Specification name="SwiftMessages">,
#<Pod::Specification name="SwiftMessages/App">,
#<Pod::Specification name="SwifterSwift">,
#<Pod::Specification name="SwifterSwift/AppKit">,
#<Pod::Specification name="SwifterSwift/CoreGraphics">,
#<Pod::Specification name="SwifterSwift/CoreLocation">,
#<Pod::Specification name="SwifterSwift/Dispatch">,
#<Pod::Specification name="SwifterSwift/Foundation">,
#<Pod::Specification name="SwifterSwift/MapKit">,
#<Pod::Specification name="SwifterSwift/SpriteKit">,
#<Pod::Specification name="SwifterSwift/SwiftStdlib">,
#<Pod::Specification name="SwifterSwift/UIKit">,
#<Pod::Specification name="SwiftyUserDefaults">,
#<Pod::Specification name="WCDB.swift">,
#<Pod::Specification name="WCDBOptimizedSQLCipher">,
#<Pod::Specification name="pop">],
#<Pod::Podfile::TargetDefinition label=Pods-ShawshankTunnel>=>[],
#<Pod::Podfile::TargetDefinition label=Pods-ShawshankTests>=>[#<Pod::Specification name="Nimble">]}
# 根据 Source 对 Spec 进行分类
[10] pry(#<Pod::Installer::Analyzer>)> specs_by_source
=> {#<Pod::MasterSource name:master type:file system>=>
[#<Pod::Specification name="Alamofire">,
#<Pod::Specification name="AlamofireObjectMapper">,
#<Pod::Specification name="FLEX">,
#<Pod::Specification name="FMDB">,
#<Pod::Specification name="FMDB/standard">,
#<Pod::Specification name="GCDWebServer">,
#<Pod::Specification name="GCDWebServer/Core">,
#<Pod::Specification name="Hero">,
#<Pod::Specification name="IQKeyboardManagerSwift">,
#<Pod::Specification name="KVOController">,
#<Pod::Specification name="ObjectMapper">,
#<Pod::Specification name="SQLiteRepairKit">,
#<Pod::Specification name="SSZipArchive">,
#<Pod::Specification name="SnapKit">,
#<Pod::Specification name="SwiftMessages">,
#<Pod::Specification name="SwiftMessages/App">,
#<Pod::Specification name="SwifterSwift">,
#<Pod::Specification name="SwifterSwift/AppKit">,
#<Pod::Specification name="SwifterSwift/CoreGraphics">,
#<Pod::Specification name="SwifterSwift/CoreLocation">,
#<Pod::Specification name="SwifterSwift/Dispatch">,
#<Pod::Specification name="SwifterSwift/Foundation">,
#<Pod::Specification name="SwifterSwift/MapKit">,
#<Pod::Specification name="SwifterSwift/SpriteKit">,
#<Pod::Specification name="SwifterSwift/SwiftStdlib">,
#<Pod::Specification name="SwifterSwift/UIKit">,
#<Pod::Specification name="SwiftyUserDefaults">,
#<Pod::Specification name="WCDB.swift">,
#<Pod::Specification name="WCDBOptimizedSQLCipher">,
#<Pod::Specification name="pop">,
#<Pod::Specification name="Nimble">],
false=>[#<Pod::Specification name="GDMDebugger">, #<Pod::Specification name="Reveal-SDK">]}
我们拿到了 podfile_state
,specs_by_target
,specs_by_source
,specifications
,sandbox_state
,targets
,pod_targets
这六个根据 resolver
变换的数组后,就可以实例化 AnalysisResult
对象。这个对象也就是 analysis
对外暴露的最终结果,也是 CocoaPods 在 Install 后面过程中依赖的解析信息结果。
过程总结
在 Resolver
解析初始态的 Analysis 过程中,CocoaPods 通过对 Podfile
和 Podfile.lock
的解析,提取出锁定的 Pods,并将其与 Podfile
实例信息共同交予 Molinillo 算法分析,并反馈一个根据 Target 分类的 Spec Hash。之后通过各种的函数式变换,实例化出 AnalysisResult
对象,供后续过程使用。
检测是否有无效的 Configurations
在完成了依赖仲裁,后面只需要做一些收尾工作。CocoaPods 在这个流程中首先会检测 Podfile
中的 Configurations 是否有效。
def validate_build_configurations
# Podfile 中解析出需要将 Pod 安装的所有 Configuration
whitelisted_configs = pod_targets.
flat_map(&:target_definitions).
flat_map(&:all_whitelisted_configurations).
map(&:downcase).
uniq
# 工程中所有 Configurations
all_user_configurations = analysis_result.all_user_build_configurations.keys.map(&:downcase)
# 声明集合与工程解析集合取差集,正常情况下声明集合应该是工程解析集合的子集,所以差集应该为空
remainder = whitelisted_configs - all_user_configurations
# 不为空则说明 Podfile 中声明了非法的 Configuration,抛出异常
unless remainder.empty?
raise Informative,
"Unknown #{'configuration'.pluralize(remainder.size)} whitelisted: #{remainder.sort.to_sentence}. " \
"CocoaPods found #{all_user_configurations.sort.to_sentence}, did you mean one of these?"
end
end
清理 Sandbox 沙盒目录
依赖解析已经获得了仲裁结果 analysis_result
,后续我们需要为 Sandbox 沙盒目录(Pods/
)创造初始化环境。
def clean_sandbox
# 删除 public header 目录
sandbox.public_headers.implode!
# 找到所有 target 的 Target Support Files 目录
target_support_dirs = sandbox.target_support_files_root.children.select(&:directory?)
# 遍历所有的 pod_targets
pod_targets.each do |pod_target|
# 清理对应的 build headers
pod_target.build_headers.implode!
# 删除对应的 support files 目录
target_support_dirs.delete(pod_target.support_files_dir)
end
# 遍历所有的 target
aggregate_targets.each do |aggregate_target|
# 删除对应的 support files 目录
target_support_dirs.delete(aggregate_target.support_files_dir)
end
# 将这些 target support 根目录也一同干掉
target_support_dirs.each { |dir| FileUtils.rm_rf(dir) }
# 清理已经删除的 Pod 的沙盒目录
unless sandbox_state.deleted.empty?
# 输出 log 格式化
title_options = { :verbose_prefix => '-> '.red }
# 根据 pod_name 匹配,清理沙盒
sandbox_state.deleted.each do |pod_name|
UI.titled_section("Removing #{pod_name}".red, title_options) do
sandbox.clean_pod(pod_name)
end
end
end
end
deployment
模式下的一些强校验
另外 Resolver
阶段,由于我们已经完成了依赖版本仲裁,所以我们已经获取到了所有的版本信息,这样在 deployment
模式下就可以对 Podfile.lock
文件做强校验。
def verify_no_podfile_changes!
# 如果没有 podfile_needs_install 标记则跳过
# 用来比对 podfile 和 lock 的版本差异
return unless analysis_result.podfile_needs_install?
changed_state = analysis_result.podfile_state.to_s(:states => %i(added deleted changed))
raise Informative, "There were changes to the podfile in deployment mode:\n#{changed_state}"
end
def verify_no_lockfile_changes!
# 获取到新生成的 Lockfile
new_lockfile = generate_lockfile
return if new_lockfile == lockfile
# 如果不同的话则不满足 deplayment 标志,Log 中输出 Diff 差异
diff = Xcodeproj::Differ.hash_diff(lockfile.to_hash, new_lockfile.to_hash, :key_1 => 'Old Lockfile', :key_2 => 'New Lockfile')
pretty_diff = YAMLHelper.convert_hash(diff, Lockfile::HASH_KEY_ORDER, "\n\n")
pretty_diff.gsub!(':diff:', 'diff:'.yellow)
raise Informative, "There were changes to the lockfile in deployment mode:\n#{pretty_diff}"
end
拓展思考
看了这部分的代码,我们有什么启发?或者说有什么比较好的想法呢?这里结合业务总结了一下几个技巧。
1. 快速生成 Lockfile 文件
在大型项目中,一个 Pod 可能会有上百个,在一次完整的 pod install
最少需要数分钟以上。有时候我们在 merge 代码时,在 Podfile
和 Podfile.lock
解完冲突后,为了确保 Podfile.lock
无误,会再次进行一次 pod install
生成一次 Podfile.lock
。
但其实,Resolve
过程已经分析出了一个 Podfile.lock
中的所有待记录的内容,我们拿到 analysis
其实就可以直接写文件了。而一次 pod install
更多的时间是在 download
阶段,但是这种场景下,我们只需一个份更新后的 Podfile.lock
文件即可。根据以上需求以及对 resolve
阶段的了解,可以制作一个 pod lock
的命令来快速生成 Podfile.lock
文件。
module Pod
class Command
class Lock < Command
include RepoUpdate
include ProjectDirectory
self.summary = "Generate the `Podfile.lock` files not to install."
self.description = self.summary
def self.options
[
['--repo-update', 'Force running `pod repo update` before generate the Podfile.lock'],
].concat(super).reject { |(name, _)| name == '--no-repo-update' }
end
def initialize(argv)
super
end
def run
Dir.chdir(config.installation_root) do
verify_podfile_exists!
installer = installer_for_config
installer.repo_update = !@repo_update.nil?
installer.update = false
installer.quick_generate_lockfile!
end
end
end
end
# Add some function to Installer
class Installer
def quick_generate_lockfile!
# 初始化 sandbox 环境
quick_prepare_env
# 完成 resolve 的仲裁,拿到 analysis
resolve_dependencies
# 直接执行写 lock 到本地文件操作
quick_write_lockfiles
end
def quick_prepare_env
if Dir.pwd.start_with?(sandbox.root.to_path)
message = 'Command should be run from a directory outside Pods directory.'
message << "\n\n\tCurrent directory is #{UI.path(Pathname.pwd)}\n"
raise Informative, message
end
UI.message 'Prepareing' do
# 检测沙盒版本
deintegrate_if_different_major_version
sandbox.quick_prepare
ensure_plugins_are_installed!
run_plugins_pre_install_hooks
end
end
# Writes the Podfile lock file, and to except for sandbox `Manifest.lock`
#
# @return [void]
#
def quick_write_lockfiles
@lockfile = generate_lockfile
UI.message "- Quickly Writing Lockfile in #{UI.path config.lockfile_path}" do
@lockfile.write_to_disk(config.lockfile_path)
end
end
end
class Sandbox
def quick_prepare
FileUtils.mkdir_p(headers_root)
FileUtils.mkdir_p(sources_root)
FileUtils.mkdir_p(specifications_root)
FileUtils.mkdir_p(target_support_files_root)
end
end
end
如此,执行 pod lock
就可以迅速完成 Podfile.lock
的写入,并且其依赖仲裁的全过程也不会丢失。这个命令除了在本地开发可以迅速生成 Lock 文件以外,在抖音 iOS 的研发流程中,还会在 CI 过程最后的集成阶段用以同步 Podfile.lock
为最新态,这样也解决了研发同学在提交 MR 的时候,不明确是否要提交 Podfile.lock
的问题。
2. 调试 @activated
仲裁结果数组
在排查 Pod 依赖的激活版本时,我们需要查明 Pod 各个版本的递归结果次序,通常方法是我们讲 MOLINILLO_DEBUG
赋有效值,激活其 Debug 模式打印出更多的 Log。
export MOLINILLO_DEBUG=1
但是 Molinillo
仓库的 Debug Log 实在是反人类:
Resolving dependencies of `Podfile`
: 0: Starting resolution (2019-07-10 19:08:53 +0800)
: 0: User-requested dependencies: [<Pod::Dependency name=pop requirements=~> 1.0 source=nil external_source=nil>, <Pod::Dependency name=KVOController requirements=>= 0 source=nil external_source=nil>, <Pod::Dependency name=IQKeyboardManagerSwift requirements=~> 6.2.0 source=nil external_source=nil>, <Pod::Dependency name=SwiftMessages requirements=~> 6.0.2 source=nil external_source=nil>, <Pod::Dependency name=ESTabBarController-swift requirements=>= 0 source=nil external_source=nil>, <Pod::Dependency name=DeviceKit requirements=~> 2.0 source=nil external_source=nil>, <Pod::Dependency name=SwifterSwift requirements=~> 5.0.0 source=nil external_source=nil>, <Pod::Dependency name=Alamofire requirements=~> 4.7.3 source=nil external_source=nil>, <Pod::Dependency name=ObjectMapper requirements=~> 3.1 source=nil external_source=nil>, <Pod::Dependency name=AlamofireObjectMapper requirements=~> 5.0 source=nil external_source=nil>, <Pod::Dependency name=SDWebImage requirements=~> 4.4.6 source=nil external_source=nil>, <Pod::Dependency name=SnapKit requirements=~> 5.0.0 source=nil external_source=nil>, <Pod::Dependency name=CocoaLumberjack/Swift requirements=>= 0 source=nil external_source=nil>, <Pod::Dependency name=SwiftyUserDefaults requirements=>= 0 source=nil external_source=nil>, <Pod::Dependency name=Reveal-SDK requirements=>= 0 source=nil external_source={:path=>"Reveal"}>, <Pod::Dependency name=FLEX requirements=~> 2.0 source=nil external_source=nil>]
: 0: Creating possibility state for Reveal-SDK (from `Reveal`) (1 remaining)
: 1: Attempting to activate [Reveal-SDK (4)]
: 1: Activated Reveal-SDK at [Reveal-SDK (4)]
: 1: Requiring nested dependencies ()
: 1: Creating possibility state for KVOController (1 remaining)
: 2: Attempting to activate [KVOController (1.2.0)]
: 2: Activated KVOController at [KVOController (1.2.0)]
: 2: Requiring nested dependencies ()
: 2: Creating possibility state for IQKeyboardManagerSwift (~> 6.2.0) (1 remaining)
: 3: Attempting to activate [IQKeyboardManagerSwift (6.2.1)]
: 3: Activated IQKeyboardManagerSwift at [IQKeyboardManagerSwift (6.2.1)]
: 3: Requiring nested dependencies ()
: 3: Creating possibility state for SwiftMessages (~> 6.0.2) (1 remaining)
: 4: Attempting to activate [SwiftMessages (6.0.2)]
: 4: Activated SwiftMessages at [SwiftMessages (6.0.2)]
: 4: Requiring nested dependencies (SwiftMessages/App (= 6.0.2))
: 4: Creating possibility state for SwiftMessages/App (= 6.0.2) (1 remaining)
: 5: Attempting to activate [SwiftMessages/App (6.0.2)]
: 5: Activated SwiftMessages/App at [SwiftMessages/App (6.0.2)]
...
这时候我们通过 pry
的 binding
方式,在 lib/cocoapods/resolver.rb
中插入断点,在终端中输出 self
即可看到解析结果以及所有激活的 Pod。对于需要查看现象的我们来说,只需要结果就好了~
总结
了解 Resolve
过程可以理清 CocoaPods 在依赖解析的入口和解析完毕之后的具体流程。掌握这一部分的细节可以使得组件版本处理的过程更加清晰,方便排查 CocoaPods 在解析依赖前后的环境问题以及在传入给 Molinillo 的入参是否有误。
后续会具体深入到 Molinillo 图算法中去认识仲裁流程,尽请期待~
