summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorHiroshi SHIBATA <hsbt@ruby-lang.org>2021-03-02 20:37:31 +0900
committerNARUSE, Yui <nurse@users.noreply.github.com>2021-03-11 17:24:52 +0900
commitf375bc77d2f347dd2a44705b8abd29398feae427 (patch)
tree0988ab2b519e713ae653cc2e23609b339a9b5979 /lib
parent38f8b8d070aaac02f1d048b5d9947b2e58401e2b (diff)
Merge RubyGems-3.2.11 and Bundler-2.2.11
Diffstat (limited to 'lib')
-rw-r--r--lib/bundler/definition.rb74
-rw-r--r--lib/bundler/dsl.rb63
-rw-r--r--lib/bundler/feature_flag.rb1
-rw-r--r--lib/bundler/inline.rb1
-rw-r--r--lib/bundler/lockfile_parser.rb20
-rw-r--r--lib/bundler/man/bundle-config.16
-rw-r--r--lib/bundler/man/bundle-config.1.ronn8
-rw-r--r--lib/bundler/plugin.rb1
-rw-r--r--lib/bundler/plugin/installer.rb17
-rw-r--r--lib/bundler/resolver.rb62
-rw-r--r--lib/bundler/settings.rb1
-rw-r--r--lib/bundler/source/rubygems.rb11
-rw-r--r--lib/bundler/source_list.rb53
-rw-r--r--lib/bundler/version.rb2
-rw-r--r--lib/rubygems.rb2
-rw-r--r--lib/rubygems/config_file.rb9
-rw-r--r--lib/rubygems/core_ext/tcpsocket_init.rb49
-rw-r--r--lib/rubygems/remote_fetcher.rb1
18 files changed, 199 insertions, 182 deletions
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index 9178f0126f..3c25149d33 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -106,19 +106,6 @@ module Bundler
@locked_platforms = []
end
- @locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
- @disable_multisource = !Bundler.frozen_bundle? || @locked_gem_sources.none? {|s| s.remotes.size > 1 }
-
- unless @disable_multisource
- msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. " \
- "You should regenerate your lockfile in a non frozen environment."
-
- Bundler::SharedHelpers.major_deprecation 2, msg
-
- @sources.allow_multisource!
- @locked_gem_sources.each(&:allow_multisource!)
- end
-
@unlock[:gems] ||= []
@unlock[:sources] ||= []
@unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object
@@ -158,14 +145,6 @@ module Bundler
end
end
- def disable_multisource?
- @disable_multisource
- end
-
- def allow_multisource!
- @disable_multisource = false
- end
-
def resolve_with_cache!
raise "Specs already loaded" if @specs
sources.cached!
@@ -285,7 +264,7 @@ module Bundler
# Run a resolve against the locally available gems
Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}")
expanded_dependencies = expand_dependencies(dependencies + metadata_dependencies, @remote)
- Resolver.resolve(expanded_dependencies, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
+ Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
end
end
end
@@ -551,9 +530,6 @@ module Bundler
attr_reader :sources
private :sources
- attr_reader :locked_gem_sources
- private :locked_gem_sources
-
def nothing_changed?
!@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@locked_specs_incomplete_for_platform
end
@@ -678,20 +654,21 @@ module Bundler
end
def converge_rubygems_sources
- return false if disable_multisource?
+ return false if Bundler.feature_flag.disable_multisource?
- return false if locked_gem_sources.empty?
+ changes = false
+ # Get the RubyGems sources from the Gemfile.lock
+ locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
# Get the RubyGems remotes from the Gemfile
actual_remotes = sources.rubygems_remotes
- return false if actual_remotes.empty?
-
- changes = false
# If there is a RubyGems source in both
- locked_gem_sources.each do |locked_gem|
- # Merge the remotes from the Gemfile into the Gemfile.lock
- changes |= locked_gem.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes])
+ if !locked_gem_sources.empty? && !actual_remotes.empty?
+ locked_gem_sources.each do |locked_gem|
+ # Merge the remotes from the Gemfile into the Gemfile.lock
+ changes |= locked_gem.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes])
+ end
end
changes
@@ -916,18 +893,30 @@ module Bundler
# Record the specs available in each gem's source, so that those
# specs will be available later when the resolver knows where to
# look for that gemspec (or its dependencies)
- source_requirements = { :default => sources.default_source }.merge(dependency_source_requirements)
+ default = sources.default_source
+ source_requirements = { :default => default }
+ default = nil unless Bundler.feature_flag.disable_multisource?
+ dependencies.each do |dep|
+ next unless source = dep.source || default
+ source_requirements[dep.name] = source
+ end
metadata_dependencies.each do |dep|
source_requirements[dep.name] = sources.metadata_source
end
- source_requirements[:global] = index unless disable_multisource?
source_requirements[:default_bundler] = source_requirements["bundler"] || source_requirements[:default]
source_requirements["bundler"] = sources.metadata_source # needs to come last to override
source_requirements
end
def pinned_spec_names(skip = nil)
- dependency_source_requirements.reject {|_, source| source == skip }.keys
+ pinned_names = []
+ default = Bundler.feature_flag.disable_multisource? && sources.default_source
+ @dependencies.each do |dep|
+ next unless dep_source = dep.source || default
+ next if dep_source == skip
+ pinned_names << dep.name
+ end
+ pinned_names
end
def requested_groups
@@ -984,18 +973,5 @@ module Bundler
Bundler.settings[:allow_deployment_source_credential_changes] && source.equivalent_remotes?(sources.rubygems_remotes)
end
-
- def dependency_source_requirements
- @dependency_source_requirements ||= begin
- source_requirements = {}
- default = disable_multisource? && sources.default_source
- dependencies.each do |dep|
- dep_source = dep.source || default
- next unless dep_source
- source_requirements[dep.name] = dep_source
- end
- source_requirements
- end
- end
end
end
diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb
index ef5aaf669b..1cc7908b8a 100644
--- a/lib/bundler/dsl.rb
+++ b/lib/bundler/dsl.rb
@@ -24,9 +24,6 @@ module Bundler
def initialize
@source = nil
@sources = SourceList.new
-
- @global_rubygems_sources = []
-
@git_sources = {}
@dependencies = []
@groups = []
@@ -48,7 +45,6 @@ module Bundler
@gemfiles << expanded_gemfile_path
contents ||= Bundler.read_file(@gemfile.to_s)
instance_eval(contents.dup.tap{|x| x.untaint if RUBY_VERSION < "2.7" }, gemfile.to_s, 1)
- check_primary_source_safety
rescue Exception => e # rubocop:disable Lint/RescueException
message = "There was an error " \
"#{e.is_a?(GemfileEvalError) ? "evaluating" : "parsing"} " \
@@ -168,7 +164,8 @@ module Bundler
elsif block_given?
with_source(@sources.add_rubygems_source("remotes" => source), &blk)
else
- @global_rubygems_sources << source
+ check_primary_source_safety(@sources)
+ @sources.global_rubygems_source = source
end
end
@@ -186,14 +183,24 @@ module Bundler
end
def path(path, options = {}, &blk)
+ unless block_given?
+ msg = "You can no longer specify a path source by itself. Instead, \n" \
+ "either use the :path option on a gem, or specify the gems that \n" \
+ "bundler should find in the path source by passing a block to \n" \
+ "the path method, like: \n\n" \
+ " path 'dir/containing/rails' do\n" \
+ " gem 'rails'\n" \
+ " end\n\n"
+
+ raise DeprecatedError, msg if Bundler.feature_flag.disable_multisource?
+ SharedHelpers.major_deprecation(2, msg.strip)
+ end
+
source_options = normalize_hash(options).merge(
"path" => Pathname.new(path),
"root_path" => gemfile_root,
"gemspec" => gemspecs.find {|g| g.name == options["name"] }
)
-
- source_options["global"] = true unless block_given?
-
source = @sources.add_path_source(source_options)
with_source(source, &blk)
end
@@ -272,11 +279,6 @@ module Bundler
raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile"
end
- def check_primary_source_safety
- check_path_source_safety
- check_rubygems_source_safety
- end
-
private
def add_git_sources
@@ -438,40 +440,25 @@ repo_name ||= user_name
end
end
- def check_path_source_safety
- return if @sources.global_path_source.nil?
-
- msg = "You can no longer specify a path source by itself. Instead, \n" \
- "either use the :path option on a gem, or specify the gems that \n" \
- "bundler should find in the path source by passing a block to \n" \
- "the path method, like: \n\n" \
- " path 'dir/containing/rails' do\n" \
- " gem 'rails'\n" \
- " end\n\n"
+ def check_primary_source_safety(source_list)
+ return if source_list.rubygems_primary_remotes.empty? && source_list.global_rubygems_source.nil?
- SharedHelpers.major_deprecation(2, msg.strip)
- end
-
- def check_rubygems_source_safety
- if @global_rubygems_sources.size <= 1
- @sources.global_rubygems_source = @global_rubygems_sources.first
- return
- end
-
- @global_rubygems_sources.each do |source|
- @sources.add_rubygems_remote(source)
- end
-
- if Bundler.feature_flag.bundler_3_mode?
+ if Bundler.feature_flag.disable_multisource?
msg = "This Gemfile contains multiple primary sources. " \
"Each source after the first must include a block to indicate which gems " \
"should come from that source"
+ unless Bundler.feature_flag.bundler_2_mode?
+ msg += ". To downgrade this error to a warning, run " \
+ "`bundle config unset disable_multisource`"
+ end
raise GemfileEvalError, msg
else
Bundler::SharedHelpers.major_deprecation 2, "Your Gemfile contains multiple primary sources. " \
"Using `source` more than once without a block is a security risk, and " \
"may result in installing unexpected gems. To resolve this warning, use " \
- "a block to indicate which gems should come from the secondary source."
+ "a block to indicate which gems should come from the secondary source. " \
+ "To upgrade this warning to an error, run `bundle config set --local " \
+ "disable_multisource true`."
end
end
diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb
index 9b7172fa35..a1b443b042 100644
--- a/lib/bundler/feature_flag.rb
+++ b/lib/bundler/feature_flag.rb
@@ -32,6 +32,7 @@ module Bundler
settings_flag(:cache_all) { bundler_3_mode? }
settings_flag(:default_install_uses_path) { bundler_3_mode? }
settings_flag(:deployment_means_frozen) { bundler_3_mode? }
+ settings_flag(:disable_multisource) { bundler_3_mode? }
settings_flag(:forget_cli_options) { bundler_3_mode? }
settings_flag(:global_gem_cache) { bundler_3_mode? }
settings_flag(:only_update_to_newer_versions) { bundler_3_mode? }
diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb
index 02da06cee9..59211193d4 100644
--- a/lib/bundler/inline.rb
+++ b/lib/bundler/inline.rb
@@ -50,7 +50,6 @@ def gemfile(install = false, options = {}, &gemfile)
Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins?
builder = Bundler::Dsl.new
builder.instance_eval(&gemfile)
- builder.check_primary_source_safety
Bundler.settings.temporary(:frozen => false) do
definition = builder.to_definition(nil, true)
diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb
index 058d353bbe..f836737621 100644
--- a/lib/bundler/lockfile_parser.rb
+++ b/lib/bundler/lockfile_parser.rb
@@ -64,6 +64,8 @@ module Bundler
@state = nil
@specs = {}
+ @rubygems_aggregate = Source::Rubygems.new
+
if lockfile.match(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/)
raise LockfileError, "Your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} contains merge conflicts.\n" \
"Run `git checkout HEAD -- #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` first to get a clean lock."
@@ -87,6 +89,7 @@ module Bundler
send("parse_#{@state}", line)
end
end
+ @sources << @rubygems_aggregate unless Bundler.feature_flag.disable_multisource?
@specs = @specs.values.sort_by(&:identifier)
warn_for_outdated_bundler_version
rescue ArgumentError => e
@@ -131,19 +134,16 @@ module Bundler
@sources << @current_source
end
when GEM
- source_remotes = Array(@opts["remote"])
-
- if source_remotes.size == 1
+ if Bundler.feature_flag.disable_multisource?
@opts["remotes"] = @opts.delete("remote")
@current_source = TYPES[@type].from_lock(@opts)
+ @sources << @current_source
else
- source_remotes.each do |url|
- rubygems_aggregate.add_remote(url)
+ Array(@opts["remote"]).each do |url|
+ @rubygems_aggregate.add_remote(url)
end
- @current_source = rubygems_aggregate
+ @current_source = @rubygems_aggregate
end
-
- @sources << @current_source
when PLUGIN
@current_source = Plugin.source_from_lock(@opts)
@sources << @current_source
@@ -245,9 +245,5 @@ module Bundler
def parse_ruby(line)
@ruby_version = line.strip
end
-
- def rubygems_aggregate
- @rubygems_aggregate ||= Source::Rubygems.new
- end
end
end
diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1
index da628a7dcc..00bb708240 100644
--- a/lib/bundler/man/bundle-config.1
+++ b/lib/bundler/man/bundle-config.1
@@ -56,6 +56,9 @@ Executing \fBbundle config unset \-\-local <name> <value>\fR will delete the con
.P
Executing bundle with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\.
.
+.P
+Executing \fBbundle config set \-\-local disable_multisource true\fR upgrades the warning about the Gemfile containing multiple primary sources to an error\. Executing \fBbundle config unset disable_multisource\fR downgrades this error to a warning\.
+.
.SH "REMEMBERING OPTIONS"
Flags passed to \fBbundle install\fR or the Bundler runtime, such as \fB\-\-path foo\fR or \fB\-\-without production\fR, are remembered between commands and saved to your local application\'s configuration (normally, \fB\./\.bundle/config\fR)\.
.
@@ -181,6 +184,9 @@ The following is a list of all configuration keys and their purpose\. You can le
\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\.
.
.IP "\(bu" 4
+\fBdisable_multisource\fR (\fBBUNDLE_DISABLE_MULTISOURCE\fR): When set, Gemfiles containing multiple sources will produce errors instead of warnings\. Use \fBbundle config unset disable_multisource\fR to unset\.
+.
+.IP "\(bu" 4
\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems\' normal location\.
.
.IP "\(bu" 4
diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn
index 81d0603e8b..dc99c69412 100644
--- a/lib/bundler/man/bundle-config.1.ronn
+++ b/lib/bundler/man/bundle-config.1.ronn
@@ -47,6 +47,10 @@ configuration only from the local application.
Executing bundle with the `BUNDLE_IGNORE_CONFIG` environment variable set will
cause it to ignore all configuration.
+Executing `bundle config set --local disable_multisource true` upgrades the warning about
+the Gemfile containing multiple primary sources to an error. Executing `bundle
+config unset disable_multisource` downgrades this error to a warning.
+
## REMEMBERING OPTIONS
Flags passed to `bundle install` or the Bundler runtime, such as `--path foo` or
@@ -174,6 +178,10 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
* `disable_local_revision_check` (`BUNDLE_DISABLE_LOCAL_REVISION_CHECK`):
Allow Bundler to use a local git override without checking if the revision
present in the lockfile is present in the repository.
+* `disable_multisource` (`BUNDLE_DISABLE_MULTISOURCE`):
+ When set, Gemfiles containing multiple sources will produce errors
+ instead of warnings.
+ Use `bundle config unset disable_multisource` to unset.
* `disable_shared_gems` (`BUNDLE_DISABLE_SHARED_GEMS`):
Stop Bundler from accessing gems installed to RubyGems' normal location.
* `disable_version_check` (`BUNDLE_DISABLE_VERSION_CHECK`):
diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb
index dddb468582..da3f468da5 100644
--- a/lib/bundler/plugin.rb
+++ b/lib/bundler/plugin.rb
@@ -105,7 +105,6 @@ module Bundler
else
builder.eval_gemfile(gemfile)
end
- builder.check_primary_source_safety
definition = builder.to_definition(nil, true)
return if definition.dependencies.empty?
diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb
index 73198aec04..26cec4f18c 100644
--- a/lib/bundler/plugin/installer.rb
+++ b/lib/bundler/plugin/installer.rb
@@ -16,13 +16,15 @@ module Bundler
version = options[:version] || [">= 0"]
- if options[:git]
- install_git(names, version, options)
- elsif options[:local_git]
- install_local_git(names, version, options)
- else
- sources = options[:source] || Bundler.rubygems.sources
- install_rubygems(names, version, sources)
+ Bundler.settings.temporary(:disable_multisource => false) do
+ if options[:git]
+ install_git(names, version, options)
+ elsif options[:local_git]
+ install_local_git(names, version, options)
+ else
+ sources = options[:source] || Bundler.rubygems.sources
+ install_rubygems(names, version, sources)
+ end
end
end
@@ -82,7 +84,6 @@ module Bundler
deps = names.map {|name| Dependency.new name, version }
definition = Definition.new(nil, deps, source_list, true)
- definition.allow_multisource!
install_definition(definition)
end
diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb
index 0c11c83b72..585b8667ad 100644
--- a/lib/bundler/resolver.rb
+++ b/lib/bundler/resolver.rb
@@ -17,14 +17,15 @@ module Bundler
# ==== Returns
# <GemBundle>,nil:: If the list of dependencies can be resolved, a
# collection of gemspecs is returned. Otherwise, nil is returned.
- def self.resolve(requirements, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil)
+ def self.resolve(requirements, index, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil)
base = SpecSet.new(base) unless base.is_a?(SpecSet)
- resolver = new(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms)
+ resolver = new(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms)
result = resolver.start(requirements)
SpecSet.new(result)
end
- def initialize(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms)
+ def initialize(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms)
+ @index = index
@source_requirements = source_requirements
@base = base
@resolver = Molinillo::Resolver.new(self, self)
@@ -39,7 +40,7 @@ module Bundler
@resolving_only_for_ruby = platforms == [Gem::Platform::RUBY]
@gem_version_promoter = gem_version_promoter
@use_gvp = Bundler.feature_flag.use_gem_version_promoter_for_major_updates? || !@gem_version_promoter.major?
- @no_aggregate_global_source = @source_requirements[:global].nil?
+ @lockfile_uses_separate_rubygems_sources = Bundler.feature_flag.disable_multisource?
@variant_specific_names = []
@generic_names = ["Ruby\0", "RubyGems\0"]
@@ -124,7 +125,8 @@ module Bundler
dependency = dependency_proxy.dep
name = dependency.name
search_result = @search_for[dependency_proxy] ||= begin
- results = results_for(dependency, @base[name])
+ index = index_for(dependency)
+ results = index.search(dependency, @base[name])
if vertex = @base_dg.vertex_named(name)
locked_requirement = vertex.payload.requirement
@@ -193,26 +195,23 @@ module Bundler
search_result
end
- def index_for(dependency, base)
+ def index_for(dependency)
source = @source_requirements[dependency.name]
if source
source.specs
- elsif @no_aggregate_global_source
- dependency.all_sources.find(-> { Index.new }) do |s|
- idx = s.specs
- results = idx.search(dependency, base)
- next if results.empty? || results == base
- return idx
+ elsif @lockfile_uses_separate_rubygems_sources
+ Index.build do |idx|
+ if dependency.all_sources
+ dependency.all_sources.each {|s| idx.add_source(s.specs) if s }
+ else
+ idx.add_source @source_requirements[:default].specs
+ end
end
else
- @source_requirements[:global]
+ @index
end
end
- def results_for(dependency, base)
- index_for(dependency, base).search(dependency, base)
- end
-
def name_for(dependency)
dependency.name
end
@@ -239,13 +238,11 @@ module Bundler
def relevant_sources_for_vertex(vertex)
if vertex.root?
- [@source_requirements[vertex.name]].compact
- elsif @no_aggregate_global_source
+ [@source_requirements[vertex.name]]
+ elsif @lockfile_uses_separate_rubygems_sources
vertex.recursive_predecessors.map do |v|
@source_requirements[v.name]
- end.compact << @source_requirements[:default]
- else
- []
+ end << @source_requirements[:default]
end
end
@@ -286,7 +283,7 @@ module Bundler
if (base = @base[dependency.name]) && !base.empty?
dependency.requirement.satisfied_by?(base.first.version) ? 0 : 1
else
- all = index_for(dependency, base).search(dependency.name).size
+ all = index_for(dependency).search(dependency.name).size
if all <= 1
all - 1_000_000
@@ -329,7 +326,7 @@ module Bundler
"The source does not contain any versions of '#{name}'"
end
else
- message = "Could not find gem '#{SharedHelpers.pretty_dependency(requirement)}' in any of the gem sources " \
+ message = "Could not find gem '#{requirement}' in any of the gem sources " \
"listed in your Gemfile#{cache_message}."
end
raise GemNotFound, message
@@ -414,8 +411,14 @@ module Bundler
relevant_sources = if conflict.requirement.source
[conflict.requirement.source]
- else
+ elsif conflict.requirement.all_sources
conflict.requirement.all_sources
+ elsif @lockfile_uses_separate_rubygems_sources
+ # every conflict should have an explicit group of sources when we
+ # enforce strict pinning
+ raise "no source set for #{conflict}"
+ else
+ []
end.compact.map(&:to_s).uniq.sort
metadata_requirement = name.end_with?("\0")
@@ -452,8 +455,7 @@ module Bundler
def validate_resolved_specs!(resolved_specs)
resolved_specs.each do |v|
name = v.name
- sources = relevant_sources_for_vertex(v)
- next unless sources.any?
+ next unless sources = relevant_sources_for_vertex(v)
sources.compact!
if default_index = sources.index(@source_requirements[:default])
sources.delete_at(default_index)
@@ -462,12 +464,14 @@ module Bundler
sources.uniq!
next if sources.size <= 1
+ multisource_disabled = Bundler.feature_flag.disable_multisource?
+
msg = ["The gem '#{name}' was found in multiple relevant sources."]
msg.concat sources.map {|s| " * #{s}" }.sort
- msg << "You #{@no_aggregate_global_source ? :must : :should} add this gem to the source block for the source you wish it to be installed from."
+ msg << "You #{multisource_disabled ? :must : :should} add this gem to the source block for the source you wish it to be installed from."
msg = msg.join("\n")
- raise SecurityError, msg if @no_aggregate_global_source
+ raise SecurityError, msg if multisource_disabled
Bundler.ui.warn "Warning: #{msg}"
end
end
diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb
index ceb2fde32c..749e4eb60e 100644
--- a/lib/bundler/settings.rb
+++ b/lib/bundler/settings.rb
@@ -20,6 +20,7 @@ module Bundler
disable_exec_load
disable_local_branch_check
disable_local_revision_check
+ disable_multisource
disable_shared_gems
disable_version_check
force_ruby_platform
diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb
index 1b4bccadb8..5b89b1645d 100644
--- a/lib/bundler/source/rubygems.rb
+++ b/lib/bundler/source/rubygems.rb
@@ -21,7 +21,6 @@ module Bundler
@allow_remote = false
@allow_cached = false
@caches = [cache_path, *Bundler.rubygems.gem_cache]
- @disable_multisource = true
Array(options["remotes"] || []).reverse_each {|r| add_remote(r) }
end
@@ -50,16 +49,8 @@ module Bundler
o.is_a?(Rubygems) && (o.credless_remotes - credless_remotes).empty?
end
- def disable_multisource?
- @disable_multisource
- end
-
- def allow_multisource!
- @disable_multisource = false
- end
-
def can_lock?(spec)
- return super if disable_multisource?
+ return super if Bundler.feature_flag.disable_multisource?
spec.source.is_a?(Rubygems)
end
diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb
index 4ba6415448..436891256d 100644
--- a/lib/bundler/source_list.rb
+++ b/lib/bundler/source_list.rb
@@ -5,41 +5,24 @@ module Bundler
attr_reader :path_sources,
:git_sources,
:plugin_sources,
- :global_path_source,
- :metadata_source,
- :disable_multisource
-
- def global_rubygems_source
- @global_rubygems_source ||= rubygems_aggregate_class.new
- end
+ :global_rubygems_source,
+ :metadata_source
def initialize
@path_sources = []
@git_sources = []
@plugin_sources = []
@global_rubygems_source = nil
- @global_path_source = nil
+ @rubygems_aggregate = rubygems_aggregate_class.new
@rubygems_sources = []
@metadata_source = Source::Metadata.new
- @disable_multisource = true
- end
-
- def disable_multisource?
- @disable_multisource
- end
-
- def allow_multisource!
- rubygems_sources.map(&:allow_multisource!)
- @disable_multisource = false
end
def add_path_source(options = {})
if options["gemspec"]
add_source_to_list Source::Gemspec.new(options), path_sources
else
- path_source = add_source_to_list Source::Path.new(options), path_sources
- @global_path_source ||= path_source if options["global"]
- path_source
+ add_source_to_list Source::Path.new(options), path_sources
end
end
@@ -58,20 +41,24 @@ module Bundler
end
def global_rubygems_source=(uri)
- @global_rubygems_source ||= rubygems_aggregate_class.new("remotes" => uri)
+ if Bundler.feature_flag.disable_multisource?
+ @global_rubygems_source ||= rubygems_aggregate_class.new("remotes" => uri)
+ end
+ add_rubygems_remote(uri)
end
def add_rubygems_remote(uri)
- global_rubygems_source.add_remote(uri)
- global_rubygems_source
+ return if Bundler.feature_flag.disable_multisource?
+ @rubygems_aggregate.add_remote(uri)
+ @rubygems_aggregate
end
def default_source
- global_path_source || global_rubygems_source
+ global_rubygems_source || @rubygems_aggregate
end
def rubygems_sources
- @rubygems_sources + [global_rubygems_source]
+ @rubygems_sources + [default_source]
end
def rubygems_remotes
@@ -88,7 +75,7 @@ module Bundler
def lock_sources
lock_sources = (path_sources + git_sources + plugin_sources).sort_by(&:to_s)
- if disable_multisource?
+ if Bundler.feature_flag.disable_multisource?
lock_sources + rubygems_sources.sort_by(&:to_s)
else
lock_sources << combine_rubygems_sources
@@ -105,9 +92,9 @@ module Bundler
end
end
- replacement_rubygems = !disable_multisource? &&
+ replacement_rubygems = !Bundler.feature_flag.disable_multisource? &&
replacement_sources.detect {|s| s.is_a?(Source::Rubygems) }
- @global_rubygems_source = replacement_rubygems if replacement_rubygems
+ @rubygems_aggregate = replacement_rubygems if replacement_rubygems
return true if !equal_sources?(lock_sources, replacement_sources) && !equivalent_sources?(lock_sources, replacement_sources)
return true if replacement_rubygems && rubygems_remotes.sort_by(&:to_s) != replacement_rubygems.remotes.sort_by(&:to_s)
@@ -123,6 +110,10 @@ module Bundler
all_sources.each(&:remote!)
end
+ def rubygems_primary_remotes
+ @rubygems_aggregate.remotes
+ end
+
private
def rubygems_aggregate_class
@@ -145,9 +136,7 @@ module Bundler
end
def combine_rubygems_sources
- aggregate_source = Source::Rubygems.new("remotes" => rubygems_remotes)
- aggregate_source.allow_multisource! unless disable_multisource?
- aggregate_source
+ Source::Rubygems.new("remotes" => rubygems_remotes)
end
def warn_on_git_protocol(source)
diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb
index 0ddfa668cd..64c46ddb25 100644
--- a/lib/bundler/version.rb
+++ b/lib/bundler/version.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: false
module Bundler
- VERSION = "2.2.10".freeze
+ VERSION = "2.2.11".freeze
def self.bundler_major_version
@bundler_major_version ||= VERSION.split(".").first.to_i
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index 22fe0366b2..d5f6356822 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -8,7 +8,7 @@
require 'rbconfig'
module Gem
- VERSION = "3.2.10".freeze
+ VERSION = "3.2.11".freeze
end
# Must be first since it unloads the prelude from 1.9.2
diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb
index 854d09ef3d..9dc41a2995 100644
--- a/lib/rubygems/config_file.rb
+++ b/lib/rubygems/config_file.rb
@@ -45,6 +45,7 @@ class Gem::ConfigFile
DEFAULT_UPDATE_SOURCES = true
DEFAULT_CONCURRENT_DOWNLOADS = 8
DEFAULT_CERT_EXPIRATION_LENGTH_DAYS = 365
+ DEFAULT_IPV4_FALLBACK_ENABLED = false
##
# For Ruby packagers to set configuration defaults. Set in
@@ -141,6 +142,12 @@ class Gem::ConfigFile
attr_accessor :cert_expiration_length_days
##
+ # == Experimental ==
+ # Fallback to IPv4 when IPv6 is not reachable or slow (default: false)
+
+ attr_accessor :ipv4_fallback_enabled
+
+ ##
# Path name of directory or file of openssl client certificate, used for remote https connection with client authentication
attr_reader :ssl_client_cert
@@ -175,6 +182,7 @@ class Gem::ConfigFile
@update_sources = DEFAULT_UPDATE_SOURCES
@concurrent_downloads = DEFAULT_CONCURRENT_DOWNLOADS
@cert_expiration_length_days = DEFAULT_CERT_EXPIRATION_LENGTH_DAYS
+ @ipv4_fallback_enabled = ENV['IPV4_FALLBACK_ENABLED'] == 'true' || DEFAULT_IPV4_FALLBACK_ENABLED
operating_system_config = Marshal.load Marshal.dump(OPERATING_SYSTEM_DEFAULTS)
platform_config = Marshal.load Marshal.dump(PLATFORM_DEFAULTS)
@@ -203,6 +211,7 @@ class Gem::ConfigFile
@disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server
@sources = @hash[:sources] if @hash.key? :sources
@cert_expiration_length_days = @hash[:cert_expiration_length_days] if @hash.key? :cert_expiration_length_days
+ @ipv4_fallback_enabled = @hash[:ipv4_fallback_enabled] if @hash.key? :ipv4_fallback_enabled
@ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode
@ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert
diff --git a/lib/rubygems/core_ext/tcpsocket_init.rb b/lib/rubygems/core_ext/tcpsocket_init.rb
new file mode 100644
index 0000000000..3275c58112
--- /dev/null
+++ b/lib/rubygems/core_ext/tcpsocket_init.rb
@@ -0,0 +1,49 @@
+require 'socket'
+
+module CoreExtensions
+ module TCPSocketExt
+ def self.prepended(base)
+ base.prepend Initializer
+ end
+
+ module Initializer
+ CONNECTION_TIMEOUT = 5
+ IPV4_DELAY_SECONDS = 0.1
+
+ def initialize(host, serv, *rest)
+ mutex = Mutex.new
+ addrs = []
+ cond_var = ConditionVariable.new
+
+ Addrinfo.foreach(host, serv, nil, :STREAM) do |addr|
+ Thread.report_on_exception = false if defined? Thread.report_on_exception = ()
+
+ Thread.new(addr) do
+ # give head start to ipv6 addresses
+ sleep IPV4_DELAY_SECONDS if addr.ipv4?
+
+ # raises Errno::ECONNREFUSED when ip:port is unreachable
+ Socket.tcp(addr.ip_address, serv, connect_timeout: CONNECTION_TIMEOUT).close
+ mutex.synchronize do
+ addrs << addr.ip_address
+ cond_var.signal
+ end
+ end
+ end
+
+ mutex.synchronize do
+ timeout_time = CONNECTION_TIMEOUT + Time.now.to_f
+ while addrs.empty? && (remaining_time = timeout_time - Time.now.to_f) > 0
+ cond_var.wait(mutex, remaining_time)
+ end
+
+ host = addrs.shift unless addrs.empty?
+ end
+
+ super(host, serv, *rest)
+ end
+ end
+ end
+end
+
+TCPSocket.prepend CoreExtensions::TCPSocketExt
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
index 53e840978c..7ac334d30d 100644
--- a/lib/rubygems/remote_fetcher.rb
+++ b/lib/rubygems/remote_fetcher.rb
@@ -78,6 +78,7 @@ class Gem::RemoteFetcher
# fetching the gem.
def initialize(proxy=nil, dns=nil, headers={})
+ require 'rubygems/core_ext/tcpsocket_init' if Gem.configuration.ipv4_fallback_enabled
require 'net/http'
require 'stringio'
require 'uri'