From dce202d6d653dfc1b2c64822fe53066c3c558a78 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Thu, 9 Oct 2025 17:23:43 +0200 Subject: [PATCH] [rubygems/rubygems] Add checksum of gems hosted on private servers: - ### Problem Running `bundle lock --add-checksums` doesn't add the checksum of gems hosted on server that don't implement the compact index API. This result in a lockfile which is unusable in production as some checksums will be missing and Bundler raising an error. Users can work around this problem by running: `BUNDLE_LOCKFILE_CHECKSUMS=true bundle install --force` But this means redownloading and installing all gems which isn't great and slow on large apps. ### Context Bundler uses the Compact Index API to get the checksum of gems, but most private gem servers don't implement the compact index API (such as cloudsmith or packagecloud). This results in a soft failure on bundler side, and bundler leaving out blank checksum for those gems. ### Solution For gems that are hosted on private servers that don't send back the checksum of the gem, I'd like to fallback to the `bundle install` mechanism, which don't rely on an external API but instead compute the checksum of the package installed on disk. This patch goes through the spec that didn't return a checksum, and compute one if the package exists on disk. This solution makes the `bundle lock --add-checksums` command actually usable in real world scenarios while keeping the `bundle lock` command fast enough. https://github.com/rubygems/rubygems/commit/8e9abb5472 --- lib/bundler/definition.rb | 13 ++++- spec/bundler/commands/lock_spec.rb | 91 ++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 6cae8964d8..3c8c13b130 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -540,7 +540,18 @@ module Bundler setup_domain!(add_checksums: true) - specs # force materialization to real specifications, so that checksums are fetched + # force materialization to real specifications, so that checksums are fetched + specs.each do |spec| + next unless spec.source.is_a?(Bundler::Source::Rubygems) + # Checksum was fetched from the compact index API. + next if !spec.source.checksum_store.missing?(spec) && !spec.source.checksum_store.empty?(spec) + # The gem isn't installed, can't compute the checksum. + next unless spec.loaded_from + + package = Gem::Package.new(spec.source.cached_built_in_gem(spec)) + checksum = Checksum.from_gem_package(package) + spec.source.checksum_store.register(spec, checksum) + end end private diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index a7460ed695..36493e108a 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -2174,6 +2174,97 @@ RSpec.describe "bundle lock" do L end + it "add checksums for gems installed on disk" do + build_repo4 do + build_gem "warning", "18.0.0" + end + + bundle "config lockfile_checksums false" + + simulate_platform "x86_64-linux" do + install_gemfile(<<-G, artifice: "endpoint") + source "https://gem.repo4" + + gem "warning" + G + + bundle "config --delete lockfile_checksums" + bundle("lock --add-checksums", artifice: "endpoint") + end + + checksums = checksums_section do |c| + c.checksum gem_repo4, "warning", "18.0.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + warning (18.0.0) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + warning + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "doesn't add checksum for gems not installed on disk" do + lockfile(<<~L) + GEM + remote: https://gem.repo4/ + specs: + warning (18.0.0) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + warning + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile(<<~G) + source "https://gem.repo4" + + gem "warning" + G + + build_repo4 do + build_gem "warning", "18.0.0" + end + + FileUtils.rm_rf("#{gem_repo4}/gems") + + bundle("lock --add-checksums", artifice: "endpoint") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + warning (18.0.0) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + warning + + CHECKSUMS + warning (18.0.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + context "when re-resolving to include prereleases" do before do build_repo4 do