7. Molinillo 依赖校验

Molinillo是CocoaPods和Bundler依赖管理工具中的核心组件,用于处理和解决依赖冲突和循环依赖。本文详细介绍了Molinillo的依赖校验过程,包括ResolutionState、DependencyGraph、UnwindForConflict等关键概念,以及如何通过回溯和数据结构来解决版本冲突和循环依赖问题。此外,还讨论了SpecificationProvider的角色,确保了Molinillo的通用性和灵活性。
摘要由CSDN通过智能技术生成

d8dd192398f295c8b03a8c9dd36e56a1.png

引子

通过「PodSpec 管理策略」对 CocaPods-Core 的分析,我们大体了解了 Pod 是如何被解析、查询与管理的。有了这些整体概念之后,我们就可以逐步深入 pod install 的各个细节。

今天我们就来聊聊 Pod 的依赖校验工具 --- Molinillo[1]

开始前,需要聊聊依赖校验的背景。

依赖管理的挑战

同大多数包管理工具一样 Pod 会将传递依赖的包用扁平化的形式,安装至 workspace 目录 (即:Pods/)。

依赖传递pod A 依赖于 pod B,而 pod B 依赖 Alamofire

3ddaddb3a147157de73b067fb7af4342.png

可以看到,经依赖解析原有的依赖树被拍平了,安装在同层目录中。

然而在大型项目中,遇到的更多情况可能像下面这样:

656658a907f82111fc23d33c8c6e98e2.png

依赖冲突:pod Apod B 分别依赖不同版本的 Alamofire。这就是 依赖地狱[2] 的开始。

依赖地狱:指在操作系统中由于软件之间的依赖性不能被满足而引发的问题。

随着项目的迭代,我们不断引入依赖并最终形成错综复杂的网络。这使得项目的依赖性解析变得异常困难,甚至出现 致命错误[3]

那么,产生的问题有哪些类型 ?

问题类型

依赖过多/多重依赖

即项目存在大量依赖关系,或者依赖本身有其自身依赖(依赖传递),导致依赖层级过深。像微信或淘宝这样的超级应用,其中的单一业务模块都可能存在这些问题,这将使得依赖解析过于复杂,且容易产生依赖冲突和依赖循环。

依赖冲突

即项目中的两个依赖包无法共存的情况。可能两个依赖库内部的代码冲突,也可能其底层依赖互相冲突。上面例子中因 Alamofire 版本不同产生的问题就是依赖冲突。

依赖循环

即依赖性关系形成一个闭合环路。如下图三个 pod 库之间互相依赖产生循环:

22c9fc93bafd55c4b8d49dd42af1c88b.png

要判断依赖关系中是否存在依赖环,则需要通依赖仲裁算法来解决。

依赖关系的解决

对于依赖过多或者多重依赖问题,我们可通过合理的架构和设计模式来解决。而依赖校验主要解决的问题为:

  1. 检查依赖图是否存在版本冲突;

  2. 判断依赖图是否存在循环依赖;

版本冲突的解决方案

对于版本冲突可通过修改指定版本为带兼容性的版本范围问题来避免。如上面的问题有两个解决方案:

  • 通过修改两个 podAlamofire 版本约束为 ~> 4.0 来解决。

  • 去除两个 pod 的版本约束,交由项目中的 Podfile 来指定。

不过这样会有一个隐患,由于两个 Pod 使用的主版本不同,可能带来 API 不兼容,导致 pod install 即使成功了,最终也无法编译或运行时报错。

还有一种解决方案,是基于语言特性来进行依赖性隔离。如 npm 的每个传递依赖包如果冲突都可以有自己的 node_modules 依赖目录,即一个依赖库可以存在多个不同版本。

77fd1a46d75afe2c15a10d63a1a3b708.png

循环依赖的解决方案

循环依赖则需要需要进行数学建模生成 DAG 图,利用拓扑排序的拆点进行处理。通过确定依赖图是否为 DAG 图,来验证依赖关系的合理性。

一个 DAG 图的示例:

da683ec3646b98d49a38746d4b22125c.png

DAG 是图论中常见的一种描述问题的结构,全称 有向无环图 (Directed Acyclic Graph)。想了解更多,可查看冬瓜的文章 --- 「从拓扑排序到 Carthage 依赖校验算法」

另外,各种包管理工具的依赖校验算法也各不相同,有如 Dart 和 SwiftPM 所使用的 PubGrub[4],作者号称其为下一代依赖校验算法,Yarn 的 Selective dependency resolutions[5],还有我们今天聊到的 Molinillo。

Molinillo

Molinillo 作为通用的依赖解析工具,它不仅应用在 CocoaPods 中,在 Bundler 1.9 版本也采用 Molinillo。另外,值得注意的是 Bundler 在 Ruby 2.6 中被作为了默认的 Gem 工具内嵌。可以说 Ruby 相关的依赖工具都通过 Molinillo 完成依赖解析。

ResolutionState

Molinillo 算法的核心是基于回溯 (Backtracking)[6] 和 [向前检查 (forward checking)](https://en.wikipedia.org/wiki/Look-ahead_(backtracking "向前检查 (forward checking)")),整个过程会追踪栈中的两个状态 DependencyStatePossibilityState

module Molinillo
  # 解析状态
  ResolutionState = Struct.new(
    # [String] 当前需求名称
    :name,
    # [Array<Object>] 未处理的需求
    :requirements,
    # [DependencyGraph] 依赖关系图
    :activated,
    # [Object] 当前需求
    :requirement,
    # [Object] 满足当前需求的可能性
    :possibilities,
    # [Integer] 解析深度
    :depth,
    # [Hash] 未解决的冲突,以需求名为 key
    :conflicts,
    # [Array<UnwindDetails>] 记录着未处理过的需要用于回溯的信息
    :unused_unwind_options
  )

  class ResolutionState
    def self.empty
      new(nil, [], DependencyGraph.new, nil, nil, 0, {}, [])
    end
  end

  # 记录一组需求和满足当前需求的可能性
  class DependencyState < ResolutionState
  # 通过不断 pop 过滤包含的可能性,找出最符合需求的解
    def pop_possibility_state
      PossibilityState.new(
        name,
        requirements.dup,
        activated,
        requirement,
        [possibilities.pop],
        depth + 1,
        conflicts.dup,
        unused_unwind_options.dup
      ).tap do |state|
        state.activated.tag(state)
      end
    end
  end

  # 仅包含一个满足需求的可能性
  class PossibilityState < ResolutionState
  end
end

光看 state 定义大家可能觉得云里雾里。这里很有必要解释一下:

我们说的需求 (requirement) 到底是指什么呢?大家可以理解为在 Podfile 中声明的 pod。之所以称为需求,是由于无法判断定义的 dependency 是否合法。 假设它合法,又是否存在符合需求限制版本的解呢 ?即是否存在对应的 PodSpec 我们不而知。因此,这些未知状态称为统一被可能性 possibility

Tips: 了解这个概念非常重要,这也是笔者在几乎写完本文的情况下,才想明白这些变量名的意义。💔

Resolution Loop

我们先通过图来了解一下 Molinillo 的核心流程 (先忽略异常流):

e2c4a7090d72a21603922967a6a97240.png

可以看到整个流程就是不断的将 requirement 的 possibility 过滤和处理,一层层剥离转换为 DependencyState,如此循环往复。

Molinillo 的入口为 Resolution::resolve 方法,也是上图对应的实现,逻辑如下:

# lib/molinillo/resolution.rb

def resolve
  # 1. 初始化 timer 统计耗时初始位置打点
  # 2. 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值