Skip to content

Commit

Permalink
fix syncing of ruby version
Browse files Browse the repository at this point in the history
and auto-infer ruby version if the parent lockfile constrains it,
but a child lockfile does not
  • Loading branch information
ccutrer committed Mar 29, 2024
1 parent e7698f7 commit 65551fd
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 6 deletions.
15 changes: 12 additions & 3 deletions lib/bundler/multilock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class << self
attr_reader :lockfile_definitions
# @!visibility private
attr_accessor :prepare_block
# @!visibility private
attr_accessor :default_ruby_version

# @param lockfile [String] The lockfile path (defaults to Gemfile.lock)
# @param builder [Dsl] The Bundler DSL
Expand Down Expand Up @@ -276,7 +278,7 @@ def after_install_all(install: true)
p2 != "ruby" && p1 != p2 && MatchPlatform.platforms_match?(p2, p1)
end
end
lockfile.instance_variable_set(:@ruby_version, parent_lockfile.ruby_version)
lockfile.instance_variable_set(:@ruby_version, parent_lockfile.ruby_version) if lockfile.ruby_version
unless lockfile.bundler_version == parent_lockfile.bundler_version
unlocking_bundler = parent_lockfile.bundler_version
lockfile.instance_variable_set(:@bundler_version, parent_lockfile.bundler_version)
Expand Down Expand Up @@ -454,6 +456,9 @@ def write_lockfile(lockfile_definition,
builder = Dsl.new
builder.eval_gemfile(gemfile, &prepare_block) if prepare_block
builder.eval_gemfile(gemfile)
if default_ruby_version && !builder.instance_variable_get(:@ruby_version)
builder.instance_variable_set(:@ruby_version, default_ruby_version)
end

definition = builder.to_definition(lockfile, { bundler: unlocking_bundler })
definition.instance_variable_set(:@dependency_changes, dependency_changes) if dependency_changes
Expand Down Expand Up @@ -511,11 +516,15 @@ def write_lockfile(lockfile_definition,
end
SharedHelpers.capture_filesystem_access do
definition.instance_variable_set(:@resolved_bundler_version, unlocking_bundler) if unlocking_bundler

# need to force it to _not_ preserve unknown sections, so that it
# will overwrite the ruby version
definition.instance_variable_set(:@unlocking_bundler, true)
if Bundler.gem_version >= Gem::Version.new("2.5.6")
definition.instance_variable_set(:@lockfile, lockfile_definition[:lockfile])
definition.lock(true)
definition.lock
else
definition.lock(lockfile_definition[:lockfile], true)
definition.lock(lockfile_definition[:lockfile])
end
end
ensure
Expand Down
15 changes: 12 additions & 3 deletions lib/bundler/multilock/check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ def run(skip_base_checks: false)

success = true
unless skip_base_checks
base_check({ gemfile: Bundler.default_gemfile,
lockfile: Bundler.default_lockfile(force_original: true) })
default_lockfile_definition = Multilock.lockfile_definitions.find do |defn|
defn[:lockfile] == Bundler.default_lockfile(force_original: true)
end
default_lockfile_definition ||= { gemfile: Bundler.default_gemfile,
lockfile: Bundler.default_lockfile(force_original: true) }
base_check(default_lockfile_definition)
end
Multilock.lockfile_definitions.each do |lockfile_definition|
next if lockfile_definition[:lockfile] == Bundler.default_lockfile(force_original: true)
Expand Down Expand Up @@ -68,7 +72,7 @@ def base_check(lockfile_definition, check_missing_deps: false)
end
end

next false unless not_installed.empty? && definition.no_resolve_needed?
next false unless not_installed.empty?

# cache a sentinel so that we can share a cache regardless of the check_missing_deps argument
next :missing_deps unless (definition.locked_gems.dependencies.values - definition.dependencies).empty?
Expand Down Expand Up @@ -105,6 +109,11 @@ def deep_check(lockfile_definition)
"does not match the parent lockfile's version (@#{parent_parser.bundler_version}).")
success = false
end
unless parser.ruby_version == parent_parser.ruby_version
Bundler.ui.error("ruby (#{parser.ruby_version || "<none>"}) in #{lockfile_path} " \
"does not match the parent lockfile's version (#{parent_parser.ruby_version}).")
success = false
end

# look through top-level explicit dependencies for pinned requirements
if lockfile_definition[:enforce_pinned_additional_dependencies]
Expand Down
8 changes: 8 additions & 0 deletions lib/bundler/multilock/ext/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@ module ClassMethods

# Significant changes:
# * evaluate the prepare block as part of the gemfile
# * keep track of the ruby version set in the default gemfile
# * apply that ruby version to alternate lockfiles if they didn't set one
# themselves
# * mark Multilock as loaded once the main gemfile is evaluated
# so that they're not loaded multiple times
def evaluate(gemfile, lockfile, unlock)
builder = new
builder.eval_gemfile(gemfile, &Multilock.prepare_block) if Multilock.prepare_block
builder.eval_gemfile(gemfile)
if gemfile == Bundler.default_gemfile && lockfile == Bundler.default_lockfile(force_original: true)
Multilock.default_ruby_version = builder.instance_variable_get(:@ruby_version)
elsif builder.instance_variable_get(:@ruby_version).nil? && Multilock.default_ruby_version
builder.instance_variable_set(:@ruby_version, Multilock.default_ruby_version)
end
Multilock.loaded!
builder.to_definition(lockfile, unlock)
end
Expand Down
46 changes: 46 additions & 0 deletions spec/bundler/multilock_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,44 @@
end
end

it "syncs ruby version" do
with_gemfile(<<~RUBY) do
gem "concurrent-ruby", "1.2.2"
lockfile do
ruby ">= 2.1"
end
lockfile "alt" do
end
RUBY
invoke_bundler("install")

expect(File.read("Gemfile.lock")).to include(Bundler::RubyVersion.system.to_s)
expect(File.read("Gemfile.alt.lock")).to include(Bundler::RubyVersion.system.to_s)

update_lockfile_ruby("Gemfile.alt.lock", "ruby 2.1.0p0")

expect do
invoke_bundler("check")
end.to raise_error(/ruby \(ruby 2.1.0p0\) in Gemfile.alt.lock does not match the parent lockfile's version/)

update_lockfile_ruby("Gemfile.alt.lock", nil)
expect do
invoke_bundler("check")
end.to raise_error(/ruby \(<none>\) in Gemfile.alt.lock does not match the parent lockfile's version/)

invoke_bundler("install")
expect(File.read("Gemfile.alt.lock")).to include(Bundler::RubyVersion.system.to_s)

update_lockfile_ruby("Gemfile.lock", "ruby 2.6.0p0")
update_lockfile_ruby("Gemfile.alt.lock", nil)

invoke_bundler("install")
expect(File.read("Gemfile.alt.lock")).to include("ruby 2.6.0p0")
end
end

private

def create_local_gem(name, content = "", subdirectory: true)
Expand Down Expand Up @@ -960,4 +998,12 @@ def update_lockfile_bundler(lockfile, version)

File.write(lockfile, new_contents)
end

def update_lockfile_ruby(lockfile, version)
old_contents = File.read(lockfile)
new_version = version ? "RUBY VERSION\n #{version}\n\n" : ""
new_contents = old_contents.gsub(/RUBY VERSION\n #{Bundler::RubyVersion::PATTERN}\n\n/o, new_version)

File.write(lockfile, new_contents)
end
end

0 comments on commit 65551fd

Please sign in to comment.