diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb index 8e64a18cee9a..b9dd546e8d29 100644 --- a/lib/rubygems/commands/fetch_command.rb +++ b/lib/rubygems/commands/fetch_command.rb @@ -2,6 +2,8 @@ require_relative "../command" require_relative "../local_remote_options" +require_relative "../remote_fetcher" +require_relative "../resolver" require_relative "../version_option" class Gem::Commands::FetchCommand < Gem::Command @@ -79,31 +81,59 @@ def fetch_gems platform = Gem.platforms.last gem_names = get_all_gem_names_and_versions + # A BestSet reads a source's compact index when it serves one, the same way + # gems are looked up when installing them. + remote_set = Gem::Resolver::BestSet.new + gem_names.each do |gem_name, gem_version| gem_version ||= version dep = Gem::Dependency.new gem_name, gem_version dep.prerelease = options[:prerelease] suppress_suggestions = !options[:suggest_alternate] - specs_and_sources, errors = - Gem::SpecFetcher.fetcher.spec_for_dependency dep + remote_specs, errors = find_remote_specs dep, remote_set if platform - filtered = specs_and_sources.select {|s,| s.platform == platform } - specs_and_sources = filtered unless filtered.empty? + filtered = remote_specs.select {|s| s.platform == platform } + remote_specs = filtered unless filtered.empty? end - spec, source = specs_and_sources.max_by {|s,| s } + remote_spec = remote_specs.max_by {|s| [s.version, Gem::Platform.sort_priority(s.platform)] } - if spec.nil? + if remote_spec.nil? show_lookup_failure gem_name, gem_version, errors, suppress_suggestions, options[:domain] exit_code |= 2 next end - source.download spec + + spec = remote_spec.spec + remote_spec.source.download spec say "Downloaded #{spec.full_name}" end exit_code end + + # Find specs in +set+ that match +dep+ and can be used on this platform, + # along with the reasons any other spec was rejected. + + def find_remote_specs(dep, set) + set.prerelease = dep.prerelease? + + request = Gem::Resolver::DependencyRequest.new dep, nil + + matching, mismatched = set.find_all(request).partition do |spec| + Gem::Platform.match_spec? spec + end + + [matching, set.errors + platform_mismatches(mismatched)] + end + + def platform_mismatches(specs) + specs.group_by {|spec| [spec.name, spec.version] }.map do |(name, version), group| + mismatch = Gem::PlatformMismatch.new name, version + group.each {|spec| mismatch.add_platform spec.platform.to_s } + mismatch + end + end end diff --git a/test/rubygems/test_gem_commands_fetch_command.rb b/test/rubygems/test_gem_commands_fetch_command.rb index e673e391fe45..58d19840f711 100644 --- a/test/rubygems/test_gem_commands_fetch_command.rb +++ b/test/rubygems/test_gem_commands_fetch_command.rb @@ -81,7 +81,7 @@ def test_execute_platform @cmd.options[:args] = %w[a] - @fetcher.data["#{@gem_repo}latest_specs.#{Gem.marshal_version}.gz"] = util_gzip(Marshal.dump([ + @fetcher.data["#{@gem_repo}specs.#{Gem.marshal_version}.gz"] = util_gzip(Marshal.dump([ Gem::NameTuple.new(a2_spec.name, a2_spec.version, a2_spec.platform), Gem::NameTuple.new(a2_universal_darwin_spec.name, a2_universal_darwin_spec.version, a2_universal_darwin_spec.platform), ])) @@ -100,6 +100,66 @@ def test_execute_platform "#{a2_universal_darwin_spec.full_name} not fetched") end + def test_execute_compact_index + specs = spec_fetcher do |fetcher| + fetcher.gem "a", 1 + fetcher.gem "a", 2 + end + + util_setup_compact_index(*specs.values) + + @cmd.options[:args] = %w[a] + + execute_with_exit_code + + a2 = specs["a-2"] + + assert_path_exist(File.join(@tempdir, a2.file_name), + "#{a2.full_name} not fetched") + + assert_includes @fetcher.paths, "#{@gem_repo}info/a" + refute_includes @fetcher.paths, "#{@gem_repo}specs.#{Gem.marshal_version}.gz" + end + + def test_execute_compact_index_platform + specs = spec_fetcher do |fetcher| + fetcher.gem "a", 2 + fetcher.gem("a", 2) {|s| s.platform = "universal-darwin" } + end + + util_setup_compact_index(*specs.values) + + @cmd.options[:args] = %w[a] + + util_set_arch "arm64-darwin20" do + execute_with_exit_code + end + + a2_universal_darwin = specs["a-2-universal-darwin"] + + assert_path_exist(File.join(@tempdir, a2_universal_darwin.file_name), + "#{a2_universal_darwin.full_name} not fetched") + end + + def test_execute_compact_index_platform_mismatch + specs = spec_fetcher do |fetcher| + fetcher.spec("a", 2) {|s| s.platform = "java" } + end + + util_setup_compact_index(*specs.values) + + @cmd.options[:args] = %w[a] + + execute_with_term_error + + expected = <<-EXPECTED +ERROR: Could not find a valid gem 'a' (>= 0), here is why: + Found a (2), but was for platform java + EXPECTED + + assert_equal expected, @ui.error + end + def test_execute_specific_prerelease specs = spec_fetcher do |fetcher| fetcher.gem "a", 2