版本管理简介
了解 RVM 是什么以及如何在你的 shell 中使用它。
什么是 Ruby 版本管理器(RVM)
本章对 Ruby 初学者至关重要。现在我们已经了解了 Ruby 的工作原理,并完成了许多练习,接下来可以通过设置一些实用工具来进一步实践。
RVM 是我们 shell 或 IDE 的一层轻量级封装。每位程序员通常都会通过 ruby app.rb 来运行 Ruby 程序,但这个实际的 ruby 命令从何而来呢?让我们看看未安装 RVM 时 which 命令的输出:
1$ which ruby 2/usr/bin/ruby
可见,Ruby 编程语言只是一个位于 /usr/bin/ruby 的可执行二进制文件。用 Far Manager 打开它时,可以看到它的样子:
如果我们删除这个文件,就无法再运行 Ruby 程序。那么,shell 是如何知道要从 /usr/bin 目录启动 Ruby 的呢?让我们查看一下 shell 的 $PATH 环境变量(我们的输出可能略有不同):
1$ echo $PATH 2/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
$PATH 变量包含多个由冒号 : 分隔的目录:
/usr/local/bin/usr/bin(ruby实际所在位置)/bin/usr/sbin/sbin
当我们输入 ruby 命令时,shell 会首先在第一个目录中查找该文件。如果找不到,就会依次检查后续目录,直到找到为止。我们可以重新定义这个变量,让 shell 优先在其他位置查找文件。需要注意的是,必须将新目录添加到开头,而不是末尾。那么,我们应该在哪里进行这样的设置?
所有 shell(如 Bash、Zsh 等)的配置都保存在用户主目录中。我们可以通过输入 echo $HOME 或 echo ~ 来查看主目录路径。Bash 的设置保存在主目录下的 .bashrc 配置文件中。
在 POSIX/UNIX 兼容系统(非 Windows)中,“点文件”是指以点开头的文件,默认是隐藏的。它们不会出现在 ls 命令的输出中,但可以通过 ls -a(“列出所有”)看到。在 Windows 上,点文件并不默认隐藏,其可见性由文件属性控制。Far Manager 中使用 Ctrl+A 组合键可以控制 Windows 上的文件属性。而在 POSIX/UNIX 系统上的 Far Manager,相关对话框略有不同。
Zsh 的设置则保存在主目录下的 .zshrc 文件中,完整路径为 ~/.zshrc。$PATH 环境变量几乎总是定义在用于保存 shell 设置的点文件中。
为什么我们需要 RVM?
但我们为什么要使用 RVM 和这些复杂的操作呢?
我们已经知道,Ruby 是一个独立程序,存在于磁盘的某个位置。但如果发布了新版本怎么办?事实上,Ruby 的版本更新相当频繁,新版本带来了独特的功能和性能提升。我们之前讨论的是 Ruby 语言本身,但你系统上实际运行的是哪个版本?可以通过以下命令查看版本信息:
1$ ruby -v 2ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-darwin16]
“系统 Ruby”指的是位于 /usr/bin 目录下的、随操作系统一起提供的 Ruby 版本。
不过,Windows 系统的情况有所不同。
此外,系统自带的 Ruby 通常不是市场上最新的版本;很可能存在更新的版本,具备新功能和性能优化。但我们如何知道最新版本是什么?
稍等片刻——我们可能并不希望使用最新的 Ruby 版本。虽然这并不意味着新版本总是有缺陷,但它们几乎都是“每日构建版”(nightly builds),即当天的所有更改都会合并到一个分支中,并自动编译生成。
补充说明: 编译是指将源代码转换为机器语言的过程。
Ruby 程序本身并不需要编译,而是被解释执行:我们通过 ruby app.rb 运行程序,而不是直接运行 ./app。因此,不存在所谓的“Ruby 程序构建”。然而,许多开发者将“构建”理解为所有测试成功通过的运行过程。实际上,Ruby 本身是用 C 语言编写的,这一点可以通过在 pry 中调用 show-method loop 来验证。我们可能需要先通过 gem install pry-doc 安装 pry-doc:
1$ pry 2[1] pry(main)> show-method loop 3 4From: vm_eval.c (C Method): 5Owner: Kernel 6Visibility: private 7Number of lines: 6 8 9static VALUE 10rb_f_loop(VALUE self) 11{ 12 RETURN_SIZED_ENUMERATOR(self, 0, 0, rb_f_loop_size); 13 return rb_rescue2(loop_i, (VALUE)0, loop_stop, (VALUE)0, rb_eStopIteration, (VALUE)0); 14}
值得一提的是,流行的浏览器 Firefox 也提供每日构建版本。根据 Firefox 开发者文档 的描述:
“每天,Mozilla 开发者都会将代码合并到共享代码库(Mozilla-central)中。每天,这些代码都会被编译,生成一个基于当前代码的预发布版本,用于测试。这就是我们所说的‘每日构建’。当代码趋于成熟后,会被合并到稳定化仓库(Beta 和 Dev Edition),经过打磨,直到达到足够质量标准,才能向数亿用户发布新版 Firefox。”
除了每日构建外,还有“预览版”构建。每日构建与预览版的区别在于,后者更加稳定。而我们真正关心的是“稳定版”或“长期支持版”(LTS),这更像是一种版本标签,而非构建产物。以下是不同类型的构建版本,代表程序的不同版本形态:
- 每日构建(nightly build)
- 预览版(preview)
- Alpha 版
- Beta 版
- 稳定版(stable)
- LTS(长期支持):并非构建,更像是版本标签
要了解最新的稳定版本,可以访问 官方 Ruby 下载页面,向下滚动至“稳定版本”部分。你可以直接从网页下载,文件格式为 tar.gz。使用以下命令解压:
1$ tar xzf ~/Downloads/ruby-X.Y.Z.tar.gz
上述指令适用于本地计算机。如果你希望在下方的终端中尝试,请先复制下载页面中 Ruby 发布版本的链接地址,然后将其粘贴到 wget 命令之后,中间用空格分隔。
wget 命令用于从网络服务器获取内容,完成后文件将下载到当前目录。因此,tar 命令的路径也需要相应调整。例如:
1$ wget https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.2.tar.gz # 下载 Ruby 2$ tar xzf ./ruby-3.0.2.tar.gz # 解压文件
请记得将 “X.Y.Z” 替换为你实际下载的 Ruby 版本号。解压后,你会看到一个包含源码的目录。可以使用 cd ruby-X.Y.Z 和 ls -lah 查看内容。如果无法直接运行源码,就需要先编译 Ruby,生成可执行的 ruby 文件。这可以通过运行 ./configure 和 make 实现:
1$ cd ruby-X.Y.Z 2$ ./configure 3checking for ruby... /usr/bin/ruby 4tool/config.guess already exists 5tool/config.sub already exists 6checking build system type... x86_64-apple-darwin17.6.0 7checking host system type... x86_64-apple-darwin17.6.0 8checking target system type... x86_64-apple-darwin17.6.0 9checking for clang... clang 10checking for gcc... (cached) clang 11... 12$ make 13 CC = clang 14 LD = ld 15...
最终,你会在当前目录下得到一个 ruby 文件。可以运行它来检查版本:
1$ ./ruby -v 2ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]
前缀 ./ 表示该文件需从当前目录执行,而不是从 $PATH 中的某个目录加载。让我们对比一下上面的结果与下面的输出:
1$ ruby -v 2ruby 2.3.3p222 (2016-11-21 revision 56859) [universal.x86_64-darwin17]
如果我们使用 sudo make install,将会用新编译的版本替换系统 Ruby。不过,新版本通常会被安装到 macOS 的 /usr/local/bin 目录,而系统 Ruby 位于 /usr/bin。这并不是问题,因为 /usr/local/bin 在 $PATH 中排在 /usr/bin 之前。
可能需要重启终端,或运行 source ~/.bashrc、source ~/.zshrc,具体取决于你使用的 shell。
我们是否同意,整个过程确实有些复杂?为什么 Ruby 团队要这样设计?答案是:虽然只有一种 Ruby,但存在多种操作系统。不仅有 Windows、macOS 和 Linux,还有这些系统的不同版本。每种操作系统都有自己的配置,比如性能相关的设置。为了让 Ruby 能充分利用这些特性,我们必须在目标机器上编译 Ruby。此外,同一操作系统下的 CPU 可能也存在差异。例如,某些 CPU 优化可能仅适用于较新的设备,而不适用于旧型号。
现在我们已经成功编译并安装了 Ruby,用新版本替换了系统 Ruby。今后就不必再重复这一系列繁琐步骤了。太棒了!
请在下方的终端中尝试上述命令。