Ruby版本管理器¶
在 Download Ruby 官方下载页面介绍中可以知晓,除了Linux/UNIX发行版提供的Ruby安装包,另一种常用的Ruby安装方式是采用 Ruby版本管理器
安装:
Ruby版本管理器就类似于
npm
之于 JavaScript Atlas ,可以在系统中安装不同版本的Ruby并提供切换能力,这样不同的应用可以采用不同的Ruby版本运行来满足特定需求由于Ruby是不断发展的语言,所以编写的程序可能需要特定的Ruby版本运行,所以Ruby版本管理器非常适合这种场景
在多个Ruby应用程序的运行环境中,同一台服务器上可能需要不同的Ruby版本来支持不同的Ruby应用
目前常用的Ruby版本管理器有两种:
RVM
rbenv
这两个Ruby版本管理器工作原理不同,但是结果是相同的。
在Linux平台, RVM
被广泛接受为标准,很大程度上是因为它提供了广泛的toolkit(工具包);但是, rbenv
凭借其轻量级的方法成为强有力的竞争者。
此外,在 macOS 平台,由于系统内置了旧版本Ruby,会导致RVM存在一些问题,所以更推荐通过 Homebrew 安装 rbenv
来管理Ruby版本。
Under the Hood(工作原理)¶
RVM
通过覆盖cd
Shell Atlas 命令来加载当前的Ruby环境变量。这种直接粗暴覆盖的风险是可能会导致意外行为,并且还意味着切换目录时会加载ruby
以及gemset
rbenw
则相对轻柔很多:在
PATH
的前面部分插入一个~/.rbenv/shims
(目录垫片, a directory of shims)在这个目录下保存了每个Ruby命令的
shim
操作系统搜索与命令名字匹配的
shim
,然后将其传递给rbenv
来确定要执行的Ruby版本提供了一个
RBENV_VERSION
变量来快速指定Ruby默认版本(排在第一位)
RVM¶
典型的
rvm
目录存储了不同的Ruby版本,类似:
$ tree /usr/local/rvm # the following is partial output
/usr/local/rvm # RVM path directory
├── gems
│ ├── ruby-2.2.2
│ │ ├── bin
│ │ │ ├── bundle
│ │ │ └── rubocop
│ │ └── gems
│ │ ├── bundler-1.12.5
│ │ ├── freewill-1.0.0
│ │ │ └── lib
│ │ │ └── freewill.rb
│ │ ├── pry-0.10.4
│ │ └── rubocop-0.43.1
│ └── ruby-2.3.1
│ ├── bin
│ │ ├── bundle
│ │ └── rubocop
│ └── gems
│ ├── bundler-1.12.5
│ ├── freewill-1.0.0
│ │ └── lib
│ │ └── freewill.rb
│ ├── pry-0.10.4
│ └── rubocop-0.45.0
└── rubies
├── ruby-2.2.2
│ └── bin
│ ├── gem
│ ├── irb
│ └── ruby
└── ruby-2.3.1
└── bin
├── gem
├── irb
└── ruby
RVM的核心是一组目录,RVM在目录中储存了所有Ruby版本以及相关工具(gem和irb),每个目录都针对特定的Ruby版本
RVM定义了一个名为
rvm
的 Shell Atlas 函数,这样shell会优先使用这个函数而不是执行磁盘中的rvm
命令(原因是函数可以修改环境,但是基于磁盘的命令则不能)当运行
rvm use VERSION
来修改Ruby版本时,实际上调用了rvm
函数来修改环境,以便各种ruby
命令调用正确的版本: 例如rvm use 2.2.2
会修改PATH
变量,以便使用ruby
命令是使用安装在ruby-2.2.2
目录中的 ruby (还有一些其他修改,但最主要是修改PATH
)
安装¶
参考官方文档 Installing RVM 安装RVM:
# 安装GPG keys
gpg2 --keyserver keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
# 安装development版本(master分支)
curl -sSL https://get.rvm.io | bash
# 安装development版本同时也安装rails
curl -sSL https://get.rvm.io | bash -s master --rails
# ==== 以下安装适合生产稳定环境 ==== #
# 安装stable版本
curl -sSL https://get.rvm.io | bash -s stable --ruby
# 安装stable版本同时也安装rails
curl -sSL https://get.rvm.io | bash -s stable --rails
安装完成后(我这里的案例是安装 master
分支,并且同时安装 Ruby on Rails )输出类似:
...
Done installing documentation for zeitwerk, thor, webrick, rack, rackup, concurrent-ruby, tzinfo, i18n, connection_pool, activesupport, mini_portile2, nokogiri, crass, loofah, rails-html-sanitizer, rails-dom-testing, rack-test, rack-session, erubi, builder, actionview, actionpack, railties, marcel, activemodel, activerecord, globalid, activejob, activestorage, actiontext, mini_mime, mail, actionmailer, actionmailbox, websocket-extensions, websocket-driver, nio4r, actioncable, rails after 43 seconds
39 gems installed
* To start using RVM you need to run `source /home/admin/.rvm/scripts/rvm`
in all your open shell windows, in rare cases you need to reopen all shell windows.
* To start using rails you need to run `rails new <project_dir>`.
备注
Ubuntu Linux 发行版内置提供了 RVM
警告
get.rvm.io
实际上是 raw.githubusercontent.com
重定向,所以在墙内是无法直接执行上述安装脚本的。解决的方法是使用 curl代理 结合 SSH隧道 (socks代理) 或者 Squid父级socks代理 ,总之,需要 “梯子”
使用¶
安装多版本ruby¶
检查系统已经安装的ruby版本:
rvm list
输出信息:
=* ruby-3.3.0 [ x86_64 ]
# => - current
# =* - current && default
# * - default
安装特定版本(举例 3.1.4 这里选择 3.1.4 是因为当前Gentoo官方提供的安装包就是选择3.1.4,我想既然我安装了最新版本的3.3.0,那么也有必要安装一下主流stable版本):
rvm install 3.1.4
再次检查系统已经安装的ruby版本(
rvm list
),就会发现当前有2个版本已经完成安装:
=> ruby-3.1.4 [ x86_64 ]
* ruby-3.3.0 [ x86_64 ]
# => - current
# =* - current && default
# * - default
设置当前ruby版本¶
rvm use XXXX
可以指定当前会话的ruby版本,以下为将当前版本3.1.4切换到3.3.0:
$ ruby --version
ruby 3.1.4p223 (2023-03-30 revision 957bb7cb81) [x86_64-linux]
# 切换到3.3.0版本
$ rvm use 3.3.0
Using /home/admin/.rvm/gems/ruby-3.3.0
# 再次检查当前会话就会看到版本已经从 3.1.4 切换到 3.3.0
$ ruby --version
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-linux]
# 设置3.3.0为默认版本
$ rvm use 3.3.0 --default
Using /home/admin/.rvm/gems/ruby-3.3.0
此时检查 rvm list
可以看到输出已经是采用 3.3.0 版本:
ruby-3.1.4 [ x86_64 ]
=* ruby-3.3.0 [ x86_64 ]
# => - current
# =* - current && default
# * - default
备注
设置完成后检查 env
输出信息就可以看到 RVM
修改了用户的环境变量 PATH
PATH=/home/admin/.rvm/gems/ruby-3.3.0/bin:/home/admin/.rvm/gems/ruby-3.3.0@global/bin:/home/admin/.rvm/rubies/ruby-3.3.0/bin:/home/admin/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/bin
设置项目使用的ruby版本¶
在Ruby程序的项目目录下执行 rvm --ruby-version use XXX
指定版本后, RVM
会在当前的项目目录下创建一个 .ruby-version
文件,这个文件内容就是项目期望的ruby版本。这样就不需要每次为项目通过 rvm use
来切换Ruby了:
# 案例设置一个 ~/src/magic 目录模拟Ruby项目目录
mkdir -p ~/src/magic
cd ~/src/magic
# 指定该目录使用的ruby版本
rvm --ruby-version use 3.1.4
此时检查项目当前目录,会看到一个 .ruby-version
文件,内容哥就是指定的ruby版本:
$ ls -a
. .. .ruby-version
$ cat .ruby-version
ruby-3.1.4
现在来演练一下,先离开项目目录,执行 ruby --version
就会看到默认的ruby版本3.3.0,当重新回到该项目目录,则 ruby --version
就立即显示(切换)成该目录下 .ruby-version
指定的版本:
$ pwd
/home/admin/src/magic
$ ruby --version
ruby 3.1.4p223 (2023-03-30 revision 957bb7cb81) [x86_64-linux]
# 返回用户HOME目录,ruby版本切换回之前选择的默认版本3.3.0
$ cd
admin@gentoo-base-plus ~ $ ruby --version
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-linux]
# 再次回到项目目录,ruby版本自动切换到 .ruby-version 文件中指定的项目版本
$ cd src/magic/
$ ruby --version
ruby 3.1.4p223 (2023-03-30 revision 957bb7cb81) [x86_64-linux]
备注
RVM
通过在目录下创建一个 .ruby-version
文件(内容是ruby版本,类似于Python的 requirements.txt)。则每次用户进入该目录,则 ruby
版本会自动切换到 .ruby-version
文件指定的版本。这样就避免用户反复手工设置调整 RVM
指定ruby版本(也很容易忘记)。
实现的原理是 RVM
将shell中的 cd
命令替换成 shell 函数,该函数调用真正的 cd
命令,然后检查 .ruby-version
文件是否存在,如果存在,则从该文件检索版本号,并以 rvm
函数相同的方式修改环境变量以正确运行Ruby版本。
通过
rvm info rvm
命令可以获取RVM
详细的信息:
rvm info rvm
在不同目录下,注意 ruby 版本不同(项目目录下有 .ruby-version
配置文件):
~/src/magic $ rvm info rvm
ruby-3.1.4:
rvm:
version: "1.29.12-next (master)"
updated: "3 hours 54 minutes 24 seconds ago"
path: "/home/admin/.rvm"
autolibs: "[4] Allow RVM to use package manager if found, install missing dependencies, install package manager (only OS X)."
~/src/magic $ cd
~ $ rvm info rvm
ruby-3.3.0:
rvm:
version: "1.29.12-next (master)"
updated: "3 hours 57 minutes 31 seconds ago"
path: "/home/admin/.rvm"
autolibs: "[4] Allow RVM to use package manager if found, install missing dependencies, install package manager (only OS X)."
简单的
rvm info
命令可以输出系统的详细信息:
ruby-3.3.0:
system:
uname: "Linux gentoo-base-plus 6.1.67-gentoo-dist #1 SMP PREEMPT_DYNAMIC Sun Dec 17 15:08:29 CST 2023 x86_64 Intel(R) Core(TM) i5-4260U CPU @ 1.40GHz GenuineIntel GNU/Linux"
name: "Gentoo"
version: "base-2.14"
architecture: "x86_64"
bash: "/bin/bash => GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)"
zsh: " => not installed"
remote_path: "gentoo/base-2.14/x86_64"
rvm:
version: "1.29.12-next (master)"
updated: "4 hours 5 minutes 47 seconds ago"
path: "/home/admin/.rvm"
autolibs: "[4] Allow RVM to use package manager if found, install missing dependencies, install package manager (only OS X)."
ruby:
interpreter: "ruby"
version: "3.3.0"
date: "2023-12-25"
platform: "x86_64-linux"
patchlevel: "2023-12-25 revision 5124f9ac75"
full_version: "ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-linux]"
homes:
gem: "/home/admin/.rvm/gems/ruby-3.3.0"
ruby: "/home/admin/.rvm/rubies/ruby-3.3.0"
binaries:
ruby: "/home/admin/.rvm/rubies/ruby-3.3.0/bin/ruby"
irb: "/home/admin/.rvm/rubies/ruby-3.3.0/bin/irb"
gem: "/home/admin/.rvm/rubies/ruby-3.3.0/bin/gem"
rake: "/home/admin/.rvm/rubies/ruby-3.3.0/bin/rake"
environment:
PATH: "/home/admin/.rvm/gems/ruby-3.3.0/bin:/home/admin/.rvm/gems/ruby-3.3.0@global/bin:/home/admin/.rvm/rubies/ruby-3.3.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/bin:/home/admin/.rvm/bin"
GEM_HOME: "/home/admin/.rvm/gems/ruby-3.3.0"
GEM_PATH: "/home/admin/.rvm/gems/ruby-3.3.0:/home/admin/.rvm/gems/ruby-3.3.0@global"
MY_RUBY_HOME: "/home/admin/.rvm/rubies/ruby-3.3.0"
IRBRC: "/home/admin/.rvm/rubies/ruby-3.3.0/.irbrc"
RUBYOPT: ""
gemset: ""
RVM的困扰¶
RVM
实际上比较复杂(shell函数代替了shell命令),有时候会出现异常:
需要 确保目录名称不包含任何空格 ,也包括上级目录:
RVM
当前不支持待空格的目录名称type cd | head -1 ; type rvm | head -1
可以看到cd
和rvm
都是shell函数 如果输出不是如下内容,则表明RVM的shell设置出现了问题cd is a function rvm is a function
echo $PATH
确保{RVM PATH}/rubies-{VERSION}/bin
和{RVM PATH}/gem/rubies-{VERSION}/bin
已经设置在PATH
环境变量中。并且比其他目录(可能包含相同程序名)列在前面其他检查信息的方法:
# 当前激活版本信息
rvm current
# 修复RVM文件权限,特别是sudo安装,修改或更新RVM,遇到 "permission denied" ,可以尝试运行这个命令
rvm fix-permissions
# 修复帮助RVM管理不同rubies的文件
rvm repair all
# 下载安装最新版本RVM
rvm get latest
# 检查RubyGems系统配置信息
gem env
# 列出当前Ruby安装的所有Gems
gem list
Uninstall RVM¶
RVM
提供了一个自己卸载的方法:
rvm implode
输出显示:
Are you SURE you wish for rvm to implode?
This will recursively remove /home/admin/.rvm and other rvm traces?
(anything other than 'yes' will cancel) > yes
Removing rvm-shipped binaries (rvm-prompt, rvm, rvm-sudo rvm-shell and rvm-auto-ruby)
Removing rvm wrappers in /home/admin/.rvm/bin
Hai! Removing /home/admin/.rvm
/home/admin/.rvm has been removed.
Note you may need to manually remove /etc/rvmrc and ~/.rvmrc if they exist still.
Please check all .bashrc .bash_profile .profile and .zshrc for RVM source lines and delete or comment out if this was a Per-User installation.
Also make sure to remove `rvm` group if this was a system installation.
Finally it might help to relogin / restart if you want to have fresh environment (like for installing RVM again).
只需要输入 yes
就能自动完成,必要时按照提示手工移除 ~/.rvm
目录
rbenv¶
典型的
rbenv
目录结构:
$ tree /usr/local/rbenv # the following is partial output
/usr/local/rbenv # rbenv root directory
├── shims
│ ├── bundle
│ ├── irb
│ ├── rubocop
│ └── ruby
└── versions
├── 2.2.2
│ ├── bin
│ │ ├── bundle
│ │ ├── irb
│ │ ├── rubocop
│ │ └── ruby
│ └── lib
│ └── ruby
│ └── gems
│ └── 2.2.0
│ └── gems
│ ├── bundler-1.12.5
│ ├── freewill-1.0.0
│ │ └── lib
│ │ └── freewill.rb
│ ├── pry-0.10.4
│ └── rubocop-0.43.1
└── 2.3.1
├── bin
│ ├── bundle
│ ├── irb
│ ├── rubocop
│ └── ruby
└── lib
└── ruby
└── gems
└── 2.2.0
└── gems
├── bundler-1.12.5
├── freewill-1.0.0
│ └── lib
│ └── freewill.rb
├── pry-0.10.4
└── rubocop-0.45.0
rbenv
的核心是一组与RVM
核心目录非常相似的目录然而,在底层,
rbenv
管理Rubies
的方式与RVM
的管理方式有很大不同:rbenv
不是直接修改用户的PATH
环境变量,而是在用户PATH
中插入一组shims
小脚本,类似于包装当运行
ruby
或Gems
时,系统会执行shim
脚本,然后shim
脚本会依次执行rbenv exec PROGRAM
,该命令确定应该使用哪个版本的Ruby
rbenv
的优点是:可以在任何类Unix系统的任何shell中工作,唯一依赖是 Bash 解释器
rbenv
只完成最低限度工作,其余部分留给插件生态系统
rbenv
的缺点是:由于
rbenv
会拦截对 ruby 的每次调用,因此它可能会给 ruby 执行时间增加约50 毫秒的开销
,对于速度至关重要的时候可能会导致问题由于
rbenv
在项目之间严格版本隔离,可能会导致难以保留”全局”(系统级)Ruby工具调用,并且无法简单从一个Ruby版本的项目直接shell out
到另外一个Ruby版本的脚本无法感知安装到用户主目录的
gems
:rbenv
无法阿发现使用--user-install
安装的gems
可执行文件,即安装在~/.gem
或者~/.local/share/gem
目录下的可执行文件(解决方法是避免依赖rbenv
管理项目中的这些gem
或者手动将相应的~/.local/share/gem/<engine>/<version>/bin
添加到路径中)
安装¶
# 安装 rbenv
brew install rbenv ruby-build
# 在shell中加载 rbenv
rbenv init
然后关闭当前终端窗口,并再次打开新终端窗口,则此时修改生效
使用¶
rbenv
基本使用:
# list latest stable versions:
rbenv install -l
# list all local versions:
rbenv install -L
# install a Ruby version:
rbenv install 3.1.2
容器时代¶
通过简单的尝试,我感觉:
如果在Linux平台为自己的部署开发环境,例如我采用 Gentoo镜像 运行 在Gentoo上运行Gentoo(容器) ,那么使用 RVM 非常方便,也能够用于小型项目生产环境
如果在 macOS 环境使用 Homebrew 管理,那么 rbenv 或许是更方便的方式,不过按照官方文档,对于生产环境大压力需要考虑
rbenv
包装脚本的细微性能损失
现在随着容器技术的不断普及,生产和开发测试环境也大量使用容器,实际上对于Ruby应用,需要通过ruby版本管理器来区分和切换ruby版本的需求大为降低:
容器天然分隔了不同的应用程序运行环境
每个容器都有自己独立的定制版本,界面清晰
轻量级的容器(非 “富容器”)资源占用低,无版本干扰且必定是准确配置ruby版本
所以后续在容器化运行,乃至 Kubernetes Atlas 化运行Ruby应用,可以替代本文的ruby版本管理器。