Skip to content

Commit bfefbc7

Browse files
authored
Merge pull request #9332 from ruby/show-incorrect-dependencies-message
Include detailed dependencies when gemfile and lockfile are conflicts
2 parents 5013be8 + 7c30560 commit bfefbc7

6 files changed

Lines changed: 159 additions & 6 deletions

File tree

bundler/lib/bundler/errors.rb

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,14 +265,40 @@ def message
265265
class InvalidArgumentError < BundlerError; status_code(40); end
266266

267267
class IncorrectLockfileDependencies < BundlerError
268-
attr_reader :spec
268+
attr_reader :spec, :actual_dependencies, :lockfile_dependencies
269269

270-
def initialize(spec)
270+
def initialize(spec, actual_dependencies = nil, lockfile_dependencies = nil)
271271
@spec = spec
272+
@actual_dependencies = actual_dependencies
273+
@lockfile_dependencies = lockfile_dependencies
272274
end
273275

274276
def message
275-
"Bundler found incorrect dependencies in the lockfile for #{spec.full_name}"
277+
lines = ["Bundler found incorrect dependencies in the lockfile for #{spec.full_name}", ""]
278+
279+
if @actual_dependencies && @lockfile_dependencies
280+
actual_by_name = @actual_dependencies.each_with_object({}) {|d, h| h[d.name] = d }
281+
lockfile_by_name = @lockfile_dependencies.each_with_object({}) {|d, h| h[d.name] = d }
282+
283+
(actual_by_name.keys | lockfile_by_name.keys).sort.each do |name|
284+
actual = actual_by_name[name]
285+
lockfile = lockfile_by_name[name]
286+
next if actual && lockfile && actual.requirement == lockfile.requirement
287+
288+
if actual && lockfile
289+
lines << " #{name}: gemspec specifies #{actual.requirement}, lockfile has #{lockfile.requirement}"
290+
elsif actual
291+
lines << " #{name}: gemspec specifies #{actual.requirement}, not in lockfile"
292+
else
293+
lines << " #{name}: not in gemspec, lockfile has #{lockfile.requirement}"
294+
end
295+
end
296+
297+
lines << ""
298+
end
299+
300+
lines << "Please run `bundle install` to regenerate the lockfile."
301+
lines.join("\n")
276302
end
277303

278304
status_code(41)

bundler/lib/bundler/lazy_specification.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def validate_dependencies(spec)
262262
spec.dependencies = dependencies
263263
else
264264
if !source.is_a?(Source::Path) && spec.runtime_dependencies.sort != dependencies.sort
265-
raise IncorrectLockfileDependencies.new(self)
265+
raise IncorrectLockfileDependencies.new(self, spec.runtime_dependencies, dependencies)
266266
end
267267
end
268268
end

spec/bundler/errors_spec.rb

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe Bundler::IncorrectLockfileDependencies do
4+
describe "#message" do
5+
let(:spec) do
6+
double("LazySpecification", full_name: "rubocop-1.82.0")
7+
end
8+
9+
context "without dependency details" do
10+
subject { described_class.new(spec) }
11+
12+
it "provides a basic error message" do
13+
expect(subject.message).to include("Bundler found incorrect dependencies in the lockfile for rubocop-1.82.0")
14+
expect(subject.message).to include("Please run `bundle install` to regenerate the lockfile.")
15+
end
16+
end
17+
18+
context "with dependency details" do
19+
let(:actual_dependencies) do
20+
[
21+
Gem::Dependency.new("json", [">= 2.3", "< 4.0"]),
22+
Gem::Dependency.new("parallel", ["~> 1.10"]),
23+
Gem::Dependency.new("parser", [">= 3.3.0.2"]),
24+
]
25+
end
26+
27+
let(:lockfile_dependencies) do
28+
[
29+
Gem::Dependency.new("json", [">= 2.3", "< 3.0"]),
30+
Gem::Dependency.new("parallel", ["~> 1.10"]),
31+
Gem::Dependency.new("parser", [">= 3.2.0.0"]),
32+
]
33+
end
34+
35+
subject { described_class.new(spec, actual_dependencies, lockfile_dependencies) }
36+
37+
it "shows only mismatched dependencies" do
38+
message = subject.message
39+
40+
expect(message).to include("json: gemspec specifies")
41+
expect(message).to include("parser: gemspec specifies")
42+
expect(message).not_to include("parallel")
43+
end
44+
end
45+
46+
context "when gemspec has dependencies but lockfile has none" do
47+
let(:actual_dependencies) do
48+
[
49+
Gem::Dependency.new("myrack-test", ["~> 1.0"]),
50+
]
51+
end
52+
53+
let(:lockfile_dependencies) { [] }
54+
55+
subject { described_class.new(spec, actual_dependencies, lockfile_dependencies) }
56+
57+
it "shows the dependency as not in lockfile" do
58+
message = subject.message
59+
60+
expect(message).to include("myrack-test: gemspec specifies ~> 1.0, not in lockfile")
61+
end
62+
end
63+
64+
context "when gemspec has no dependencies but lockfile has some" do
65+
let(:actual_dependencies) { [] }
66+
67+
let(:lockfile_dependencies) do
68+
[
69+
Gem::Dependency.new("unexpected", ["~> 1.0"]),
70+
]
71+
end
72+
73+
subject { described_class.new(spec, actual_dependencies, lockfile_dependencies) }
74+
75+
it "shows the dependency as not in gemspec" do
76+
message = subject.message
77+
78+
expect(message).to include("unexpected: not in gemspec, lockfile has ~> 1.0")
79+
end
80+
end
81+
end
82+
83+
describe "#status_code" do
84+
let(:spec) { double("LazySpecification", full_name: "test-1.0.0") }
85+
subject { described_class.new(spec) }
86+
87+
it "returns 41" do
88+
expect(subject.status_code).to eq(41)
89+
end
90+
end
91+
end

spec/commands/install_spec.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1658,7 +1658,8 @@ def run
16581658
bundle "install", raise_on_error: false
16591659

16601660
expect(exitstatus).to eq(41)
1661-
expect(err).to eq("Bundler found incorrect dependencies in the lockfile for myrack_middleware-1.0")
1661+
expect(err).to include("Bundler found incorrect dependencies in the lockfile for myrack_middleware-1.0")
1662+
expect(err).to include("myrack: gemspec specifies = 0.9.1, not in lockfile")
16621663
end
16631664

16641665
it "updates the lockfile when not frozen" do

spec/install/failure_spec.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,38 @@
4848
end
4949
end
5050
end
51+
52+
context "when lockfile dependencies don't match the gemspec" do
53+
before do
54+
build_repo4 do
55+
build_gem "myrack", "1.0.0" do |s|
56+
s.add_dependency "myrack-test", "~> 1.0"
57+
end
58+
59+
build_gem "myrack-test", "1.0.0"
60+
end
61+
62+
gemfile <<-G
63+
source "https://gem.repo4"
64+
gem "myrack"
65+
G
66+
67+
# First install to generate lockfile
68+
bundle :install
69+
70+
# Manually edit lockfile to have incorrect dependencies
71+
lockfile_content = File.read(bundled_app_lock)
72+
# Remove the myrack-test dependency from myrack
73+
lockfile_content.gsub!(/^ myrack \(1\.0\.0\)\n myrack-test \(~> 1\.0\)\n/, " myrack (1.0.0)\n")
74+
File.write(bundled_app_lock, lockfile_content)
75+
end
76+
77+
it "reports the mismatch with detailed information" do
78+
bundle :install, raise_on_error: false, env: { "BUNDLE_FROZEN" => "true" }
79+
80+
expect(err).to include("Bundler found incorrect dependencies in the lockfile for myrack-1.0.0")
81+
expect(err).to include("myrack-test: gemspec specifies ~> 1.0, not in lockfile")
82+
expect(err).to include("Please run `bundle install` to regenerate the lockfile.")
83+
end
84+
end
5185
end

spec/lock/lockfile_spec.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1608,7 +1608,8 @@
16081608
gem "myrack_middleware"
16091609
G
16101610

1611-
expect(err).to eq("Bundler found incorrect dependencies in the lockfile for myrack_middleware-1.0")
1611+
expect(err).to include("Bundler found incorrect dependencies in the lockfile for myrack_middleware-1.0")
1612+
expect(err).to include("myrack: gemspec specifies = 0.9.1, not in lockfile")
16121613
expect(the_bundle).not_to include_gems "myrack_middleware 1.0"
16131614
end
16141615

0 commit comments

Comments
 (0)