Let gem install use Compact Index API only when present.#9314
Let gem install use Compact Index API only when present.#9314simi wants to merge 3 commits intoruby:masterfrom
Conversation
dc193a0 to
4e508a1
Compare
4e508a1 to
fc0f6af
Compare
| ## | ||
| # Minimal CompactIndex implementation for tests. | ||
| # This is a simplified version that only implements what's needed for test fixtures. | ||
| module CompactIndexBuilder |
There was a problem hiding this comment.
I tried to use compact_index gem, but it is not possible to load external dependency on ruby-core CI. For now I have provided minimal implementation for now.
There was a problem hiding this comment.
@simi I paired with Claude/Copilot on this and found a couple of small areas for improvement. I tried pushing to the PR, but I don't think Allow edits from maintainers is currently enabled.
1. Missing platform suffix in compact index info output
CompactIndexBuilder.info doesn't include the platform in the version string. The compact index format requires VERSION-PLATFORM (e.g., 1.0-java) for non-ruby platforms, which is what GemParser#parse expects when it splits by "-". Without this, all platform-specific gems appear as platform: "ruby".
The existing platform tests (test_install_gem_ignore_dependencies_remote_platform_local, test_execute_required_ruby_version_upper_bound) still pass without this fix because Gem::Source::Local picks up the .gem files built by CompactIndexSetup#stub and bypasses the compact index data entirely for platform resolution.
2. Wrong separator for compound requirements
Dependency#requirement.to_s returns comma-separated strings like ">= 1.0, < 2.0", but the compact index format uses & for compound requirements within a single dependency (e.g., dep_name:>= 1.0&< 2.0). The , separator is reserved for separating different dependencies. Same issue applies to ruby/rubygems version metadata.
Reference: GemVersion#join_multiple in the canonical library does exactly split(", ") then join("&").
Patch
diff --git a/lib/rubygems/resolver/api_specification.rb b/lib/rubygems/resolver/api_specification.rb
index 8772c103be..0c23c9e0be 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/utilities.rb b/test/rubygems/utilities.rb
index 124dbc9579..5150403fb4 100644
--- a/test/rubygems/utilities.rb
+++ b/test/rubygems/utilities.rb
@@ -426,23 +426,26 @@ def write_spec(spec) # :nodoc:
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
+ # 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"
diff --git a/test/rubygems/test_gem_resolver_api_set.rb b/test/rubygems/test_gem_resolver_api_set.rb
index b0b4943bea..43d5193e22 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
While I was working on publishing some gems in private repositories, I have realized bundler already uses Compact Index API only when present, but gem commands sometimes not.
This is first change in chain to update gem commands (
gem installhere) to be able to use Compact Index API only when present. The change is simple, Compact Index API/info/:gemendpoint already provides all info needed to provide gemspec stub needed to resolve dependencies.I have added also (in separate commit) infrastructure for simple stub of Compact Index API (reusing same API as
spec_fetcher) and in last commit I have ported some basicgem installtests to use Compact Index API (as new default option).As a side-effect it is also faster. Running simple command like
GEM_HOME=/tmp/gem_test gem installwith empty GEM_HOME makes gem install 5s faster locally on my Linux (Fedora) computer.