diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index 0cf2da007aa..8794b6909b6 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -43,9 +43,12 @@ def initialize(index, source_requirements, base, gem_version_promoter, additiona def start(requirements) verify_gemfile_dependencies_are_found!(requirements) dg = @resolver.resolve(requirements, @base_dg) - dg.map(&:payload). + dg. + tap {|resolved| validate_resolved_specs!(resolved) }. + map(&:payload). reject {|sg| sg.name.end_with?("\0") }. - map(&:to_specs).flatten + map(&:to_specs). + flatten rescue Molinillo::VersionConflict => e message = version_conflict_message(e) raise VersionConflict.new(e.conflicts.keys.uniq, message) @@ -353,5 +356,29 @@ def version_conflict_message(e) :version_for_spec => lambda {|spec| spec.version } ) end + + def validate_resolved_specs!(resolved_specs) + resolved_specs.each do |v| + name = v.name + next unless sources = relevant_sources_for_vertex(v) + sources.compact! + if default_index = sources.index(@source_requirements[:default]) + sources.delete_at(default_index) + end + sources.reject! {|s| s.specs[name].empty? } + 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 #{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 multisource_disabled + Bundler.ui.error "Warning: #{msg}" + end + end end end diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 424965771a4..e8a180f5db7 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -7,7 +7,7 @@ module Bundler # We're doing this because we might write tests that deal # with other versions of bundler and we are unsure how to # handle this better. - VERSION = "1.15.4" unless defined?(::Bundler::VERSION) + VERSION = "2.15.4" unless defined?(::Bundler::VERSION) def self.overwrite_loaded_gem_version begin diff --git a/spec/install/gemfile/sources_spec.rb b/spec/install/gemfile/sources_spec.rb index 0b837f87a10..8d69a663de5 100644 --- a/spec/install/gemfile/sources_spec.rb +++ b/spec/install/gemfile/sources_spec.rb @@ -545,7 +545,7 @@ end it "does not re-resolve" do - bundle :install, :verbose => true + bundle! :install, :verbose => true expect(out).to include("using resolution from the lockfile") expect(out).not_to include("re-resolving dependencies") end @@ -617,4 +617,31 @@ end end end + + context "when a gem is available from multiple ambiguous sources", :bundler => "2" do + it "raises, suggesting a source block" do + build_repo4 do + build_gem "depends_on_rack" do |s| + s.add_dependency "rack" + end + build_gem "rack" + end + + install_gemfile <<-G + source "file:#{gem_repo4}" + source "file:#{gem_repo1}" do + gem "thin" + end + gem "depends_on_rack" + G + expect(last_command).to be_failure + expect(last_command.stderr).to eq strip_whitespace(<<-EOS).strip + The gem 'rack' was found in multiple relevant sources. + * rubygems repository file:#{gem_repo1}/ or installed locally + * rubygems repository file:#{gem_repo4}/ or installed locally + You must add this gem to the source block for the source you wish it to be installed from. + EOS + expect(the_bundle).not_to be_locked + end + end end