我应该在我的 Gemfile 中指定确切的版本吗?

Posted

技术标签:

【中文标题】我应该在我的 Gemfile 中指定确切的版本吗?【英文标题】:Should I specify exact versions in my Gemfile? 【发布时间】:2012-03-05 03:09:54 【问题描述】:

我注意到 ruby​​gems.org 上的许多 gem 建议您按主要版本而不是确切版本来指定它们。比如……

The haml-rails gem...

gem "haml-rails", "~> 0.3.4"  # "$ bundle install" will acquire the 
                              # latest version before 1.0.

但是,基于Bundler docs,我觉得最好像这样确定确切的版本...

gem "haml-rails", "0.3.4"

所以有了 haml-rails gem,它的所有依赖项都不会向前移动。如果您几周后在另一台机器上检查项目并运行$ bundle install,您将获得与您指定的所有内容完全相同的版本。

我看到点发布打破了一些东西,我认为 Bundler 的整个想法的一部分是“Bundle.lock”你所有的 gem 版本。

但在 ruby​​gems.org 上,他们经常使用“~>”,所以也许我遗漏了什么?

任何澄清都会对我理解 Bundler 和 gem 管理非常有帮助。

【问题讨论】:

我愿意。惊喜越少越好。一个依赖项更新只需要一次时间,而您没有故意这样做会让您陷入数小时甚至数天的兔子洞,让您学习这一课。不能信任第三方和开源库严格遵循语义版本控制(甚至是我自己的库)。不值得冒险。 【参考方案1】:

TL;DR

是的,使用pessimistic locking (~>) 并指定semantic version 以在所有宝石上打补丁 (Major.minor.patch)!

讨论

我对这个问题缺乏明确性感到惊讶,甚至“行业专家”前几天告诉我Gemfile.lock 可以维护 gem 版本。错了!

您希望以这样一种方式组织您的Gemfile,以便您可以随时运行bundle update 而不会冒险破坏一切。要实现这一点:

    为所有带有悲观锁定的 gem 指定一个补丁级别的版本。这将允许bundle update 为您提供修复,但不会破坏性更改。

    为来自 git 的 gem 指定 ref

此设置的唯一缺点是,当宝石的新次要/主要版本出现时,您必须手动升级版本。

警告场景

考虑一下如果您不锁定宝石会发生什么。 您的 gemfile 中有一个未锁定的 gem "rails"Gemfile.lock 中的版本是 4.1.16。您正在编码,并且在某些时候您会执行bundle update。现在您的 Rails 版本跳转到 5.2.0(前提是其他一些 gem 不能阻止这种情况)并且一切都中断了。 帮自己一个忙,不要让任何宝石这样做!

Gemfile 示例

# lock that bundler
if (version = Gem::Version.new(Bundler::VERSION)) < Gem::Version.new('1.16.3')
  abort "Bundler version >= 1.16.3 is required. You are running #version"
end

source "http://rubygems.org"

# specify explicit ref for git repos
gem "entity_validator",
  git: "https://github.com/plataformatec/devise",
  ref: "acc45c5a44c45b252ccba65fd169a45af73ff369" # "2018-08-02"

# consider hard-lock on gems you do not want to change one bit
gem "rails", "5.1.5"

# pessimistic lock on your common gems
gem "newrelic_rpm", "~> 4.8.0"
gem "puma", "~> 3.12.0"

group :test do
  gem "simplecov", "~> 0.16.1", require: false
end

让步 如果您确信您的测试将捕获由 gem 版本更改引入的错误,您可以在次要版本而不是补丁尝试悲观锁定 gem。 这将允许 gem 版本在指定的主要版本内增加,但永远不会进入下一个。

gem "puma", "~> 3.12"

【讨论】:

悲观这个词在这里令人困惑(我知道这只是语义,但仍然如此)。如果您将其锁定为带有 = 的版本,那就太悲观了!但是 ~> 实际上允许您更新到最新的次要版本。 你写了You want to organize your Gemfile in such a manner that you can run bundle update any time without risking breaking everything。不,这不是目标。听起来你可能不明白bundle updatebundle install 之间的区别。 update 更新 Gemfile.lock 并更改您正在使用的版本。您希望能够随时运行bundle install,而不会冒险破坏一切。事实上,你正在强迫 GemfileGemfile.lock 应该做的事情。 那些“行业专家”是正确的:Gemfile.lock确实实际上维护了 gem 版本。直到(当然)你决定bundle update 覆盖它(基本上就像说bundle overwrite_my_locked_gem_versions)。 我看不出我们对“维护”的定义有什么不同。 Gemile.lock 维护(存储)gems 的版本,但并非完全不可变——当您发出 bundle update 命令时,它会发生变异。根据你的定义,“行业专家”是完全正确的,这个答案误导了所有相信它的人。 我看到它的方式(这就是我首先写答案的原因),有一个误解,仅仅因为有一个 Gemfile.lock 文件包含所有确切版本,开发人员不需要在Gemfile 中指定 gem 版本(锁定“维护”版本的想法)。那是错误的。开发人员通过在Gemfile 中指定它们并偶尔运行bundle update 来“维护”版本。【参考方案2】:

这是 Gemfile.lock 文件的目的 - 运行 bundle install 并存在 Gemfile.lock 仅使用其中列出的依赖项进行安装;它不会重新解析 Gemfile。要更新依赖项/更新 gem 版本,您必须显式执行 bundle update,这将更新您的 Gemfile.lock 文件。

如果没有 Gemfile.lock,将代码部署到生产将是一个主要问题,因为正如您所提到的,依赖项和 gem 版本可能会发生变化。

简而言之,按照 ruby​​gems.org 的建议,使用悲观版本约束运算符 (~&gt;) 通常应该是安全的。请务必在执行bundle update 后重新运行测试以确保没有任何问题。

Yehuda Katz 的 nice article 提供了有关 Gemfile.lock 的更多信息。

【讨论】:

好的,所以 gems 保持在 Gemfile.lock 中记录的既定版本。那么添加“~>”的目的是什么?这有什么好处? @ethan RubyGems 有一个 doc 解释它(参见“防止版本灾难”部分)。它的要点是它只允许版本号中的最后一个整数增加(例如,'~> 1.0.5' 允许更新到版本 1.0.9999,但永远不会更新到 1.1.x)。该机制允许更新 gem,但不会引入可能破坏事物的不兼容性(它假设 gem 遵循链接大纲的“Rational Versioning”策略)。 我认为您所写内容的要点是,应该在自己的 Gemfile 中保留悲观的版本约束,以便 可以 轻松升级到与指定的主要版本和次要版本匹配的最新版本。但是也应该使用 Gemfile.lock 文件,并将其保存在源代码中,以便必须明确进行升级以影响部署代码的任何环境。【参考方案3】:

我肯定会说使用确切的版本号。您可能总是可以将其锁定到一个主要版本,或者从不指定任何版本,并且没关系,但是如果您真的想要那种细粒度的控制水平并且在您的程序在其他机器上运行时对您的程序有 100% 的信心,使用准确的版本号。

我遇到过未指定确切版本号的情况,当我或其他人执行bundle install 时,该项目因升级到更新版本而中断。这在部署到生产环境时尤其糟糕。

Bundler确实锁定了您的 gem 规范,但是如果您告诉它只使用主要版本,那么它会锁定它。所以只知道“哦,版本被锁定在> 0.1”或其他什么,但不是“哦,该版本专门锁定在 0.1.2.3”。

【讨论】:

如果存在Gemfile.lock,那么实际上Bundler 确实知道要安装哪个特定版本(这就是为什么Gemfile.lock 应该与Gemfile 一起存储在repo 中)。 尽管Gemfile.lock 存在,但执行bundle update &lt;gem&gt; 最终更新的方式可能比您想象的要多,这可能是一个危险且棘手的情况。 我同意 RubyGems 自己在这个问题上的建议:使用悲观约束(~>)即可。这鼓励了整个社区进行语义版本控制,这是一件好事,在这与 Gemfile.lock 的内置稳定性功能之间,您的基础应该被覆盖得更多。 @solidcell 我不认为每次更新 gem 时都必须输入源代码。我更喜欢使用尽可能精确的版本,但正如前面提到的,大多数时候您通常可以只使用 ~> 约束。但是,我之前已经得到了一个新的、有漏洞的 gems 版本。 您不需要(也不应该)在Gemfile 中使用精确的版本。这就是Gemfile.lock 的目的。如果您将Gemfile.lock 提交到源代码控制中,那么有人拉这个并执行bundle install 将获得与您完全相同的gem 版本。在Gemfile 中设置精确版本会阻止您执行bundle update gem_you_want_to_update,而悲观版本(~&gt;)或根本没有版本允许您运行bundle update gem_you_want_to_update 并获得最新(次要)版本

以上是关于我应该在我的 Gemfile 中指定确切的版本吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何在我的 Gemfile 中指定本地 .gem 文件?

在 Gemfile 和 .ruby-version Dotfile 中列出 Ruby 版本是一种不好的做法吗?

在 Gemfile 中指定 ruby​​gem 时 ~> 和 >= 有啥区别?

ruby 在Gemfile.lock中指定git分支

将版本设置为最新版本,包括 Gemfile 中的预发布版本

如何在 COM 事件源和处理程序中指定“事件类型”?