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.rc2rails
  • 任意版本的rack-cache
  • 版本是>= 1.6.1但是< 1.7.0nokogiri

在你第一次声明完依赖后,你要告诉 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让你省略了大量重复的依赖引用。

把你的代码放进版本库

在你开发你的应用一段时间后,把应用跟GemfileGemfile.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.0rack-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.0rack-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 会更新rack1.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说你的GemfileGemfie.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 第一章第一节的内容差不多,但支持下,翻译得很好哦~

Bundler 的作用及原理相关推荐

  1. volatile关键字的作用、原理

    在只有双重检查锁,没有volatile的懒加载单例模式中,由于指令重排序的问题,我确实不会拿到两个不同的单例了,但我会拿到"半个"单例. 而发挥神奇作用的volatile,可以当之 ...

  2. JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架...

    1.类加载器 ·简要介绍什么是类加载器,和类加载器的作用 ·Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader ...

  3. Batch Normalization的作用及原理

    目录 声明 BN是什么[1] 为什么提出BN[1, 2] BN的作用及原理 加速训练,提高收敛速度[1] 缓解梯度消失(梯度爆炸)[3] 缓解过拟合[4] 其他相关问题 BN和激活函数的顺序问题[5] ...

  4. 【原创】uC/OS 中LES BX,DWORD PTR DS:_OSTCBCur的作用及原理

    1 LES BX, DWORD PTR DS:_OSTCBCur ;OSTCBCur->OSTCBStkPtr = SS:SP!!! 2 MOV ES:[BX+2], SS ;将当前SS(栈的基 ...

  5. python super()方法的作用_详解python的super()的作用和原理

    Python中对象方法的定义很怪异,第一个参数一般都命名为self(相当于其它语言的this),用于传递对象本身,而在调用的时候则不必显式传递,系统会自动传递.uz0免费资源网 今天我们介绍的主角是s ...

  6. 主从复制面试之作用和原理

    主从复制面试之作用和原理 有些同学连集群和主从都分不清楚的,这里我说一下他们最本质的区别,其实也就是data-sharing和nothing-sharing的区别.集群是共享存储的.主从复制中没有任何 ...

  7. Java之String系列--intern方法的作用及原理

    原文网址:Java之String系列--intern方法的作用及原理_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Java的String的intern方法的原理. 常量池简介 在 JAVA 语言中 ...

  8. 计算机中数值怎么比较大小,数值比较器,数值比较器的作用和原理是什么?

    描述 数值比较器,数值比较器的作用和原理是什么? 一.数值比较器的定义及功能 在数字系统中,特别是在计算机中都具有运算功能,一种简单的运算就是比较两个数A和B的大小.数值比较器就是对两数A.B进行比较 ...

  9. 代理ARP的作用和原理

    Proxy(代理)ARP作用及原理 代理ARP是ARP协议的一个变种.对于没有配置缺省网关的计算机要和其他网络中的计算机实现通信,网关收到源计算机的 ARP 请求会使用自己的 MAC 地址与目标计算机 ...

最新文章

  1. 独立云计算服务商的多维实践之道:用户需求驱动变革
  2. Error in match.names(clabs, names(xi)) : names do not match previous names
  3. [Head First设计模式]饺子馆(冬至)中的设计模式——工厂模式
  4. web前端——让人头疼的多列复选框排列解决办法
  5. 浅析“高斯白噪声”,“泊松噪声”,“椒盐噪声”的区别
  6. (转)基于MVC4+EasyUI的Web开发框架经验总结(8)--实现Office文档的预览
  7. 安卓开发仿微信图片拖拽_Android 仿微信朋友圈图片拖拽返回
  8. C#.NET学习笔记1---C#.NET简介
  9. AutoMapper,对象映射的简单使用
  10. python程序画漂亮图_用python画图代码:正弦图像、多轴图等案例
  11. Eclipse中集成SVN
  12. php选择版本,怎样选择PHP的版本
  13. 魏兴华_ORACLE优化器革命漫谈
  14. 专业的统计分析软件 IBM SPSS Statistics 26.0.2 Mac版(内附安装包网盘链接)
  15. 使用高德地图加载kml文件
  16. 论文的重复率修改方法
  17. 国际c语言乱码大赛PDF,国际C 语言乱码大赛(IOCCC)获奖作品
  18. ISIS路由过载概述
  19. 阿里云ace考试内容是什么?
  20. 学习正则有感by魔芋(命名问题)

热门文章

  1. 大理大学日常作业计算机基础知识,大理学院成人高等教育计算机应用基础课程作业题及答案...
  2. 关于RPM包中的rpmnew和rpmsave
  3. move std 函数 示例_确保(值类型)可拷贝类有默认构造函数
  4. python用一行代码编写一个回声程序_一行python代码实现树结构
  5. 两个开发源码加密库openssl和cryptlib的比较
  6. 合适么?现在学ASP.NET Core入门编程……
  7. Exchange 2010/2013 删除默认数据库
  8. linux 开启独立iptables日志
  9. Windows多线程编程总结
  10. GRE OVER IPSEC