Skip to content

Commit b5beb19

Browse files
rwstaunerhsbt
authored andcommitted
[rubygems/rubygems] Validate dependencies when doing bundle install
ruby/rubygems@b0983f392f
1 parent 35fc19f commit b5beb19

File tree

5 files changed

+63
-7
lines changed

5 files changed

+63
-7
lines changed

lib/bundler/cli/install.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ def run
6666

6767
Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
6868

69-
definition = Bundler.definition
69+
# For install we want to enable strict validation
70+
# (rather than some optimizations we perform at app runtime).
71+
definition = Bundler.definition(strict: true)
7072
definition.validate_runtime!
7173

7274
installer = Installer.install(Bundler.root, definition, options)

lib/bundler/definition.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
6060

6161
if unlock == true
6262
@unlocking_all = true
63+
strict = false
6364
@unlocking_bundler = false
6465
@unlocking = unlock
6566
@sources_to_unlock = []
@@ -68,6 +69,7 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
6869
conservative = false
6970
else
7071
@unlocking_all = false
72+
strict = unlock.delete(:strict)
7173
@unlocking_bundler = unlock.delete(:bundler)
7274
@unlocking = unlock.any? {|_k, v| !Array(v).empty? }
7375
@sources_to_unlock = unlock.delete(:sources) || []
@@ -97,7 +99,7 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
9799

98100
if lockfile_exists?
99101
@lockfile_contents = Bundler.read_file(lockfile)
100-
@locked_gems = LockfileParser.new(@lockfile_contents)
102+
@locked_gems = LockfileParser.new(@lockfile_contents, strict: strict)
101103
@locked_platforms = @locked_gems.platforms
102104
@most_specific_locked_platform = @locked_gems.most_specific_locked_platform
103105
@platforms = @locked_platforms.dup

lib/bundler/lazy_specification.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def self.from_spec(s)
3333
lazy_spec
3434
end
3535

36-
def initialize(name, version, platform, source = nil)
36+
def initialize(name, version, platform, source = nil, **materialization_options)
3737
@name = name
3838
@version = version
3939
@dependencies = []
@@ -43,6 +43,7 @@ def initialize(name, version, platform, source = nil)
4343

4444
@original_source = source
4545
@source = source
46+
@materialization_options = materialization_options
4647

4748
@force_ruby_platform = default_force_ruby_platform
4849
@most_specific_locked_platform = nil
@@ -226,12 +227,13 @@ def choose_compatible(candidates, fallback_to_non_installable: Bundler.frozen_bu
226227
# Validate dependencies of this locked spec are consistent with dependencies
227228
# of the actual spec that was materialized.
228229
#
229-
# Note that we don't validate dependencies of locally installed gems but
230+
# Note that unless we are in strict mode (which we set during installation)
231+
# we don't validate dependencies of locally installed gems but
230232
# accept what's in the lockfile instead for performance, since loading
231233
# dependencies of locally installed gems would mean evaluating all gemspecs,
232234
# which would affect `bundler/setup` performance.
233235
def validate_dependencies(spec)
234-
if spec.is_a?(StubSpecification)
236+
if !@materialization_options[:strict] && spec.is_a?(StubSpecification)
235237
spec.dependencies = dependencies
236238
else
237239
if !source.is_a?(Source::Path) && spec.runtime_dependencies.sort != dependencies.sort

lib/bundler/lockfile_parser.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def self.bundled_with
9494
lockfile_contents.split(BUNDLED).last.strip
9595
end
9696

97-
def initialize(lockfile)
97+
def initialize(lockfile, strict: false)
9898
@platforms = []
9999
@sources = []
100100
@dependencies = {}
@@ -106,6 +106,7 @@ def initialize(lockfile)
106106
"Gemfile.lock"
107107
end
108108
@pos = Position.new(1, 1)
109+
@strict = strict
109110

110111
if lockfile.match?(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/)
111112
raise LockfileError, "Your #{@lockfile_path} contains merge conflicts.\n" \
@@ -286,7 +287,7 @@ def parse_spec(line)
286287

287288
version = Gem::Version.new(version)
288289
platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
289-
@current_spec = LazySpecification.new(name, version, platform, @current_source)
290+
@current_spec = LazySpecification.new(name, version, platform, @current_source, strict: @strict)
290291
@current_source.add_dependency_names(name)
291292

292293
@specs[@current_spec.full_name] = @current_spec

spec/bundler/commands/install_spec.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1500,6 +1500,55 @@ def run
15001500
end
15011501
end
15021502

1503+
context "when lockfile has incorrect dependencies" do
1504+
before do
1505+
build_repo2
1506+
1507+
gemfile <<-G
1508+
source "https://gem.repo2"
1509+
gem "myrack_middleware"
1510+
G
1511+
1512+
system_gems "myrack_middleware-1.0", path: default_bundle_path
1513+
1514+
# we want to raise when the 1.0 line should be followed by " myrack (= 0.9.1)" but isn't
1515+
lockfile <<-L
1516+
GEM
1517+
remote: https://gem.repo2/
1518+
specs:
1519+
myrack_middleware (1.0)
1520+
1521+
PLATFORMS
1522+
#{lockfile_platforms}
1523+
1524+
DEPENDENCIES
1525+
myrack_middleware
1526+
1527+
BUNDLED WITH
1528+
#{Bundler::VERSION}
1529+
L
1530+
end
1531+
1532+
it "raises a clear error message when frozen" do
1533+
bundle "config set frozen true"
1534+
bundle "install", raise_on_error: false
1535+
1536+
expect(exitstatus).to eq(41)
1537+
expect(err).to eq("Bundler found incorrect dependencies in the lockfile for myrack_middleware-1.0")
1538+
end
1539+
1540+
it "updates the lockfile when not frozen" do
1541+
missing_dep = "myrack (0.9.1)"
1542+
expect(lockfile).not_to include(missing_dep)
1543+
1544+
bundle "config set frozen false"
1545+
bundle :install
1546+
1547+
expect(lockfile).to include(missing_dep)
1548+
expect(out).to include("now installed")
1549+
end
1550+
end
1551+
15031552
context "with --local flag" do
15041553
before do
15051554
system_gems "myrack-1.0.0", path: default_bundle_path

0 commit comments

Comments
 (0)