Rubegem and Bundler

2020-08-28

Ruby 是解释型语言, 引入一个文件实际上是执行这个文件:

# pseudocode
def require(filename)
  eval File.read(filename)
end

对于同一个文件, require 实际上只会引入一次, 这需要引入一个数组来记录已经引入过的文件:

# pseudocode
$LOADED_FEATURES = []

def require(filename)
  return true if $LOADED_FEATURES.include?(filename)

  eval File.read(filename)
  $LOADED_FEATURES << filename
end

互联网和开源文化催生了各种功能的第三方库, 原始的做法是, 找到库的托管地址, 下载库的源文件到本地, 再 require "拼接好的文件路径" 就可以使用了.

这有几个问题:

  • 第三方库风格迥异, 安装过程不统一;
  • 项目文件中充斥着业务逻辑无关的第三库的源码;
  • 更新第三方库的版本很繁琐;

RubyGem

RubyGems 很好的解决了这些问题.

这是一个典型的 Gem 的文件结构, Gemfile 中指明了 Gem 的依赖库, lib 目录存放库的源码, lib 下有一个跟库同名的文件( require "focus_actor" 就是在执行这个文件 ), 核心逻辑归档到 lib 的子目录中.

.
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── Rakefile
├── bin
│   ├── console
│   └── setup
├── focus_actor.gemspec
├── lib
│   ├── focus_actor
│   │   ├── async.rb
│   │   ├── async_processor.rb
│   │   ├── cell_context.rb
│   │   ├── future.rb
│   │   ├── future_cell.rb
│   │   ├── future_processor.rb
│   │   └── version.rb
│   └── focus_actor.rb
├── pkg
│   ├── focus_actor-0.1.0.gem
│   └── focus_actor-0.1.1.gem
└── spec
    ├── focus_async_spec.rb
    ├── focus_future_spec.rb
    ├── spec_helper.rb
    ├── user.rb
    └── version_spec.rb

$ gem install focus_actor -v 0.1.0 会访问去 RubyGems 的中心仓库下载 focus_actor 指定版本的代码, 存放到本地某个约定好的目录下: $HOME/.rvm/gems/ruby-2.6.3/gems/ ( 使用了 rvm ).

Bundler

管理第三方库的工具有了, 但是在使用库时还是会有些问题:

  • 每个项目使用的库各不相同, 逐个下载即繁琐又容易遗漏;
  • 项目依赖库的版本不一致;

如果项目中依赖很多个 Gem, 需要预先安装这些 Gem.

项目的老组员把所需 Gem 记到一个特殊文件里 Gemfile, 新组员配置环境的时候参照 Gemfile 逐个安装. 把这个步骤自动化, 也就是 bundler 的主要工作.

当然 bundler 做了更多, 他会检查各个依赖的版本, 检查依赖的依赖的版本, 检查依赖的依赖的依赖…的版本, 找到他们都兼容的版本, 把详细条目写到 Gemfile.lock. 如此, 下次安装也省去了检查依赖版本的工作, 更重要的是能让新的安装保持版本一致.

需要注意的是, 一个项目不能同时使用某个库的多个版本 ( 这个需求本身就很可能有问题 ) .

参考

https://guides.rubygems.org/rubygems-basics

https://www.cloudcity.io/blog/2015/07/10/how-bundler-works-a-history-of-ruby-dependency-management