=
依赖关系?问:我理解将我的 gems 锁定到特定版本的重要性,但是为什么我不能只在 Gemfile
中为所有依赖关系指定 =
版本,然后忘记 Gemfile.lock
?
答:你的许多 gems 将有自己的依赖关系,而且它们不太可能指定 =
依赖关系。此外,gems 如此严格地锁定所有它们的依赖关系可能不明智。Gemfile.lock
允许你在 Gemfile
中指定你的应用程序需要的依赖关系的版本,同时记住你的应用程序上次正常工作时使用的所有第三方代码的精确版本。
通过在你的 Gemfile
中指定更宽松的依赖关系(例如 nokogiri ~> 1.4.2
),你获得了运行 bundle update nokogiri
的能力,并让 bundler 处理仅更新 nokogiri
及其依赖关系到仍然满足 ~> 1.4.2
版本要求的最新版本。这也允许你说“我想使用 nokogiri 的当前版本”(在你的 Gemfile
中使用 gem 'nokogiri'
),而无需查找确切的版本号,同时仍然获得确保你的应用程序始终使用完全相同的第三方代码版本的优势。
问:我不明白为什么我需要 bundler 以这种方式管理我的 gems。为什么我不能只获取我需要的 gems 并将它们放在子模块中,然后将每个子模块放在加载路径上?
答:不幸的是,这种解决方案要求你手动解决应用程序中的所有依赖关系,包括依赖关系的依赖关系。即使你成功地做到了这一点,如果你想更新某个特定的 gem,你也需要重新做这项工作。例如,如果你想更新 rails
gem,你需要找到所有依赖于 Rails 依赖关系的 gems(rack
、erubis
、i18n
、tzinfo
等),并找到满足 Rails 新版本要求的新版本。
坦率地说,这正是计算机擅长的工作,而你,作为一名开发人员,不应该花时间做这种事情。
更令人担忧的是,如果你在手动依赖关系解析过程中犯了错误,你将不会收到有关不同依赖关系之间冲突的任何反馈,从而导致运行时错误。例如,如果你不小心将错误版本的 rack
放入子模块,它很可能在运行时崩溃,当 Rails 或其他依赖关系试图依赖于不存在的方法时。
底线: 尽管乍一看可能更简单,但实际上它要复杂得多。
问: 我运行了 `bundle install --without production`,但 Bundler 仍然下载了 `:production` 组中的 Gem。为什么?
答: Bundler 的 `Gemfile.lock` 必须包含 `Gemfile` 中所有依赖项的精确版本,无论你传递了哪些选项。如果没有,将你的应用程序部署到生产环境可能会改变所有依赖项,从而消除 Bundler 的优势。你将无法再确定你的应用程序在生产环境中使用的 Gem 与开发和测试时使用的 Gem 相同。此外,在生产环境中添加依赖项可能会导致无法部署的应用程序。
例如,假设你有一个仅限生产环境的 Gem(我们称之为 `rack-debugging`),它依赖于 `rack =1.1`。如果我们在运行 `bundle install --without production` 时没有评估生产组,你将部署你的应用程序,但会收到一个错误,提示 `rack-debugging` 与 `rails` 冲突(它依赖于 `actionpack`,而 `actionpack` 依赖于 `rack ~> 1.2.1`)。
另一个例子:假设一个简单的 Rack 应用程序在 `Gemfile` 中有 `gem 'rack'`。同样,假设你将 `rack-debugging` 放入 `:production` 组。如果我们在通过 `bundle install --without production` 安装时没有评估 `:production` 组,你的应用程序将在开发环境中使用 `rack 1.2.1`,并且你将在部署时发现 `rack-debugging` 与你测试时使用的 Rack 版本冲突。
相反,通过在调用 `bundle install` 时评估所有组中的 Gem,无论你实际上想要在该环境中使用哪些组,我们都会发现 `rack-debugger` 需求,并安装 `rack 1.1`,它也与 `Gemfile` 中的 `gem 'rack'` 需求兼容。
简而言之,通过始终评估 `Gemfile` 中的所有依赖项,无论你打算在特定环境中使用哪些依赖项,你都可以避免在切换到不同环境中的不同组时出现意外问题。而且因为我们只是下载(但没有安装)Gem,所以你不必担心在生产环境(或开发环境)中仅使用的 Gem 可能出现的困难安装过程。
问:我有一个 C 扩展 Gem,例如 `mysql`,它需要特殊的标志才能编译和安装。如何将这些标志传递到这些 Gem 的安装过程中?
A: 首先,这个问题在 mysql2
gem 中不存在,它可以完全替代 mysql
gem。一般来说,现代的 C 扩展可以正确地发现所需的标头文件。
如果你真的需要向 C 扩展传递标志,可以使用 bundle config
命令
$ bundle config build.mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config
Bundler 会将此配置存储在 ~/.bundle/config
中,并且 Bundler 会将此配置用于同一用户执行的任何 bundle install
操作。因此,一旦你为 gem 指定了必要的构建标志,你就可以成功地安装该 gem,次数不限。
Q: 我没有网络连接,但我之前已经安装了 gem。如何让 Bundler 使用我的本地 gem 缓存,而不是连接到 gem 服务器?
A: 使用 --local
标志与 bundle install 一起使用。--local
标志告诉 Bundler 使用本地 gem 缓存,而不是连接到远程 gem 服务器。
$ bundle install --local
Q: 当我从 RubyGems.org 安装 gem 时,速度非常慢。有什么方法可以加快速度吗?
A: 首先,通过运行 gem install bundler
更新到最新版本的 Bundler。多年来,我们添加了许多改进,使安装 gem 的速度更快。如果你有极高的延迟连接,使用 --full-index
标志也可能会看到改进。这会一次性下载 gem 信息,而不是发出许多小的 HTTP 请求。
$ bundle install --full-index
Q: 如果我在 gem 中放一个 Gemfile
会发生什么?
问: 当有人安装你的 gem 时,Gemfile
和 Gemfile.lock
文件会被完全忽略,即使你将它们包含在上传到 rubygems.org 的 .gem
文件中。gem 内部的 Gemfile
只是为了方便开发者(比如你)安装开发你的 gem 所需的依赖项。Gemfile
还提供了一种简单的方法来跟踪和安装仅用于开发或测试的 gem。从 gem 中的 Bundler 页面和 如何使用 Bundler 创建 gem 指南中了解有关 gem 中的 Gemfile 的信息。
问: 在编写 gem 时,我应该提交我的 Gemfile.lock
吗?
答: 是的,你应该提交它。在 gem 的仓库中存在 Gemfile.lock
可以确保每次从仓库中进行新的检出都使用完全相同的依赖项集。我们认为这使得仓库对新老贡献者更加友好。理想情况下,任何人都应该能够克隆仓库,运行 bundle install
,并通过测试。如果你没有检入你的 Gemfile.lock
,新的贡献者可能会获得不同版本的依赖项,并遇到他们不知道如何解决的测试失败。
问: 但是我听说 gem 不应该检入 Gemfile.lock!
答: 不检入你的 Gemfile.lock 的主要优点是,如果你的某个依赖项以破坏性的方式发生变化,新的检出(包括 CI)将立即出现测试失败。Bundler 团队建议使用像 Dependabot 这样的工具来自动创建 PR 并运行测试套件,以便在你的依赖项发布新版本时立即进行测试。如果你不想使用依赖项监控机器人,我们建议创建一个额外的每日 CI 构建,在运行 bundle install
之前删除 Gemfile.lock。这样,你和其他监控你的 CI 状态的人将是第一个知道依赖项更改导致的任何失败的人。