[rubygems/rubygems] Refactor Fetcher#api_fetcher? and fetcher loading logic

https://github.com/rubygems/rubygems/commit/f664d60114
This commit is contained in:
Martin Emde 2023-08-21 14:05:57 -07:00 committed by Hiroshi SHIBATA
parent 0c9d0684e1
commit 7bf5f78028
5 changed files with 108 additions and 38 deletions

View File

@ -9,6 +9,7 @@ require "rubygems/request"
module Bundler
# Handles all the fetching with the rubygems server
class Fetcher
autoload :Base, File.expand_path("fetcher/base", __dir__)
autoload :CompactIndex, File.expand_path("fetcher/compact_index", __dir__)
autoload :Downloader, File.expand_path("fetcher/downloader", __dir__)
autoload :Dependency, File.expand_path("fetcher/dependency", __dir__)
@ -134,18 +135,7 @@ module Bundler
def specs(gem_names, source)
index = Bundler::Index.new
if Bundler::Fetcher.disable_endpoint
@use_api = false
specs = fetchers.last.specs(gem_names)
else
specs = []
@fetchers = fetchers.drop_while do |f|
!f.available? || (f.api_fetcher? && !gem_names) || !specs = f.specs(gem_names)
end
@use_api = false if fetchers.none?(&:api_fetcher?)
end
specs.each do |name, version, platform, dependencies, metadata|
fetch_specs(gem_names).each do |name, version, platform, dependencies, metadata|
spec = if dependencies
EndpointSpecification.new(name, version, platform, self, dependencies, metadata)
else
@ -158,22 +148,10 @@ module Bundler
index
rescue CertificateFailureError
Bundler.ui.info "" if gem_names && use_api # newline after dots
Bundler.ui.info "" if gem_names && api_fetcher? # newline after dots
raise
end
def use_api
return @use_api if defined?(@use_api)
fetchers.shift until fetchers.first.available?
@use_api = if remote_uri.scheme == "file" || Bundler::Fetcher.disable_endpoint
false
else
fetchers.first.api_fetcher?
end
end
def user_agent
@user_agent ||= begin
ruby = Bundler::RubyVersion.system
@ -209,10 +187,6 @@ module Bundler
end
end
def fetchers
@fetchers ||= FETCHERS.map {|f| f.new(downloader, @remote, uri) }
end
def http_proxy
return unless uri = connection.proxy_uri
uri.to_s
@ -222,9 +196,36 @@ module Bundler
"#<#{self.class}:0x#{object_id} uri=#{uri}>"
end
def api_fetcher?
fetchers.first.api_fetcher?
end
private
FETCHERS = [CompactIndex, Dependency, Index].freeze
def available_fetchers
if Bundler::Fetcher.disable_endpoint
[Index]
elsif remote_uri.scheme == "file"
Bundler.ui.debug("Using a local server, bundler won't use the CompactIndex API")
[Index]
else
[CompactIndex, Dependency, Index]
end
end
def fetchers
@fetchers ||= available_fetchers.map {|f| f.new(downloader, @remote, uri) }.drop_while {|f| !f.available? }
end
def fetch_specs(gem_names)
fetchers.reject!(&:api_fetcher?) unless gem_names
fetchers.reject! do |f|
specs = f.specs(gem_names)
return specs if specs
true
end
[]
end
def cis
env_cis = {

View File

@ -60,10 +60,6 @@ module Bundler
Bundler.ui.debug("FIPS mode is enabled, bundler can't use the CompactIndex API")
return nil
end
if fetch_uri.scheme == "file"
Bundler.ui.debug("Using a local server, bundler won't use the CompactIndex API")
return false
end
# Read info file checksums out of /versions, so we can know if gems are up to date
compact_index_client.update_and_parse_checksums!
rescue CompactIndexClient::Updater::MisMatchedChecksumError => e

View File

@ -37,9 +37,9 @@ module Bundler
root_version = Resolver::Candidate.new(0)
@all_specs = Hash.new do |specs, name|
specs[name] = source_for(name).specs.search(name).reject do |s|
s.dependencies.any? {|d| d.name == name && !d.requirement.satisfied_by?(s.version) } # ignore versions that depend on themselves incorrectly
end.sort_by {|s| [s.version, s.platform.to_s] }
matches = source_for(name).specs.search(name)
matches = filter_invalid_self_dependencies(matches, name)
specs[name] = matches.sort_by {|s| [s.version, s.platform.to_s] }
end
@sorted_versions = Hash.new do |candidates, package|
@ -318,6 +318,13 @@ module Bundler
specs.reject {|s| s.version.prerelease? }
end
# Ignore versions that depend on themselves incorrectly
def filter_invalid_self_dependencies(specs, name)
specs.reject do |s|
s.dependencies.any? {|d| d.name == name && !d.requirement.satisfied_by?(s.version) }
end
end
def requirement_satisfied_by?(requirement, spec)
requirement.satisfied_by?(spec.version) || spec.source.is_a?(Source::Gemspec)
end

View File

@ -392,7 +392,7 @@ module Bundler
end
def api_fetchers
fetchers.select {|f| f.use_api && f.fetchers.first.api_fetcher? }
fetchers.select(&:api_fetcher?)
end
def remote_specs

View File

@ -189,4 +189,70 @@ RSpec.describe Bundler::Fetcher do
end
end
end
describe "#specs_with_retry" do
let(:downloader) { double(:downloader) }
let(:remote) { double(:remote, :cache_slug => "slug", :uri => uri, :original_uri => nil, :anonymized_uri => uri) }
let(:compact_index) { double(Bundler::Fetcher::CompactIndex, :available? => true, :api_fetcher? => true) }
let(:dependency) { double(Bundler::Fetcher::Dependency, :available? => true, :api_fetcher? => true) }
let(:index) { double(Bundler::Fetcher::Index, :available? => true, :api_fetcher? => false) }
before do
allow(Bundler::Fetcher::CompactIndex).to receive(:new).and_return(compact_index)
allow(Bundler::Fetcher::Dependency).to receive(:new).and_return(dependency)
allow(Bundler::Fetcher::Index).to receive(:new).and_return(index)
end
it "picks the first fetcher that works" do
expect(compact_index).to receive(:specs).with("name").and_return([["name", "1.2.3", "ruby"]])
expect(dependency).not_to receive(:specs)
expect(index).not_to receive(:specs)
fetcher.specs_with_retry("name", double(Bundler::Source::Rubygems))
end
context "when APIs are not available" do
before do
allow(compact_index).to receive(:available?).and_return(false)
allow(dependency).to receive(:available?).and_return(false)
end
it "uses the index" do
expect(compact_index).not_to receive(:specs)
expect(dependency).not_to receive(:specs)
expect(index).to receive(:specs).with("name").and_return([["name", "1.2.3", "ruby"]])
fetcher.specs_with_retry("name", double(Bundler::Source::Rubygems))
end
end
end
describe "#api_fetcher?" do
let(:downloader) { double(:downloader) }
let(:remote) { double(:remote, :cache_slug => "slug", :uri => uri, :original_uri => nil, :anonymized_uri => uri) }
let(:compact_index) { double(Bundler::Fetcher::CompactIndex, :available? => false, :api_fetcher? => true) }
let(:dependency) { double(Bundler::Fetcher::Dependency, :available? => false, :api_fetcher? => true) }
let(:index) { double(Bundler::Fetcher::Index, :available? => true, :api_fetcher? => false) }
before do
allow(Bundler::Fetcher::CompactIndex).to receive(:new).and_return(compact_index)
allow(Bundler::Fetcher::Dependency).to receive(:new).and_return(dependency)
allow(Bundler::Fetcher::Index).to receive(:new).and_return(index)
end
context "when an api fetcher is available" do
before do
allow(compact_index).to receive(:available?).and_return(true)
end
it "is truthy" do
expect(fetcher).to be_api_fetcher
end
end
context "when only the index fetcher is available" do
it "is falsey" do
expect(fetcher).not_to be_api_fetcher
end
end
end
end