名校课程推荐 | MIT《CS 实用工具课程》-包管理和依赖管理
包管理和依赖管理
软件通常构建在另一个(一系列)软件之上,这就需要依赖管理。
包/依赖管理程序是针对特定语言的,但很多都有相同的逻辑。
包存储库
包存储在包存储库中。不同语言有不同的包存储库(有时同一种语言会有多个存储库),比PyPI包含了Python库,RubyGems包含了Ruby库,Rust的crates.io等等。它们通常存储一个包的所有版本的软件(源代码,有时还包括特定平台的预编译的二进制文件)。
语义化版本
软件会随着时间推移更新,我们需要一种方法来指向软件版本。简单的方法是通过序列号或提交哈希来引用软件,但我们可以使用版本号更好地传递更多信息。
有很多方法,较流行的一种方法是语义化版本:
x.y.z
^ ^ ^
| | +- patch
| +--- minor
+----- major
当你做了不兼容的API修改,递增主版本号
当你做了向下兼容的功能性新增,递增次版本号
当你做了向下兼容的错误修复,递增补丁号
例如,如果你依赖某软件v1.2.0
版本引进的一个特性,那么你可以对任何x >= 2
的次版本号及y
补丁号安装v1.x.y
版本。你需要安装1
主版本号(因为2
可能会引起向下不兼容的修改),并且你需要安装>= 2
的次版本号(因为依赖次版本号引入的特性)。你可以使用任何较新的次版本号或补丁号,因为它们不会引入任何向下不兼容的修改。
锁文件
除了指定版本外,最好强制依赖项的内容没有修改,以防止篡改。有些工具使用锁文件(lock files)来指定包安装时要检查的依赖项(以及版本)的加密哈希。
指定版本
工具通常允许你以多种方式指定版本,例如:
确切的版本号,比如
2.3.12
最小的主版本号,比如
>= 2
具体的主版本号及最小的补丁号,比如
>= 2.3,<3.0
指定一个确切的版本有利于避免基于已安装依赖项的不同行为(如果所有依赖项都忠实遵循语义化版本号,就不应该会发生这种情况,但人们有时会犯错)。指定最低需求的好处是允许安装错误修复(比如补丁升级)。
依赖解析
包管理器使用各种依赖关系解析算法来满足依赖需求。对于复杂的依赖关系,这常常是一个挑战(例如,一个包可能被多个顶级依赖关系间接依赖,并且可能需要不同的版本)。不同的包管理器在依赖关系解析方面有不同的复杂程度,但是需要注意:如果你正在调试依赖关系,你可能需要理解这一点。
虚拟环境
如果你正在开发多个软件项目,它们可能依赖于某个特定软件的不同版本。有时候,你的构建工具会很自然地处理这个问题(例如,通过构建一个静态二进制文件)。
对于其他构建工具和编程语言来说,一种方法是使用虚拟环境来处理这个问题(例如Python的 virtualenv 工具)。你可以在虚拟环境中为每个项目安装依赖项,而不是在系统范围内安装,然后在处理特定项目的时候激活想要使用的虚拟环境。
Vendoring
另一种非常不同的依赖管理方法是vendoring。不必使用依赖管理器或构建工具来获取软件,而是把依赖中的所有源代码拷贝到你的软件存储库中。这样做的好处是你总是针对依赖项的相同版本进行构建,并且不需要依赖包存储库,但需要花更多精力升级依赖项。