Skip to content
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
31 changes: 27 additions & 4 deletions lib/bundler/lockfile_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,40 @@ def add_section(name, value)
end

def bundler_checksum
# `.dev` versions and `SKIP_BUNDLER_CHECKSUM` are deliberate opt-outs (used
# by Bundler/RubyGems' own development and release tasks): never record a
# checksum for Bundler itself in those cases.
return [] if Bundler.gem_version.to_s.end_with?(".dev") || ENV["SKIP_BUNDLER_CHECKSUM"]

bundler_spec = definition.sources.metadata_source.specs.search(["bundler", Bundler.gem_version]).last
return [] unless File.exist?(bundler_spec.cache_file)

require "rubygems/package"
# Record a fresh checksum from the locally cached gem when it's available.
# When it isn't (e.g. a fresh checkout/CI that never downloaded the bundler
# gem), fall back to whatever checksum is already locked rather than
# dropping it, so the entry stays consistent across environments.
if File.exist?(bundler_spec.cache_file)
require "rubygems/package"

package = Gem::Package.new(bundler_spec.cache_file)
definition.sources.metadata_source.checksum_store.register(bundler_spec, Checksum.from_gem_package(package))
elsif bundled_with_changing?
# We can't compute a fresh checksum (the bundler gem isn't cached) and the
# BUNDLED WITH version is changing. Keeping the previously locked checksum
# would leave a `bundler (<old version>) sha256=...` entry that no longer
# matches the new BUNDLED WITH version, so drop it instead.
return []
end

package = Gem::Package.new(bundler_spec.cache_file)
definition.sources.metadata_source.checksum_store.register(bundler_spec, Checksum.from_gem_package(package))
return [] if definition.sources.metadata_source.checksum_store.missing?(bundler_spec)

[definition.sources.metadata_source.checksum_store.to_lock(bundler_spec)]
end

def bundled_with_changing?
locked_gems = definition.locked_gems
return false unless locked_gems

locked_gems.bundler_version != definition.bundler_version_to_lock
end
end
end
79 changes: 79 additions & 0 deletions spec/bundler/commands/update_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,85 @@
expect(out).to include("Using bundler 9.0.0")
end

it "preserves the locked bundler checksum when re-locking without the bundler gem cached" do
system_gems "bundler-9.0.0"

build_repo4 do
build_gem "myrack", "1.0"
build_gem "weakling", "0.0.3"

build_bundler "9.0.0"
end

install_gemfile <<-G
source "https://gem.repo4"
gem "myrack"
G

system_gems "bundler-9.0.0", path: local_gem_path
bundle :update, bundler: "9.0.0", verbose: true

# Sanity check: the lockfile now records the bundler checksum.
expect(lockfile).to match(/^ bundler \(9\.0\.0\) sha256=/)

# Simulate a machine where the bundler gem is not present in the cache
# (e.g. a fresh CI checkout that never downloaded bundler-9.0.0.gem).
FileUtils.rm_f Dir[local_gem_path("cache", "bundler-9.0.0.gem")]
FileUtils.rm_f Dir[system_gem_path("cache", "bundler-9.0.0.gem")]

# Force a re-resolution / lockfile rewrite.
install_gemfile <<-G
source "https://gem.repo4"
gem "myrack"
gem "weakling"
G

# The bundler checksum must survive the rewrite, since it was already locked.
expect(lockfile).to match(/^ bundler \(9\.0\.0\) sha256=/)
end

it "drops the locked bundler checksum when the bundler version changes and the gem isn't cached" do
system_gems "bundler-9.0.0"

build_repo4 do
build_gem "myrack", "1.0"

build_bundler "9.0.0"
build_bundler "9.9.9"
end

install_gemfile <<-G
source "https://gem.repo4"
gem "myrack"
G

system_gems "bundler-9.0.0", path: local_gem_path
bundle :update, bundler: "9.0.0", verbose: true

# Sanity check: the lockfile records the bundler 9.0.0 checksum and is
# locked to bundler 9.0.0.
expect(lockfile).to match(/^ bundler \(9\.0\.0\) sha256=/)
expect(lockfile).to match(/BUNDLED WITH\n\s+9\.0\.0\n/)

# Simulate a machine where the bundler gem is not present in the cache
# (e.g. a fresh CI checkout), so a fresh checksum can't be computed.
FileUtils.rm_f Dir[local_gem_path("cache", "bundler-9.0.0.gem")]
FileUtils.rm_f Dir[system_gem_path("cache", "bundler-9.0.0.gem")]

# Change the locked bundler version. `bundle lock --update --bundler` rewrites
# the BUNDLED WITH section without switching the running bundler, so the gem
# whose checksum is locked (9.0.0) is no longer the version being locked.
bundle "lock --update --bundler 9.9.9", verbose: true

# The BUNDLED WITH version was bumped...
expect(lockfile).to match(/BUNDLED WITH\n\s+9\.9\.9\n/)

# ...so the stale `bundler (9.0.0)` checksum must be dropped rather than kept,
# otherwise we'd lock a checksum that no longer matches the BUNDLED WITH
# version (and that we can't recompute since the gem isn't cached).
expect(lockfile).not_to match(/^ bundler \(/)
end

it "prints an error when trying to update bundler in frozen mode" do
system_gems "bundler-9.0.0"

Expand Down