Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix syncing a gem update that changes platforms #43

Merged
merged 2 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 2 additions & 17 deletions lib/bundler/multilock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ def after_install_all(install: true)
Bundler.ui.info("Syncing to #{relative_lockfile}...") if attempts == 1
synced_any = true

specs = lockfile_name.exist? ? cache.specs(lockfile_name) : {}
parent_lockfile_name = lockfile_definition[:parent]
parent_root = parent_lockfile_name.dirname
parent_specs = cache.specs(parent_lockfile_name)
Expand All @@ -218,7 +217,7 @@ def after_install_all(install: true)
end

# add a source for the current gem
gem_spec = parent_specs[[File.basename(Bundler.root), "ruby"]]
gem_spec = parent_specs.dig(File.basename(Bundler.root), "ruby")

if gem_spec
adjusted_parent_lockfile_contents += <<~TEXT
Expand Down Expand Up @@ -253,27 +252,13 @@ def after_install_all(install: true)
spec,
parent_spec)

# look through all reverse dependencies; if any of them say it
# has to come from self, due to conflicts, then this gem has
# to come from self as well
[cache.reverse_dependencies(lockfile_name),
cache.reverse_dependencies(parent_lockfile_name)].each do |reverse_dependencies|
break if precedence == :self

reverse_dependencies[spec.name].each do |dep_name|
precedence = check_precedence.call(specs[dep_name], parent_specs[dep_name])
break if precedence == :self
end
end

spec_precedences[spec.name] = precedence || :parent
end

# replace any duplicate specs with what's in the parent lockfile
lockfile.specs.map! do |spec|
parent_spec = parent_specs[[spec.name, spec.platform]]
parent_spec = cache.find_matching_spec(parent_specs, spec)
next spec unless parent_spec

next spec if check_precedence.call(spec, parent_spec) == :self

dependency_changes ||= spec != parent_spec
Expand Down
20 changes: 18 additions & 2 deletions lib/bundler/multilock/cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,27 @@ def parser(lockfile_name)
end

def specs(lockfile_name)
@specs[lockfile_name] ||= parser(lockfile_name).specs.to_h do |spec|
[[spec.name, spec.platform], spec]
@specs[lockfile_name] ||= begin
specs = {}
parser(lockfile_name).specs.each do |spec|
(specs[spec.name] ||= {})[spec.platform] = spec
end
specs
end
end

# sometimes a gem changes platforms with a new version, such as from aarch64-linux
# to aarch64-linux-gnu. we need to still sync it
def find_matching_spec(specs, spec)
specs = self.specs(specs) unless specs.is_a?(Hash)
platform_specs = specs[spec.name]
return unless platform_specs

parent_spec = platform_specs[spec.platform]
parent_spec ||= platform_specs.find { |platform, _| platform =~ spec.platform }&.last
parent_spec || platform_specs.find { |platform, _| platform == "ruby" }&.last
end

# @param lockfile_name [Pathname]
# @return [Hash<String, Set<String>>] hash of gem name to set of gem names that depend on it
def reverse_dependencies(lockfile_name)
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/multilock/check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def deep_check(lockfile_definition)

# check for conflicting requirements (and build list of pins, in the same loop)
parser.specs.each do |spec|
parent_spec = @cache.specs(parent_lockfile_name)[[spec.name, spec.platform]]
parent_spec = @cache.find_matching_spec(parent_lockfile_name, spec)

if lockfile_definition[:enforce_pinned_additional_dependencies]
# look through what this spec depends on, and keep track of all pinned requirements
Expand Down
29 changes: 29 additions & 0 deletions spec/bundler/multilock_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,35 @@
end
end

it "syncs gems whose platforms changed slightly" do
if RUBY_VERSION < "3.0"
skip "The test case that triggers this requires Ruby 3.0+; " \
"just rely on this test running on other ruby versions"
end

with_gemfile(<<~RUBY) do
gem "sqlite3", "~> 1.7"
lockfile("all") {}
RUBY
invoke_bundler("install")

write_gemfile(<<~RUBY)
gem "sqlite3"
lockfile("all") {}
RUBY
invoke_bundler("install")

expect(invoke_bundler("info sqlite3")).to include("1.7.3")
expect(invoke_bundler("info sqlite3", env: { "BUNDLE_LOCKFILE" => "all" })).to include("1.7.3")

invoke_bundler("update sqlite3")
expect(invoke_bundler("info sqlite3")).not_to include("1.7.3")
expect(invoke_bundler("info sqlite3", env: { "BUNDLE_LOCKFILE" => "all" })).not_to include("1.7.3")
end
end

it "syncs ruby version" do
with_gemfile(<<~RUBY) do
gem "concurrent-ruby", "1.2.2"
Expand Down
Loading