系统理解 Ruby 工具链
一直以来,我对 Ruby 工具链环境都没有一个系统的认识,这使得我在 iOS 开发时遇到的 Ruby 环境问题都难以把握其本质原因。最近开始学习 Ruby 开发,借此机会深入学习了一下 Ruby 工具链,并整理出本文以供后续的学习。
版本管理系统
在软件工程中,版本管理系统(Version Control
System,简称
VCS)是用于管理团队协作开发重要的工具,能够为后续的持续继承提供保障。源码管理器(Source
Code Manager,简称 SCM)就是 VCS
的一种,常见的工具如:git
、svn
。包管理器(Package
Manager,简称 PM)也是 VCS
的一种,常见的工具如:cocoapods
、swift package manager
、npm
、git submodule
。
源码管理器一般是针对单个文件进行版本控制;包管理器则是针对单个 package 进行版本控制。
通常在一个项目中,会同时使用源码管理器和包管理器,由于包管理器能够限定包的版本范围,所以通常不会将包加入源码管理中。举个例子:对于
iOS 项目,我们会使用 git
进行源码管理,使用
cocoapods
进行包管理,在 Pods
目录下存放项目所依赖的包,并在 .ignore
中忽略
Pods
目录。
包管理器
包管理工具通常具备语义化版本检查,依赖递归查找,依赖冲突解决,构建具体依赖等能力。这些能力则主要围绕以下两个文件来实现:
- 描述文件:声明了项目的依赖及其版本限制。
- 锁存文件:记录了依赖更新后的全版本列表。
除了上述两个文件之外,中心化的包管理器一般会提供依赖包的托管服务,比如:npm
提供的 npmjs.com 可以集中查找和下载 npm 包;去中心化的包管理器一般会通过
git 仓库的地址查找和下载依赖包,比如:iOS 的 carthage
和
spm
中所声明的依赖都会有各自的 git
仓库地址。
下面我们对常见的几种包管理器进行简要的对比:
Key File | Git Submodule | CocoaPods | SPM | npm |
---|---|---|---|---|
描述文件 | .gitmodule | Podfile | Package.swift | Package.json |
锁存文件 | .git/modules | Podfile.lock | Package.resolved | Package-lock.json |
下面我们来介绍这里提到的两种包管理器:git submodule
和
cocoapods
。
Git Submodule
Git submodule
是一种原始的包管理器,它不具备常见包管理器工具所特有的语义化管理功能,无法处理依赖共享和冲突。Git
submodule 将单独的 git
仓库以子目录的形式嵌入在项目中,保存每个依赖仓库的文件状态。
.gitmodules
Git submodule 以 .gitmodules
文件作为描述文件,其描述了模块的基本信息,如:path
、url
、branch
。如下所示是一个
.gitmodule
文件的实例: 1
2
3
4[submodule "flutter"]
path = flutter
url = https://github.com/flutter/flutter.git
branch = stable.git/modules
Git submodule
以
.git/modules
目录作为锁存文件,其中 HEAD
文件记录了项目所依赖的子仓库的 commit 信息。
CocoaPods
CocoaPods 是 iOS 开发中常用的一个第三方库的依赖管理工具。
Podfile
CocoaPods 以 Podfile
文件作为描述文件,使用基于 Ruby 的
DSL 来描述依赖关系,用于描述项目所依赖的第三方库。
Podfile.lock
CocoaPods 以 Podfile.lock
文件作为锁存文件,其记录了每个已安装 Pod 的版本。项目中,一般会把
Podfile.lock
加入到版本控制中,有助于保持团队的一致性。
Manifest.lock
CocoaPods 使用未加入版本控制的
Manifest.lock
,对比已加入版本控制的
Podfile.lock
,从而判断是否需要更新本地的依赖。我们常见的
The sandbox is not in sync with the Podfile.lock
报错就是由于 Manifest.lock
与 Podfile.lock
不一致所引起的。每次执行完 pod install
命令时会重新生成
Podfile.lock
及其副本 Manifest.lock
。
Ruby 环境版本控制
我们都知道 CocoaPods 是通过 Ruby 实现的,是一个 gem
包。在 iOS 开发中,我们经常会遇到由于 Ruby 环境变化而导致 CocoaPods
出错。一旦我们理解了 Ruby 的依赖管理,那么有助于我们更好的管理不同版本的
CocoaPods 和其他
gem
,从而顺利解决开发过程中遇到的各种环境问题。
RVM & rbenv
RVM 和 rbenv 都是管理多个 Ruby 环境的工具,它们都能够提供不同版本的 Ruby 环境管理和切换。
关于 RVM 和 rbenv 孰好孰坏,各有各的说法,我选择 RVM 作为 Ruby 版本管理工具主要是因为工作中团队使用的是 RVM。
RubyGems
RubyGems 是 Ruby 的一个包管理器,它所管理的包或者依赖,我们称之为 gem。
上述介绍的包管理器,一般都是围绕着 描述文件 和 锁存文件 来工作的。这种情况只是针对项目而言,因为不同的项目有着不同的环境。然而,RubyGems 作为系统的包管理器,它的工作环境只有一个,因此无需描述文件和锁存文件。
作为一个中心化的包管理器,RubyGems 提供了 Ruby
组件的托管服务,可以集中式的查找和安装工具和依赖。当我们使用
gem install xxx
时,会通过
rubygems.org
来查询对应的 gem。iOS
开发中常用的工具都可以通过 RubyGems
进行安装,如:bundler
、fastlane
、cocoapods
等。
默认情况下,RubyGems 总是下载 gem 的最新版本,这无法确保所安装的 gem 符合我们的预期。因此我们还缺一个工具。
Bundler
Bundler 是管理 gem 依赖的工具,它能够隔离不同项目中 gem 的版本。本质上,Bundler 是一个包管理器,也是一个 gem。
Gemfile
Bundler 以 Gemfile
文件作为描述文件,从而确定各个 gem
的版本号或范围,从而提供稳定的应用环境。
在 iOS 开发中,为了让团队的工作环境保持一致性,通常使用
Bundler,结合项目中的 Gemfile
来指定项目所使用工具的具体版本。如下所示是 Gemfile
配置的示例: 1
2
3
4source 'https://gems.ruby-china.com'
gem 'cocoapods', '~> 1.10.0'
gem 'fastlane', '~> 2.153.1'
当我们执行 bundle exec pod install
时,bundler 会读取
Gemfile 选择指定版本的 cocoapods 来执行 pod install
命令,从而让团队的工作环境保持一致。
Gemfile.lock
Bundler 以 Gemfile.lock
文件作为锁存文件,将各个 gem
的具体安装版本写入其中。
当团队中的其他人通过 bundle install
来安装 gem
时,会读取 Gemfile.lock
中的 gem
名称及其版本信息,从而安装对应的 gem 版本。
Ruby 工具链
如下所示为 Ruby 工具链的关系图,我们可以使用
homebrew
+ rvm
+ RubyGems
+
Bundler
组成的 Ruby 工具链来管理一个项目中 Ruby
工具的版本依赖,具体管理流程如下:
- 使用
homebrew
安装 Ruby 版本管理工具rbenv
或rvm
(rvm
目前不支持通过homebrew
进行安装) - 使用
rbenv
或rvm
安装并指定一个系统默认的 Ruby 版本,每个版本的 Ruby 都有一个对应的RubyGems
包管理器 - 使用
RubyGems
安装 gem 包,如:cocoapods
、bundler
、fastlane
- 使用
bundler
管理单个项目中所依赖 gem 包的版本
对于项目而言,可以使用 bundler
管理其所依赖的 gem
包的版本。具体操作是在项目中新增一个 Gemfile
描述文件,从而锁定项目中依赖 gem
包的版本。
在 Gemfile
所在目录下执行
bundle install
,能够安装 Bundler
环境下的
gem
依赖。通过如下命令可以分别查看系统环境下和
Bundler
环境下的 gem 列表。 1
2
3
4
5系统环境下的 gem 列表
gem list
Bundler 环境下的 gem 列表
bundle exec gem list
总结
本文,我们从版本管理系统出发,分别介绍了源码管理器和包管理器。然后,重点介绍了包管理器中几种常见的工具:git submodule
、cocoapods
。包管理器一般围绕着
描述文件、锁存文件
实现,cocoapods
、bundler
、npm
都是如此。
最后,我们梳理了一下 Ruby 工具链中各种工具之间的关系,并绘制了两个关系图。