大家好, 最近在研究 GemBundler,一直觉得用了 GemBundler 之后 Gem 版本发生冲突的概率大幅减小了,比以前在 config/environment.rb 中 config.gem 的办法强多了,于是一直希望找到 GemBundler 解决版本冲突的代码,但是至今无果。。。 然后我又试图做个试验来找到这段代码,但是在实验中,我震惊了: 从图中看到,我做了两个 gem,分别依赖 json >= 1.7.3 和 json <= 1.7.4,当前 rubygems.org 上 json 的最新版本是 1.7.5,然后我创建了一个 Rails 项目,导入我自己做的这两个 gem,然后在 Gemfile 中同时声明他们,运行 bundle install,出现了上述错误,GemBundler 竟然认为我做的那个依赖 json >= 1.7.3 的 gem 依赖的是 1.7.5?因而不能解决?天哪! 毫无疑问,json >= 1.7.3 和 json <= 1.7.4 的版本冲突是完全可以解决的,只要取 json 1.7.4 即可,但是 GemBundler 却不能解决它们。 本来想证明 GemBundler 在解决版本冲突上有更好的解决方案,但实验结果恰恰推翻了这个证明。那么为什么感觉用了 GemBundler 之后 Gem 版本发生冲突的概率小了呢?难道是我的幻觉? 实在不能理解,故请教大家。谢谢!
根据 http://gembundler.com/v1.2/man/bundle-update.1.html
If bundle install(1) reports a conflict, manually update the specific gems that you changed in the Gemfile(5)
试试 bundle update
? 我没有测试验证过,看文档似乎可行。
bundler 之所以会拿 1.7.5 这个版本来用,说明这个版本信息一定是从系统某处读取出来的 (反正不是这两个 gem 的 gemspec). 目前来看只能是来自 Gemfile.lock
和 gem list
.
设法把这两个信息清除掉 (亦即,分别删除 Gemfile.lock
和已经安装的 json gem), 应该就不会干扰 bundler 的解析了。
json >= 1.7.3 && json <= 1.7.4 到底是 1.7.3 还是 1.7.4,二选一你自己能回答吗? 新建目录,里面只有 Gemfile 内容: gem json, '>=1.7.3' gem json, '<=1.7.4' 执行 bundle 报错 "You cannot specify the same gem twice with different version requirements. You specified: json (>= 1.7.3) and json (<= 1.7.4)"
bundler 能解决大部分版本冲突的问题。你这么故意玩它,表示伤不起~
#9 楼 @leekelby 不是啊 不能在 Gemfile 里定义两个名字相同版本号不同的 gem,也不能定义两个名字相同但是源不同的 gem,这个特性是显而易见的,而且在一个 Gemfile 里显式的声明两个名字相同的 gem 本身就是蛋疼行为,和我的问题无关。
# if there's already a dependency with this name we try to prefer one
if current = @dependencies.find { |d| d.name == dep.name }
if current.requirement != dep.requirement
if current.type == :development
@dependencies.delete current
elsif dep.type == :development
return
else
raise DslError, "You cannot specify the same gem twice with different version requirements. " \
"You specified: #{current.name} (#{current.requirement}) and " \
"#{dep.name} (#{dep.requirement})"
end
end
if current.source != dep.source
if current.type == :development
@dependencies.delete current
elsif dep.type == :development
return
else
raise DslError, "You cannot specify the same gem twice coming from different sources. You " \
"specified that #{dep.name} (#{dep.requirement}) should come from " \
"#{current.source || 'an unspecified source'} and #{dep.source}"
end
end
end
不过关于声明版本不一致的时候,bundle
不会智能地找到最合适的版本,而是根据 Gemfile.lock
直接加载,如果 Gemfile.lock
里面出现两个不同版本,会提醒出来,( 就算使用 >= <= ), 解决方法只能删掉 lock 重新 bundle install
#11 楼 @lyfi2003 #12 楼 @hooopo 哦 我每次试验前有意先删除 Gemfile.lock 的 之前读代码就发现这个文件会影响很多行为,所以干脆先删掉了。 然后我发现,json >= 1.7.3 和 json <= 1.7.4 不能相容,但如果是 json >= 0.4.0 和 json <= 1.7.4 是能够相容的,但如果是 json >= 0.4.1 和 json <= 1.7.4 又变成不能相容了,0.4.0 的界限真的很奇怪,于是我重点研究 0.4.0 和 0.4.1 到底区别在什么地方。 随着阅读的深入,我发现其实 resolver 的 resolve instance method 是实现检查版本冲突的核心方法,但是实现非常麻烦,循环+递归,很难调试,所以我输出了一些信息,然后瞎猜了。以下内容均势我根据输出信息的猜测: 下面要输出的是调用 resolve 方法的参数,以及里面一个排序方法在排序前和排序后的数据对比,以及排序方法的 block 里返回的数据: 下面是 json >= 0.4.1 的输出信息:
➜ 6 /tmp/test-project rm Gemfile.lock; bundle install
rm: Gemfile.lock: No such file or directory
fatal: Not a git repository (or any of the parent directories): .git
fatal: Not a git repository (or any of the parent directories): .git
Fetching gem metadata from https://rubygems.org/..
resolve([use_json (>= 0) ruby, use_json_lower_than_1_7_4 (>= 0) ruby], {})
reqs before sort: [use_json (>= 0) ruby, use_json_lower_than_1_7_4 (>= 0) ruby]
use_json (>= 0) ruby => [1, 1, 1, 1]
use_json_lower_than_1_7_4 (>= 0) ruby => [1, 1, 1, 1]
reqs after sort: [use_json (>= 0) ruby, use_json_lower_than_1_7_4 (>= 0) ruby]
resolve([use_json_lower_than_1_7_4 (>= 0) ruby, json (>= 0.4.1) ruby], {"use_json"=>[#<Gem::Specification name=use_json version=0.0.1>]})
reqs before sort: [use_json_lower_than_1_7_4 (>= 0) ruby, json (>= 0.4.1) ruby]
use_json_lower_than_1_7_4 (>= 0) ruby => [1, 1, 1, 1]
json (>= 0.4.1) ruby => [1, 1, 1, 49]
reqs after sort: [use_json_lower_than_1_7_4 (>= 0) ruby, json (>= 0.4.1) ruby]
resolve([json (>= 0.4.1) ruby, json (<= 1.7.4) ruby], {"use_json"=>[#<Gem::Specification name=use_json version=0.0.1>], "use_json_lower_than_1_7_4"=>[#<Gem::Specification name=use_json_lower_than_1_7_4 version=0.0.1>]})
reqs before sort: [json (>= 0.4.1) ruby, json (<= 1.7.4) ruby]
json (>= 0.4.1) ruby => [1, 1, 1, 49]
json (<= 1.7.4) ruby => [1, 1, 1, 49]
reqs after sort: [json (>= 0.4.1) ruby, json (<= 1.7.4) ruby]
resolve([json (<= 1.7.4) ruby], {"use_json"=>[#<Gem::Specification name=use_json version=0.0.1>], "use_json_lower_than_1_7_4"=>[#<Gem::Specification name=use_json_lower_than_1_7_4 version=0.0.1>], "json"=>[#<Gem::Specification name=json version=1.7.5>, #<Gem::Specification name=json version=1.7.5>]})
reqs before sort: [json (<= 1.7.4) ruby]
json (<= 1.7.4) ruby => [0, 1, 1, 0]
reqs after sort: [json (<= 1.7.4) ruby]
Bundler could not find compatible versions for gem "json":
In Gemfile:
use_json_lower_than_1_7_4 (>= 0) ruby depends on
json (<= 1.7.4) ruby
use_json (>= 0) ruby depends on
json (1.7.5)
然后是 json >= 0.4.0 的输出信息:
➜ 6 /tmp/test-project rm Gemfile.lock; bundle install
rm: Gemfile.lock: No such file or directory
fatal: Not a git repository (or any of the parent directories): .git
fatal: Not a git repository (or any of the parent directories): .git
Fetching gem metadata from https://rubygems.org/..
resolve([use_json (>= 0) ruby, use_json_lower_than_1_7_4 (>= 0) ruby], {})
reqs before sort: [use_json (>= 0) ruby, use_json_lower_than_1_7_4 (>= 0) ruby]
use_json (>= 0) ruby => [1, 1, 1, 1]
use_json_lower_than_1_7_4 (>= 0) ruby => [1, 1, 1, 1]
reqs after sort: [use_json (>= 0) ruby, use_json_lower_than_1_7_4 (>= 0) ruby]
resolve([use_json_lower_than_1_7_4 (>= 0) ruby, json (>= 0.4.0) ruby], {"use_json"=>[#<Gem::Specification name=use_json version=0.0.1>]})
reqs before sort: [use_json_lower_than_1_7_4 (>= 0) ruby, json (>= 0.4.0) ruby]
use_json_lower_than_1_7_4 (>= 0) ruby => [1, 1, 1, 1]
json (>= 0.4.0) ruby => [1, 1, 1, 50]
reqs after sort: [use_json_lower_than_1_7_4 (>= 0) ruby, json (>= 0.4.0) ruby]
resolve([json (>= 0.4.0) ruby, json (<= 1.7.4) ruby], {"use_json"=>[#<Gem::Specification name=use_json version=0.0.1>], "use_json_lower_than_1_7_4"=>[#<Gem::Specification name=use_json_lower_than_1_7_4 version=0.0.1>]})
reqs before sort: [json (>= 0.4.0) ruby, json (<= 1.7.4) ruby]
json (>= 0.4.0) ruby => [1, 1, 1, 50]
json (<= 1.7.4) ruby => [1, 1, 1, 49]
reqs after sort: [json (<= 1.7.4) ruby, json (>= 0.4.0) ruby]
resolve([json (>= 0.4.0) ruby], {"use_json"=>[#<Gem::Specification name=use_json version=0.0.1>], "use_json_lower_than_1_7_4"=>[#<Gem::Specification name=use_json_lower_than_1_7_4 version=0.0.1>], "json"=>[#<Gem::Specification name=json version=1.7.4>, #<Gem::Specification name=json version=1.7.4>]})
reqs before sort: [json (>= 0.4.0) ruby]
json (>= 0.4.0) ruby => [0, 1, 1, 0]
reqs after sort: [json (>= 0.4.0) ruby]
resolve([], {"use_json"=>[#<Gem::Specification name=use_json version=0.0.1>], "use_json_lower_than_1_7_4"=>[#<Gem::Specification name=use_json_lower_than_1_7_4 version=0.0.1>], "json"=>[#<Gem::Specification name=json version=1.7.4>, #<Gem::Specification name=json version=1.7.4>]})
Using json (1.7.4)
Using use_json (0.0.1) from source at vendor/gems/use_json-0.0.1/
Using use_json_lower_than_1_7_4 (0.0.1) from source at vendor/gems/use_json_lower_than_1_7_4-0.0.1/
Using bundler (1.2.1)
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
注意它们的区别在于,如果是 json >= 0.4.0 的时候,json >= 0.4.0 在 json <= 1.7.4 之后被解析,而 json >= 0.4.1 的时候,json >= 0.4.1 在 json <= 1.7.4 之前被解析。如果 json <= 1.7.4 先被解析,则会向数组放进一个 json = 1.7.4 的 gem,这样,后来的 json >= 0.4.0 就能和它兼容了,但是如果是 json >= 0.4.1 先被解析,则数组中直接被写入了 json 的最新版本 1.7.5,这样再解析 json <= 1.7.4 的时候就会出错了。那决定解析顺序的代码是什么呢?
reqs = reqs.sort_by do |a|
arr = [ activated[a.name] ? 0 : 1,
a.requirement.prerelease? ? 0 : 1,
@errors[a.name] ? 0 : 1,
activated[a.name] ? 0 : @gems_size[a] ]
puts "#{a.inspect} => #{arr.inspect}"
arr
end
这里我拦截这个数组,输出它后再返回它(源代码里就是直接返回这个数组)。可以从上门的输出信息中看到,真正决定谁先谁后的数据是数组最后一个元素,代码是@gems_size[a],通过阅读源码,发现 gems_size 的意思是,在 source 中符合这个 gem 要求的 gem 数量,在 rubygems.org 中,json >= 0.4.0 的 gem 有 50 个,json >= 0.4.1 的 gem 有 49 个,而 gem <= 1.7.4 的 gem 也是 49 个,由于 json >= 0.4.1 的 gem 数量等于 gem <= 1.7.4 的 gem 数量,在排序时没有优势,因此 json >= 0.4.1 先解析,然后出错了。而 json >= 0.4.0 的 gem 数量比 json <= 1.7.4 的 gem 数量多 1,在排序时有优势,所以后者前解析,成功。 这里最让人无语的是,source 里符合要求的 gem 数量竟然能影响 GemBundler 解决版本冲突的因素之一。我现在不知道该如何评论了。。。大家看看把??
好吧,历经了整整一年,这个 Bug 终于 Fix 了,还是在 1.6 的预览版本里,内流满面啊。https://github.com/bundler/bundler/issues/2122