desgard.com

CocoaPods 历险 - Resolver 仲裁入口解析

回顾

在 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

SpecsState

在二次 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 图算法中去认识仲裁流程,尽请期待~