From b70f364999c32b8f7a50649922c99e453405cdee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Thu, 5 Feb 2026 00:04:41 +0100 Subject: [PATCH 1/5] Use Compact Index API only for gem install when available. --- lib/rubygems/resolver/api_specification.rb | 25 +++++---- .../test_gem_commands_install_command.rb | 44 ++++++++++++++++ .../test_gem_resolver_api_specification.rb | 51 +++++++++++++++++++ 3 files changed, 110 insertions(+), 10 deletions(-) diff --git a/lib/rubygems/resolver/api_specification.rb b/lib/rubygems/resolver/api_specification.rb index a14bcbfeb1a6..8772c103beee 100644 --- a/lib/rubygems/resolver/api_specification.rb +++ b/lib/rubygems/resolver/api_specification.rb @@ -87,19 +87,24 @@ def pretty_print(q) # :nodoc: # Fetches a Gem::Specification for this APISpecification. def spec # :nodoc: - @spec ||= - begin - tuple = Gem::NameTuple.new @name, @version, @platform - source.fetch_spec tuple - rescue Gem::RemoteFetcher::FetchError - raise if @original_platform == @platform - - tuple = Gem::NameTuple.new @name, @version, @original_platform - source.fetch_spec tuple - end + @spec ||= build_minimal_spec_from_compact_index end def source # :nodoc: @set.source end + + private + + def build_minimal_spec_from_compact_index + spec = Gem::Specification.new + spec.name = @name + spec.version = @version + spec.platform = @platform + spec.dependencies.replace(@dependencies) + spec.required_ruby_version = @required_ruby_version + spec.required_rubygems_version = @required_rubygems_version + + spec + end end diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index d2ca933a632c..5df1e8213d62 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -1642,4 +1642,48 @@ def test_execute_bindir_with_nonexistent_parent_dirs assert_equal %w[a-2], @cmd.installed_specs.map(&:full_name) end + + def test_install_from_compact_index_only_source + specs = spec_fetcher do |fetcher| + fetcher.gem "main-gem", "2" do |s| + s.add_dependency "dep-gem", ">= 1.0" + end + fetcher.gem "dep-gem", "1" + end + + compact_index_response = Gem::Net::HTTPResponse.new "1.1", 200, "OK" + compact_index_response.uri = Gem::URI(@gem_repo) + "versions" + @fetcher.data["#{@gem_repo}versions"] = compact_index_response + + info_main_gem_response = <<~INFO + --- + 2 dep-gem:>= 1.0|ruby:>= 2.5.0,rubygems:>= 3.0.0 + INFO + @fetcher.data["#{@gem_repo}info/main-gem"] = info_main_gem_response + + info_dep_gem_response = <<~INFO + --- + 1 |ruby:>= 2.0.0 + INFO + @fetcher.data["#{@gem_repo}info/dep-gem"] = info_dep_gem_response + + @cmd.options[:args] = %w[main-gem] # gem install main-gem + + use_ui @ui do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do + @cmd.execute + end + end + + assert_equal %w[main-gem-2], @cmd.installed_specs.map(&:full_name) + + installed_gem = @cmd.installed_specs.find {|s| s.name == "main-gem" } + assert_equal "main-gem", installed_gem.name + assert_equal Gem::Version.new("2"), installed_gem.version + assert_equal 1, installed_gem.dependencies.size + assert_equal "dep-gem", installed_gem.dependencies.first.name + + marshal_requests = @fetcher.paths.select {|p| p.include?("/quick/Marshal.4.8/") } + assert_empty marshal_requests, "Should not request marshal gemspecs: #{marshal_requests.inspect}" + end end diff --git a/test/rubygems/test_gem_resolver_api_specification.rb b/test/rubygems/test_gem_resolver_api_specification.rb index 2119d734780b..766623049783 100644 --- a/test/rubygems/test_gem_resolver_api_specification.rb +++ b/test/rubygems/test_gem_resolver_api_specification.rb @@ -164,4 +164,55 @@ def test_spec_jruby_platform assert_kind_of Gem::Specification, spec assert_equal "j-1-java", spec.full_name end + + def test_spec_builds_from_compact_index_without_marshal_gemspec + dep_uri = @gem_repo + "info" + set = Gem::Resolver::APISet.new dep_uri + data = { + name: "rails", + number: "7.0.0", + platform: "ruby", + dependencies: [ + ["activesupport", "= 7.0.0"], + ["bundler", ">= 1.15.0"], + ], + requirements: { + ruby: ">= 3.4.0", + rubygems: ">= 4.0.0", + }, + } + + api_spec = Gem::Resolver::APISpecification.new set, data + spec = api_spec.spec + + assert_kind_of Gem::Specification, spec + assert_equal "rails", spec.name + assert_equal Gem::Version.new("7.0.0"), spec.version + assert_equal Gem::Platform::RUBY, spec.platform + + assert_equal 2, spec.dependencies.size + assert spec.dependencies.any? {|d| d.name == "activesupport" && d.requirement.to_s == "= 7.0.0" } + assert spec.dependencies.any? {|d| d.name == "bundler" && d.requirement.to_s == ">= 1.15.0" } + + assert_equal ">= 3.4.0", spec.required_ruby_version.to_s + assert_equal ">= 4.0.0", spec.required_rubygems_version.to_s + end + + def test_spec_builds_without_requirements + dep_uri = @gem_repo + "info" + set = Gem::Resolver::APISet.new dep_uri + data = { + name: "simple_gem", + number: "1.0.0", + platform: "ruby", + dependencies: [], + } + + api_spec = Gem::Resolver::APISpecification.new set, data + spec = api_spec.spec + + assert_kind_of Gem::Specification, spec + assert_equal "simple_gem", spec.name + assert_equal Gem::Version.new("1.0.0"), spec.version + end end From 0655f5883ac5961bcb59bf6c1998f9d58e34bab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Thu, 5 Feb 2026 01:43:46 +0100 Subject: [PATCH 2/5] Introduce better Compact Index API stub for testing. --- test/rubygems/helper.rb | 19 ++++ .../test_gem_commands_install_command.rb | 26 +---- test/rubygems/utilities.rb | 100 ++++++++++++++++++ 3 files changed, 124 insertions(+), 21 deletions(-) diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index dc40f4ecb1f8..4d2f889d1b46 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -1143,6 +1143,25 @@ def write_marshalled_gemspecs(*all_specs) end end + ## + # Sets up Compact Index API endpoints for testing. Does NOT set up old marshal API. + # This causes Source#new_dependency_resolver_set to use APISet. + # + # Usage: + # compact_index do |ci| + # ci.gem "a", 1 do |s| + # s.add_dependency "b", ">= 2.0" + # end + # ci.gem "b", 2 + # end + + def compact_index(&block) + fake_compact_index = Gem::TestCase::CompactIndexSetup.new(self, @gem_repo) + yield fake_compact_index + fake_compact_index.stub + fake_compact_index.specs + end + ## # Deflates +data+ diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 5df1e8213d62..371aa05f3fbd 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -1644,30 +1644,14 @@ def test_execute_bindir_with_nonexistent_parent_dirs end def test_install_from_compact_index_only_source - specs = spec_fetcher do |fetcher| - fetcher.gem "main-gem", "2" do |s| + compact_index do |ci| + ci.gem "main-gem", "2" do |s| s.add_dependency "dep-gem", ">= 1.0" end - fetcher.gem "dep-gem", "1" + ci.gem "dep-gem", "1" end - compact_index_response = Gem::Net::HTTPResponse.new "1.1", 200, "OK" - compact_index_response.uri = Gem::URI(@gem_repo) + "versions" - @fetcher.data["#{@gem_repo}versions"] = compact_index_response - - info_main_gem_response = <<~INFO - --- - 2 dep-gem:>= 1.0|ruby:>= 2.5.0,rubygems:>= 3.0.0 - INFO - @fetcher.data["#{@gem_repo}info/main-gem"] = info_main_gem_response - - info_dep_gem_response = <<~INFO - --- - 1 |ruby:>= 2.0.0 - INFO - @fetcher.data["#{@gem_repo}info/dep-gem"] = info_dep_gem_response - - @cmd.options[:args] = %w[main-gem] # gem install main-gem + @cmd.options[:args] = %w[main-gem] use_ui @ui do assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @@ -1675,7 +1659,7 @@ def test_install_from_compact_index_only_source end end - assert_equal %w[main-gem-2], @cmd.installed_specs.map(&:full_name) + assert_equal %w[dep-gem-1 main-gem-2], @cmd.installed_specs.map(&:full_name).sort installed_gem = @cmd.installed_specs.find {|s| s.name == "main-gem" } assert_equal "main-gem", installed_gem.name diff --git a/test/rubygems/utilities.rb b/test/rubygems/utilities.rb index bf601f6fac2d..124dbc9579c1 100644 --- a/test/rubygems/utilities.rb +++ b/test/rubygems/utilities.rb @@ -420,6 +420,106 @@ def write_spec(spec) # :nodoc: end end +## +# Minimal CompactIndex implementation for tests. +# This is a simplified version that only implements what's needed for test fixtures. +module CompactIndexBuilder + # Generates the /info/{gem_name} response body + # Format: ---\nVERSION DEPS|METADATA\n + # Where DEPS is: dep_name:requirement,dep_name:requirement + # And METADATA is: checksum:SHA256,ruby:requirement,rubygems:requirement + def self.info(versions) + lines = ["---"] + versions.each do |version| + # Add dependencies (if any) + deps = version.dependencies.map {|d| "#{d.name}:#{d.requirement}" } + deps_string = deps.join(",") + + # Build metadata + metadata = [] + metadata << "checksum:#{version.checksum}" if version.checksum + metadata << "ruby:#{version.ruby_version}" if version.ruby_version && version.ruby_version != ">= 0" + metadata << "rubygems:#{version.rubygems_version}" if version.rubygems_version && version.rubygems_version != ">= 0" + + # Format: "VERSION DEPS|METADATA" or "VERSION |METADATA" (space before | only when no deps) + line = "#{version.version} #{deps_string}|" + metadata.join(",") + lines << line + end + lines.join("\n") << "\n" + end + + GemVersion = Data.define(:version, :platform, :checksum, :info_checksum, :dependencies, :ruby_version, :rubygems_version) do + def initialize(version:, platform:, checksum:, info_checksum: nil, dependencies: [], ruby_version: nil, rubygems_version: nil) + super(version:, platform:, checksum:, info_checksum:, dependencies:, ruby_version:, rubygems_version:) + end + end + + Dependency = Data.define(:name, :requirement) +end + +## +# The CompactIndexSetup allows easy setup of compact index endpoints in tests. +# Unlike SpecFetcherSetup, this only sets up compact index (no marshal API). +# +# compact_index do |ci| +# ci.gem "a", 1 do |s| +# s.add_dependency "b", "~> 2.0" +# end +# ci.gem "b", 2 +# end + +class Gem::TestCase::CompactIndexSetup + attr_reader :specs + + def initialize(test, repository) + @test = test + @repository = repository + @test.fetcher = Gem::FakeFetcher.new + Gem::RemoteFetcher.fetcher = @test.fetcher + @specs = {} + end + + def gem(name, version, dependencies = nil, &block) + spec = @test.util_spec(name, version, dependencies, &block) + @specs[spec.full_name] = spec + end + + def stub + @specs.values.group_by(&:name).each do |name, gem_specs| + versions = gem_specs.map do |spec| + gem_file = Gem::Package.build(spec) + gem_contents = Gem.read_binary(gem_file) + FileUtils.cp gem_file, spec.cache_file + + @test.fetcher.data["#{@repository}gems/#{spec.file_name}"] = gem_contents + + dependencies = spec.dependencies.select(&:runtime?).map do |dep| + CompactIndexBuilder::Dependency.new(dep.name, dep.requirement.to_s) + end + + checksum = Digest::SHA256.hexdigest(gem_contents) + + CompactIndexBuilder::GemVersion.new( + spec.version.to_s, + spec.platform.to_s, + checksum, + nil, + dependencies, + spec.required_ruby_version.to_s, + spec.required_rubygems_version.to_s + ) + end + + @test.fetcher.data["#{@repository}info/#{name}"] = CompactIndexBuilder.info(versions) + end + + # stub only to pass Compact Index API presence check currently + versions_response = Gem::Net::HTTPResponse.new "1.1", 200, "OK" + versions_response.uri = Gem::URI("#{@repository}versions") + @test.fetcher.data["#{@repository}versions"] = versions_response + end +end + ## # A StringIO duck-typed class that uses Tempfile instead of String as the # backing store. From fc0f6afb6957f17e82650c32ef31e3ac81d057ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Thu, 5 Feb 2026 14:15:33 +0100 Subject: [PATCH 3/5] Move some gem install tests to Compact Index API. --- .../test_gem_commands_install_command.rb | 106 +++++++++--------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 371aa05f3fbd..617c678f6c00 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -29,9 +29,9 @@ def teardown end def test_execute_exclude_prerelease - spec_fetcher do |fetcher| - fetcher.gem "a", 2 - fetcher.gem "a", "2.pre" + compact_index do |ci| + ci.gem "a", 2 + ci.gem "a", "2.pre" end @cmd.options[:args] = %w[a] @@ -46,9 +46,9 @@ def test_execute_exclude_prerelease end def test_execute_explicit_version_includes_prerelease - specs = spec_fetcher do |fetcher| - fetcher.gem "a", 2 - fetcher.gem "a", "2.a" + specs = compact_index do |ci| + ci.gem "a", 2 + ci.gem "a", "2.a" end a2_pre = specs["a-2.a"] @@ -430,9 +430,9 @@ def test_execute_nonexistent_with_dashes end def test_execute_prerelease_skipped_when_no_flag_set - spec_fetcher do |fetcher| - fetcher.gem "a", 1 - fetcher.gem "a", "3.a" + compact_index do |ci| + ci.gem "a", 1 + ci.gem "a", "3.a" end @cmd.options[:prerelease] = false @@ -483,9 +483,9 @@ def test_execute_with_version_specified_by_colon end def test_execute_prerelease_skipped_when_non_pre_available - spec_fetcher do |fetcher| - fetcher.gem "a", "2.pre" - fetcher.gem "a", 2 + compact_index do |ci| + ci.gem "a", "2.pre" + ci.gem "a", 2 end @cmd.options[:prerelease] = true @@ -532,9 +532,9 @@ def test_execute_required_ruby_version def test_execute_required_ruby_version_upper_bound local = Gem::Platform.local - spec_fetcher do |fetcher| - fetcher.gem "a", 2.0 - fetcher.gem "a", 2.0 do |s| + compact_index do |ci| + ci.gem "a", 2.0 + ci.gem "a", 2.0 do |s| s.required_ruby_version = "< #{RUBY_VERSION}.a" s.platform = local end @@ -552,8 +552,8 @@ def test_execute_required_ruby_version_upper_bound end def test_execute_required_ruby_version_specific_not_met - spec_fetcher do |fetcher| - fetcher.gem "a", "1.0" do |s| + compact_index do |ci| + ci.gem "a", "1.0" do |s| s.required_ruby_version = "= 1.4.6" end end @@ -572,8 +572,8 @@ def test_execute_required_ruby_version_specific_not_met end def test_execute_required_ruby_version_specific_prerelease_met - spec_fetcher do |fetcher| - fetcher.gem "a", "1.0" do |s| + compact_index do |ci| + ci.gem "a", "1.0" do |s| s.required_ruby_version = ">= 1.4.6.preview2" end end @@ -592,8 +592,8 @@ def test_execute_required_ruby_version_specific_prerelease_met def test_execute_required_ruby_version_specific_prerelease_not_met next_ruby_pre = Gem.ruby_version.segments.map.with_index {|n, i| i == 1 ? n + 1 : n }.join(".") + ".a" - spec_fetcher do |fetcher| - fetcher.gem "a", "1.0" do |s| + compact_index do |ci| + ci.gem "a", "1.0" do |s| s.required_ruby_version = "> #{next_ruby_pre}" end end @@ -612,8 +612,8 @@ def test_execute_required_ruby_version_specific_prerelease_not_met end def test_execute_required_rubygems_version_wrong - spec_fetcher do |fetcher| - fetcher.gem "a", "1.0" do |s| + compact_index do |ci| + ci.gem "a", "1.0" do |s| s.required_rubygems_version = "< 0" end end @@ -632,8 +632,8 @@ def test_execute_required_rubygems_version_wrong end def test_execute_rdoc - specs = spec_fetcher do |fetcher| - fetcher.gem "a", 2 + specs = compact_index do |ci| + ci.gem "a", 2 end Gem.done_installing(&Gem::RDoc.method(:generation_hook)) @@ -661,8 +661,8 @@ def test_execute_rdoc end if defined?(Gem::RDoc) && !Gem.rdoc_hooks_defined_via_plugin? def test_execute_rdoc_with_path - specs = spec_fetcher do |fetcher| - fetcher.gem "a", 2 + specs = compact_index do |ci| + ci.gem "a", 2 end Gem.done_installing(&Gem::RDoc.method(:generation_hook)) @@ -690,8 +690,8 @@ def test_execute_rdoc_with_path end if defined?(Gem::RDoc) && !Gem.rdoc_hooks_defined_via_plugin? def test_execute_saves_build_args - specs = spec_fetcher do |fetcher| - fetcher.gem "a", 2 + specs = compact_index do |ci| + ci.gem "a", 2 end args = %w[--with-awesome=true --more-awesome=yes] @@ -720,8 +720,8 @@ def test_execute_saves_build_args end def test_execute_remote - spec_fetcher do |fetcher| - fetcher.gem "a", 2 + compact_index do |ci| + ci.gem "a", 2 end @cmd.options[:args] = %w[a] @@ -740,8 +740,8 @@ def test_execute_remote def test_execute_with_invalid_gem_file FileUtils.touch("a.gem") - spec_fetcher do |fetcher| - fetcher.gem "a", 2 + compact_index do |ci| + ci.gem "a", 2 end @cmd.options[:args] = %w[a] @@ -758,8 +758,8 @@ def test_execute_with_invalid_gem_file end def test_execute_remote_truncates_existing_gemspecs - spec_fetcher do |fetcher| - fetcher.gem "a", 1 + compact_index do |ci| + ci.gem "a", 1 end @cmd.options[:domain] = :remote @@ -791,9 +791,9 @@ def test_execute_remote_truncates_existing_gemspecs end def test_execute_remote_ignores_files - specs = spec_fetcher do |fetcher| - fetcher.gem "a", 1 - fetcher.gem "a", 2 + specs = compact_index do |ci| + ci.gem "a", 1 + ci.gem "a", 2 end @cmd.options[:domain] = :remote @@ -887,11 +887,11 @@ def test_execute_two_version end def test_execute_two_version_specified_by_colon - spec_fetcher do |fetcher| - fetcher.gem "a", 1 - fetcher.gem "a", 2 - fetcher.gem "b", 1 - fetcher.gem "b", 2 + compact_index do |ci| + ci.gem "a", 1 + ci.gem "a", 2 + ci.gem "b", 1 + ci.gem "b", 2 end @cmd.options[:args] = %w[a:1 b:1] @@ -956,8 +956,8 @@ def test_install_gem_ignore_dependencies_both end def test_install_gem_ignore_dependencies_remote - spec_fetcher do |fetcher| - fetcher.gem "a", 2 + compact_index do |ci| + ci.gem "a", 2 end @cmd.options[:ignore_dependencies] = true @@ -969,10 +969,10 @@ def test_install_gem_ignore_dependencies_remote def test_install_gem_ignore_dependencies_remote_platform_local local = Gem::Platform.local - spec_fetcher do |fetcher| - fetcher.gem "a", 3 + compact_index do |ci| + ci.gem "a", 3 - fetcher.gem "a", 3 do |s| + ci.gem "a", 3 do |s| s.platform = local end end @@ -1365,9 +1365,9 @@ def test_execute_with_gemdeps_path_ignores_system end def test_execute_uses_deps_a_gemdeps_with_a_path - specs = spec_fetcher do |fetcher| - fetcher.gem "q", "1.0" - fetcher.gem "r", "2.0", "q" => nil + specs = compact_index do |ci| + ci.gem "q", "1.0" + ci.gem "r", "2.0", "q" => nil end i = Gem::Installer.at specs["q-1.0"].cache_file, install_dir: "gf-path" @@ -1568,8 +1568,8 @@ def test_explain_platform_ruby_ignore_dependencies def test_suggest_update_if_enabled TestUpdateSuggestion.with_eligible_environment(cmd: @cmd) do - spec_fetcher do |fetcher| - fetcher.gem "a", 2 + compact_index do |ci| + ci.gem "a", 2 end @cmd.options[:args] = %w[a] From 29437c57b0d3efe254428a3a70bcddde85578771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Fri, 6 Mar 2026 21:12:50 +0100 Subject: [PATCH 4/5] Update with correct separator and platform suffix. --- lib/rubygems/resolver/api_specification.rb | 5 ++++- test/rubygems/test_gem_resolver_api_set.rb | 16 ++++++++++++++++ test/rubygems/utilities.rb | 20 +++++++++++--------- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/rubygems/resolver/api_specification.rb b/lib/rubygems/resolver/api_specification.rb index 8772c103beee..0c23c9e0bea6 100644 --- a/lib/rubygems/resolver/api_specification.rb +++ b/lib/rubygems/resolver/api_specification.rb @@ -84,7 +84,10 @@ def pretty_print(q) # :nodoc: end ## - # Fetches a Gem::Specification for this APISpecification. + # Returns a minimal Gem::Specification built from compact index data. + # Only includes name, version, platform, dependencies, required_ruby_version, + # and required_rubygems_version. Other specification fields (e.g., files, + # executables, authors, metadata) are not populated. def spec # :nodoc: @spec ||= build_minimal_spec_from_compact_index diff --git a/test/rubygems/test_gem_resolver_api_set.rb b/test/rubygems/test_gem_resolver_api_set.rb index b0b4943beafa..43d5193e22da 100644 --- a/test/rubygems/test_gem_resolver_api_set.rb +++ b/test/rubygems/test_gem_resolver_api_set.rb @@ -55,6 +55,22 @@ def test_find_all assert_equal expected, set.find_all(a_dep) end + def test_find_all_platform + spec_fetcher + + @fetcher.data["#{@dep_uri}a"] = "---\n1 |checksum:abc123\n1-java |checksum:def456" + + set = Gem::Resolver::APISet.new @dep_uri + + a_dep = Gem::Resolver::DependencyRequest.new dep("a"), nil + + specs = set.find_all(a_dep) + + assert_equal 2, specs.length + assert_equal Gem::Platform.new("ruby"), specs[0].platform + assert_equal Gem::Platform.new("java"), specs[1].platform + end + def test_find_all_prereleases spec_fetcher diff --git a/test/rubygems/utilities.rb b/test/rubygems/utilities.rb index 124dbc9579c1..7fa58ba8f70a 100644 --- a/test/rubygems/utilities.rb +++ b/test/rubygems/utilities.rb @@ -425,24 +425,26 @@ def write_spec(spec) # :nodoc: # This is a simplified version that only implements what's needed for test fixtures. module CompactIndexBuilder # Generates the /info/{gem_name} response body - # Format: ---\nVERSION DEPS|METADATA\n - # Where DEPS is: dep_name:requirement,dep_name:requirement - # And METADATA is: checksum:SHA256,ruby:requirement,rubygems:requirement + # Format: ---\nVERSION[-PLATFORM] DEPS|METADATA\n + # Where DEPS is: dep_name:req1&req2,dep_name:req1&req2 + # And METADATA is: checksum:SHA256,ruby:req1&req2,rubygems:req1&req2 def self.info(versions) lines = ["---"] versions.each do |version| - # Add dependencies (if any) - deps = version.dependencies.map {|d| "#{d.name}:#{d.requirement}" } + # Compact index uses & to separate compound requirements within a single dependency, + # since , is used to separate different dependencies. + deps = version.dependencies.map {|d| "#{d.name}:#{d.requirement.gsub(", ", "&")}" } deps_string = deps.join(",") # Build metadata metadata = [] metadata << "checksum:#{version.checksum}" if version.checksum - metadata << "ruby:#{version.ruby_version}" if version.ruby_version && version.ruby_version != ">= 0" - metadata << "rubygems:#{version.rubygems_version}" if version.rubygems_version && version.rubygems_version != ">= 0" + metadata << "ruby:#{version.ruby_version.gsub(", ", "&")}" if version.ruby_version && version.ruby_version != ">= 0" + metadata << "rubygems:#{version.rubygems_version.gsub(", ", "&")}" if version.rubygems_version && version.rubygems_version != ">= 0" - # Format: "VERSION DEPS|METADATA" or "VERSION |METADATA" (space before | only when no deps) - line = "#{version.version} #{deps_string}|" + metadata.join(",") + # Format: "VERSION[-PLATFORM] DEPS|METADATA" or "VERSION[-PLATFORM] |METADATA" + version_string = version.platform && version.platform != "ruby" ? "#{version.version}-#{version.platform}" : version.version + line = "#{version_string} #{deps_string}|" + metadata.join(",") lines << line end lines.join("\n") << "\n" From e701ccf50a21c4e4ca4036a2397fee5d0dc59a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Fri, 6 Mar 2026 22:01:50 +0100 Subject: [PATCH 5/5] Unify terminology. --- lib/rubygems/resolver/api_specification.rb | 2 +- test/rubygems/utilities.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/rubygems/resolver/api_specification.rb b/lib/rubygems/resolver/api_specification.rb index 0c23c9e0bea6..b814b1cccdca 100644 --- a/lib/rubygems/resolver/api_specification.rb +++ b/lib/rubygems/resolver/api_specification.rb @@ -84,7 +84,7 @@ def pretty_print(q) # :nodoc: end ## - # Returns a minimal Gem::Specification built from compact index data. + # Returns a minimal Gem::Specification built from Compact Index API data. # Only includes name, version, platform, dependencies, required_ruby_version, # and required_rubygems_version. Other specification fields (e.g., files, # executables, authors, metadata) are not populated. diff --git a/test/rubygems/utilities.rb b/test/rubygems/utilities.rb index 7fa58ba8f70a..e8693e094f0c 100644 --- a/test/rubygems/utilities.rb +++ b/test/rubygems/utilities.rb @@ -421,7 +421,7 @@ def write_spec(spec) # :nodoc: end ## -# Minimal CompactIndex implementation for tests. +# Minimal Compact Index API implementation for tests. # This is a simplified version that only implements what's needed for test fixtures. module CompactIndexBuilder # Generates the /info/{gem_name} response body @@ -431,7 +431,7 @@ module CompactIndexBuilder def self.info(versions) lines = ["---"] versions.each do |version| - # Compact index uses & to separate compound requirements within a single dependency, + # Compact Index API uses & to separate compound requirements within a single dependency, # since , is used to separate different dependencies. deps = version.dependencies.map {|d| "#{d.name}:#{d.requirement.gsub(", ", "&")}" } deps_string = deps.join(",") @@ -460,8 +460,8 @@ def initialize(version:, platform:, checksum:, info_checksum: nil, dependencies: end ## -# The CompactIndexSetup allows easy setup of compact index endpoints in tests. -# Unlike SpecFetcherSetup, this only sets up compact index (no marshal API). +# The CompactIndexSetup allows easy setup of Compact Index API endpoints in tests. +# Unlike SpecFetcherSetup, this only sets up Compact Index API (no marshal API). # # compact_index do |ci| # ci.gem "a", 1 do |s|