系统理解 Ruby 工具链

一直以来,我对 Ruby 工具链环境都没有一个系统的认识,这使得我在 iOS 开发时遇到的 Ruby 环境问题都难以把握其本质原因。最近开始学习 Ruby 开发,借此机会深入学习了一下 Ruby 工具链,并整理出本文以供后续的学习。

版本管理系统

在软件工程中,版本管理系统(Version Control System,简称 VCS)是用于管理团队协作开发重要的工具,能够为后续的持续继承提供保障。源码管理器(Source Code Manager,简称 SCM)就是 VCS 的一种,常见的工具如:gitsvn包管理器(Package Manager,简称 PM)也是 VCS 的一种,常见的工具如:cocoapodsswift package managernpmgit submodule

源码管理器一般是针对单个文件进行版本控制;包管理器则是针对单个 package 进行版本控制。

通常在一个项目中,会同时使用源码管理器和包管理器,由于包管理器能够限定包的版本范围,所以通常不会将包加入源码管理中。举个例子:对于 iOS 项目,我们会使用 git 进行源码管理,使用 cocoapods 进行包管理,在 Pods 目录下存放项目所依赖的包,并在 .ignore 中忽略 Pods 目录。

包管理器

包管理工具通常具备语义化版本检查,依赖递归查找,依赖冲突解决,构建具体依赖等能力。这些能力则主要围绕以下两个文件来实现:

  • 描述文件:声明了项目的依赖及其版本限制。
  • 锁存文件:记录了依赖更新后的全版本列表。

除了上述两个文件之外,中心化的包管理器一般会提供依赖包的托管服务,比如:npm 提供的 npmjs.com 可以集中查找和下载 npm 包;去中心化的包管理器一般会通过 git 仓库的地址查找和下载依赖包,比如:iOS 的 carthagespm 中所声明的依赖都会有各自的 git 仓库地址。

下面我们对常见的几种包管理器进行简要的对比:

Key File Git Submodule CocoaPods SPM npm
描述文件 .gitmodule Podfile Package.swift Package.json
锁存文件 .git/modules Podfile.lock Package.resolved Package-lock.json

下面我们来介绍这里提到的两种包管理器:git submodulecocoapods

Git Submodule

Git submodule 是一种原始的包管理器,它不具备常见包管理器工具所特有的语义化管理功能,无法处理依赖共享和冲突。Git submodule 将单独的 git 仓库以子目录的形式嵌入在项目中,保存每个依赖仓库的文件状态。

.gitmodules

Git submodule 以 .gitmodules 文件作为描述文件,其描述了模块的基本信息,如:pathurlbranch。如下所示是一个 .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.lockPodfile.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 进行安装,如:bundlerfastlanecocoapods 等。

默认情况下,RubyGems 总是下载 gem 的最新版本,这无法确保所安装的 gem 符合我们的预期。因此我们还缺一个工具。

Bundler

Bundler 是管理 gem 依赖的工具,它能够隔离不同项目中 gem 的版本。本质上,Bundler 是一个包管理器,也是一个 gem

Gemfile

Bundler 以 Gemfile 文件作为描述文件,从而确定各个 gem 的版本号或范围,从而提供稳定的应用环境。

在 iOS 开发中,为了让团队的工作环境保持一致性,通常使用 Bundler,结合项目中的 Gemfile 来指定项目所使用工具的具体版本。如下所示是 Gemfile 配置的示例:

1
2
3
4
source '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 版本管理工具 rbenvrvmrvm 目前不支持通过 homebrew 进行安装)
  • 使用 rbenvrvm 安装并指定一个系统默认的 Ruby 版本,每个版本的 Ruby 都有一个对应的 RubyGems 包管理器
  • 使用 RubyGems 安装 gem 包,如:cocoapodsbundlerfastlane
  • 使用 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 submodulecocoapods。包管理器一般围绕着 描述文件锁存文件 实现,cocoapodsbundlernpm 都是如此。

最后,我们梳理了一下 Ruby 工具链中各种工具之间的关系,并绘制了两个关系图。

参考

  1. Homebrew
  2. RVM
  3. rbenv
  4. Cocoapods
  5. Package Manager
  6. npm
  7. Gradle
  8. 版本管理工具及 Ruby 工具链环境
  9. Why rbenv?
  10. Bundler
  11. Ruby实用指南