您的位置:首页 > 其它

Bundler 的作用及原理

2016-07-20 10:14 495 查看


Bundler 的作用及原理

翻译 · yesmeck ·
Created at one year ago · Last by teacafe2000 Replied
at one year ago · 514 hits





原文:http://bundler.io/rationale.html
首先,你要在你应用根目录下一个叫
Gemfile
文件里声明这些依赖,它看起来是这个样子的:
source 'https://rubygems.org'

gem 'rails', '4.1.0.rc2'
gem 'rack-cache'
gem 'nokogiri', '~> 1.6.1'

这个
Gemfile
说明了这些事情:
首先,他告诉 bundler 默认是在
Gemfile
里指定的
https://rubygems.org
上来找
gem。如果你的一些 gem 需要从一个私有的 gem 服务器上获取,那么你可以为这些 gem 覆盖掉这个默认的源设置。
接着,你声明了一些依赖:
版本是
4.1.0.rc2
rails

任意版本的
rack-cache

版本是
>=
1.6.1
但是
<
1.7.0
nokogiri

在你第一次声明完依赖后,你要告诉 bundler 去获取它们:
$ bundle install    # 也可以直接运行 'bundle',相当于 'bundle install'

Bundler 会连接
rubygems.org
(或者其他你声明的源),然后列出所有你指定的符合你需要的
gem。因为所有你在
Gemfile
里的依赖有它们自己的依赖,所以基于上面的
Gemfile
运行
bundle
install
会安装相当多的的 gem。
$ bundle install
Fetching gem metadata from https://rubygems.org/......... Fetching additional metadata from https://rubygems.org/.. Resolving dependencies...
Using rake 10.3.1
Using json 1.8.1
Installing minitest 5.3.3
Installing i18n 0.6.9
Installing thread_safe 0.3.3
Installing builder 3.2.2
Installing rack 1.5.2
Installing erubis 2.7.0
Installing mime-types 1.25.1
Using bundler 1.6.2
Installing polyglot 0.3.4
Installing arel 5.0.1.20140414130214
Installing hike 1.2.3
Installing mini_portile 0.5.3
Installing multi_json 1.9.3
Installing thor 0.19.1
Installing tilt 1.4.1
Installing tzinfo 1.1.0
Installing rack-test 0.6.2
Installing rack-cache 1.2
Installing treetop 1.4.15
Installing sprockets 2.12.1
Installing activesupport 4.1.0.rc2
Installing mail 2.5.4
Installing actionview 4.1.0.rc2
Installing activemodel 4.1.0.rc2
Installing actionpack 4.1.0.rc2
Installing activerecord 4.1.0.rc2
Installing actionmailer 4.1.0.rc2
Installing sprockets-rails 2.0.1
Installing railties 4.1.0.rc2
Installing rails 4.1.0.rc2
Installing nokogiri 1.6.1
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.

如果任何需要的 gem 已经被安装了,bundler 会直接使用它们。在你的系统上安装完所有的 gem 后,bundler 会写一个所有这些 gem 和它们的版本号的快照到 Gemfile.lock 里。


配置你的应用使用 Bundler

Bundler 保证 Ruby 能找到
Gemfile
里的所有
gem 和这些 gem 自己的依赖。如果你的应用是个 Rails 3 以上的应用的话,你的应用默认已经有运行 bundler 的代码了。如果是 Rails 2.3 的应用,可以看在
Rails 2.3 中设置 Bundler。
对于另外的应用来说(比如说基于 Sinatra 的应用),你需要在你引用任何 gem 之前配置一下 bundler。在你应用加载的第一个文件的第一行(对于 Sinatra, 就是写着
require
'sinatra'
的那个文件)加入以下下代码:
require 'rubygems' # Ruby 1.8 以后的版本不再需要这句(楼主注)
require 'bundle/setup'

这样 bundler 就能自动找到你的
Gemfile
,并且让你
Gemfile
里的所有
gem 是可用的(从技术上讲,就是把这些 gem 放到
$LOAD_PATH
里)。
现在你的代码就可以运行了,你可以引用你需要的 gem。比如说你可以
require
'sinatra'
。如果你有很多依赖,你可能希望“引用所有我
Gemfile

gem”。如果要这样做的话,可以把下面这行代码放到
require
'bundler/setup'
的下一行:
Bundler.require(:default)

对于我们刚才的
Gemfile
来说,这行代码相当于:
require 'rails'
require 'rack-cache'
require 'nokogiri'

精明的读者会发现正确引用
rack-cache
的方式是
rake/cache
,而不是
require
'rack-cache
'。为了告诉 bundler 使用
require
'rack/cache'
,只要更新你的
Gemfie
:
source 'https://rubygems.org'

gem 'rails', '4.1.0.rc2'
gem 'rack-cache', require: 'rack/cache'
gem 'nokogiri', '~> 1.6.1'

对于这么小的一个
Gemfile
来说,我们建议你跳过
Bundler.require
而是手动引用这些
gem(特别是你还需要在
Gemfile
里写一个
:require
配置)。对于很大的
Gemfile
来说,使用
Bundler.require
让你省略了大量重复的依赖引用。


把你的代码放进版本库

在你开发你的应用一段时间后,把应用跟
Gemfile
Gemfile.lock
一起放到版本库里。这样,你的版本库里就有了你的应用最后一次你确定能正常工作时所有的
gem 以及版本号的记录。要记住,尽管你的
Gemfile
里只有三个
gem,但是当你去考虑你依赖的 gem 也依赖其他 gem 时,你的应用实际上依赖了大量的 gem,
这个非常重要:
Gemfile.lock
把你的应用变成一个你的代码跟第三方代码最后一次你确定能正常工作的包。 在
Gemfile
里确切指定你依赖的第三方代码的版本并不能提供同样的保证,因为
gem 通常给它们自己的依赖声明一个版本号的范围。
你在同一台机器上再次运行
bundle
install
的时候,bundler 会发现系统上已经有了你需要的依赖,然后就会跳过安装的过程。
不要把
.bundle
目录放入版本库,以及所有它里面的文件。这些文件在不同的机器上是不同的,主要是用来保存运行
bundle
install
时的参数。
如果你运行了
bundle
pack
,你需要的 gem (除了来源是 git 仓库的 gem 以外)都会被下载到
vendor/cache
目录。如果所有你需要的
gem 都在那个目录里而且你把它放进了版本库里,bundler 运行的时候就不需要联网了。这是一个可选的步骤,因为这样做你的版本库就会变得很大。


与其他开发者共享你的应用

当你的同事(或者你在另外一台机器上)获取你的代码的时候,它会包含你最近开发时使用的所有第三方代码的确切版本。当他们运行
bundle
install
,bundler 会找到
Gemfile.lock
并跳过解决依赖的步骤,改为安装所有你原来机器上一样的
gem。
换句话说,你不需要去猜你需要安装什么版本的依赖。在我们刚才用过的栗子里,尽管
rake-cache
声明了依赖
rack
>= 0.4
,但是我们确定他能正常工作在
rack
1.5.2
下。即使 Rack 的团队发布了
rack
1.5.3
,bundler 还是会安装
1.5.2
这个我们已经知道的确切的版本。这为开发者减轻了大量的维护负担,因为所有机器上都运行着同样的第三方代码。


更新依赖

当然,有时候你可能要更新你的应用依赖的部分 gem。比如说,你想要把
rails
升级到
4.1.0
。重点是,你只想要升级一个依赖,而不是要重新解决你的所有依赖并且使用所有
gem 最新的版本。在我们栗子里,你只有3个依赖,但是即使在这个栗子里,更新任何一个东西都会变得复杂。
比如说,
rails
4.1.0.rc2
依赖
actionpack
4.1.0.rc2
,而
actionpack
又依赖
rack
~> 1.5.2
(意思是
>=
1.5.2
<
1.6.0
)。
rack-cache
又依赖
rack
>= 0.4
。我们假设
rails
4.1.0
也依赖
rack
~> 1.5.2
,并且在
rails
4.1.0
发布后 Rack 团队发布了
rack
1.5.3

如果我们为了更新 Rails,天真地更新了所有它依赖的 gem,我们得到了
rack
1.5.3
,这刚好满足
rails
4.1.0
rack-cache
的要求。然而我们并没有特别说要更新
rack-cache
,它就可能跟
rack
1.5.3
不兼容(不管什么原因)。虽然把
rack
1.5.2
升级到
rack
1.5.3
不会搞坏什么东西,但是类似这种导致更大版本跨度的更新场景也会发生(见下面[1]更多讨论)。
为了避免这个问题,当你更新一个 gem 时,如果有其他 gem 有与它相同的依赖,bundler 就不会更新那个相同的依赖。在上面的栗子里,由于
rack-cache
依然依赖
rack
,bundler
不会更新
rack
。这样保证了更新
rails
不会不小心搞坏
rack-cache
。由于
rails
4.1.0
的依赖
actionpack
4.1.0
保留了
rack
1.5.2
的兼容,bundler 就不会管它,
rack-cache
就会继续工作,尽管它可能面临跟
rack
1.5.3
的不兼容。
由于你一开始声明了依赖
rails
4.1.0.rc2
,如果你想要更新到
rails
4.1.0
,只要简单地在
Gemfile
里更新成
gem
'rails', '4.1.0'
并且运行:
$ bundle install

根据上面地描述,
bundle
install
总是执行保守地升级,不会更新你没有在
Gemfile
里显式更改的
gem(或者它们的依赖)。也就是说你不修改
Gemfile
里的
rack-cache
,bundler
就会把它 和它的依赖(rack) 当成一个不可修改的整体。如果
rails
3.0.0
rack-cache
不兼容,bundler
就会显示你的依赖快照(
Gemfile.lock
)跟你更新后的
Gemfile
之间的冲突。
如果你更新了你的
Gemfile
,并且你的系统上已经有你所有需要的依赖了,当你启动应用的时候
bundler 会透明地更新
Gemfile.lock
。举个栗子,如果你把
mysql
加到你的
Gemfile
里,并且已经在你的系统上安装了,你可以不需要运行
bundle
install
就能启动你的应用,并且 bundler 会把最近一次正确的配置写到
Gemfile.lock

这个功能在你添加或更新依赖很少的 gem 时就会比较方便。在你更新一些比较重要的 gem(比如 
rails
)或者有被很多
gem 依赖的 gem(比如
rack
)它就可能失败。如果透明更新失败了,你的应用就会启动失败,bundler
会显示错误引导你运行
bundle
install


不修改 Gemfile 来更新 Gem

有时候,你想要不修改 Gemfile 来更新一个依赖。比如说,你想要更新到最新版本的
rack-cache
。而你又没有在
Gemfie
里指定
rack-cache
的版本,你可能想要周期性地获取
rakc-cache
地最新版。那么你可以使用
bundle
update
命令:
$ bundle update rack-cache

这个命令会更新
rack-cache
和它地依赖更新到
Gemfile
里允许地最新版本(在这个栗子里就是更新到最新版本)。它不会修改其地依赖。
但是它会在需要地时候更新其他 gem 的依赖。举个栗子,如果最新版的
rack-cache
指定了依赖
rack
>= 1.5.2
,bundler 会更新
rack
1.5.2
尽管你没有要求
bundler 更新 
rack
。如果
bundler 需要更新一个其他的 gem 依赖的 gem,那么它会在更新完成后告诉你这件事。
如果你要更新所有 Gemfile 里的 gem 到最新的能用的版本,运行:
$ bundle update

这个命令会从头开始解决依赖并忽略掉
Gemfile.lock
。如果你这么做了,你要准备好
git
reset --hard
和测试用例。从头解决依赖会有意想不到的结果,特别是一部分你依赖的第三方库在你上一次更新的时候发布了新的版本。


总结


一个简单的 Bundler 流程

当你第一次创建 Rails 应用的时候,它已经包含了
Gemfile
。其他的应用可以运行:
$ bundle init

bundle
init
命令会创建一个简单的
Gemfile
让你编辑。
下面,添加你的应用需要的 gem。如果你关心部分你需要的 gem 的版本,可以加一个合适的版本约束:
source 'https://rubygems.org'

gem 'sinatra', '~> 1.3.6'
gem 'rack-cache'
gem 'rack-bug'

如果你有 gem 没在你的系统上安装,运行:
$ bundle install

更新一个 gem 的版本,首先修改 Gemfile:
source 'https://rubygems.org'

gem 'sinatra', '~> 1.4.5'
gem 'rack-cache'
gem 'rack-bug'

然后运行:
$ bundle install

如果
bundle
install
说你的
Gemfile
Gemfie.lock
之间有冲突,运行:
$ bundle update sinatra

这个会升级 Sinatra 这个 gem,以及它所有的依赖。
更新所有你
Gemfile
里的
gem 到最新可用的版本,运行:
$ bundle update


每当你的
Gemfile.lock
变化的时候,把它放入你的版本库。它保存了你的应用能成功运行所依赖的所有第三方代码的确切版本的历史。

当部署你的代码到测试或者生产服务器的时候,首先运行你的测试(或启动你的本地开发服务器),确定你把
Gemfile.lock
放到了版本库里。在远程服务器上,运行:

$ bundle install --deployment


备注

[1] 举个栗子,如果
rails
4.1.0
依赖
rack
2.0
,这个
rack
2.0
满足
rack-cache
的依赖,因为它声明了
>=
0.4
的依赖。当然你能指责说
rack-cache
不指定依赖的最高版本很愚蠢,但是这种情况确实是普遍存在的,而且很多项目声明依赖的时候会发现它们处在一个很尴尬的场面。依赖限制太严(
rack
= 1.5.1
)就会让你的项目很难兼容其他项目。依赖限制太宽(rack >= 1.0)会在 Rack 发布新版本的时候可能搞坏你的代码。使用这样的依赖声明
rack
~> 1.5.2
和 SemVer 兼容的版本号基本上能解决这个问题,但是这也只是一个普遍能接受的方案。由于 RubyGems 有超过十万个库,这个假设在实际应用中可能并不成立。

 本帖已被设为精华帖!



共收到 19 条回复





yesmeck · #1 · one
year ago

半夜翻的,有几处自己看的也不是很明白,大家多多指正。





est · #2 · one
year ago

直接 bundle 和 bundle install 有啥不同?





imlcl · #3 · one
year ago







yakczh · #4 · one
year ago

ruby 应用服务器启动以后,是把所有的类都加载到内存,还是运行时用到的时候才去加载?





yesmeck · #5 · one
year ago

#2楼 @est 是一样的,bundle
不加子命令默认执行 install。





yuhaidonghd · #6 · one
year ago

#4楼 @yakczh 如果是
Rails 应用的话,这和启动的环境有关。
development
 环境肯定不是,
production
 的话应该是几乎全加载了,极少数不在启动时加载。不过这个问题和
Bundler 无关,不同的应用不同的场景会使用不同的加载策略。





ericguo · #7 · one
year ago

#6楼 @yuhaidonghd 
#4楼 @yakczh config/intializers里面都会在启动时加载,但是只是在model/controller/helper里面require的话,还是在第一次用的时候加载





jimrokliu · #8 · one
year ago 2
个赞

补充几点:

1. bundle install 可以将gem安装在另外的目录,参数是--path=

2. 如果让bundle找到gem,可以设置ENV['GEM_PATH'] = "the_path_bundle_install_gem".

3.如果自己设置ruby代码载入Gemfile定义的环境,需要下列代码
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __FILE__)
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
Bundler.require(:default, ENV['RACK_ENV'])






hujinpu · #9 · one
year ago

赞!!!





yakczh · #10 · one
year ago

ruby也是动态脚本,也有require 'xxxx' 为什么不能象php那样修改了立即生效, 是因为web容器启动的时候,已经把所有类加载的内存的原因吗? 





crazyjin · #11 · one
year ago

这样的帖子每天来一个, 生活该多美好..:)





gofreesky · #12 · one
year ago 1
个赞

#10楼 @yakczh 对于你说的这种情况,应该把require改为load





rubyu2 · #13 · one
year ago

官方文档讲的更清楚些。





jun1st · #14 · one
year ago

#10楼 @yakczh 是rails在prod环境这么干的,启动时全部加载,





yakczh · #15 · one
year ago 1
个赞

#14楼 @jun1st developer环境下,是不是跟php一样,新改了代码会重新解析一遍?





jun1st · #16 · one
year ago

#15楼 @yakczh 是的





hbin · #17 · one
year ago

说好的原理呢?标题党?





grd0n9 · #18 · one
year ago 

虽然这篇原文和 the rails 4 way 第一章第一节的内容差不多,但支持下,翻译得很好哦~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: