Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ lib/rubygems/compact_index_client/http_fetcher.rb
lib/rubygems/compact_index_client/parser.rb
lib/rubygems/compact_index_client/updater.rb
lib/rubygems/config_file.rb
lib/rubygems/content_address.rb
lib/rubygems/cooldown.rb
lib/rubygems/cooldown_option.rb
lib/rubygems/core_ext/kernel_gem.rb
Expand Down
26 changes: 22 additions & 4 deletions lib/bundler/endpoint_specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,32 @@ module Bundler
class EndpointSpecification < Gem::Specification
include MatchRemoteMetadata

attr_reader :name, :version, :platform, :checksum, :created_at
attr_reader :name, :version, :platform, :checksum, :created_at, :content_address
attr_writer :dependencies
attr_accessor :remote, :locked_platform

def initialize(name, version, platform, spec_fetcher, dependencies, metadata = nil)
def initialize(name, version, suffix, spec_fetcher, dependencies, metadata = nil)
super()
@name = name
@version = Gem::Version.create version
@platform = Gem::Platform.new(platform)
@spec_fetcher = spec_fetcher
@dependencies = nil
@unbuilt_dependencies = dependencies
@content_address = nil
@required_platform = nil

@loaded_from = nil
@remote_specification = nil
@locked_platform = nil

parse_metadata(metadata)

if Gem::ContentAddress.match?(suffix) && @required_platform
@content_address = suffix
@platform = @required_platform
else
@platform = Gem::Platform.new(suffix)
end
end

def insecurely_materialized?
Expand Down Expand Up @@ -147,7 +155,8 @@ def inspect
private

def _remote_specification
@_remote_specification ||= @spec_fetcher.fetch_spec([@name, @version, @platform])
suffix = @content_address || @platform
@_remote_specification ||= @spec_fetcher.fetch_spec([@name, @version, suffix])
end

def local_specification_path
Expand Down Expand Up @@ -189,6 +198,8 @@ def parse_metadata(data)
nil
end
end
when "platform"
@required_platform = required_platform_from(Array(v).last)
end
end
rescue StandardError => e
Expand All @@ -198,5 +209,12 @@ def parse_metadata(data)
def build_dependency(name, requirements)
Dependency.new(name, requirements)
end

def required_platform_from(value)
op, platform = value.to_s.split(" ", 2)
return unless op == "=" && platform

Gem::Platform.new(platform)
end
end
end
6 changes: 3 additions & 3 deletions lib/bundler/fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,13 @@ def specs_with_retry(gem_names, source)
def specs(gem_names, source)
index = Bundler::Index.new

fetch_specs(gem_names).each do |name, version, platform, dependencies, metadata|
fetch_specs(gem_names).each do |name, version, suffix, dependencies, metadata|
spec = if dependencies
EndpointSpecification.new(name, version, platform, self, dependencies, metadata).tap do |es|
EndpointSpecification.new(name, version, suffix, self, dependencies, metadata).tap do |es|
source.checksum_store.replace(es, es.checksum)
end
else
RemoteSpecification.new(name, version, platform, self)
RemoteSpecification.new(name, version, suffix, self)
end
spec.source = source
spec.remote = @remote
Expand Down
11 changes: 7 additions & 4 deletions lib/bundler/lazy_specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class LazySpecification
include MatchPlatform
include ForcePlatform

attr_reader :name, :version, :platform, :materialization
attr_reader :name, :version, :platform, :materialization, :content_address
attr_accessor :source, :remote, :force_ruby_platform, :dependencies, :required_ruby_version, :required_rubygems_version
attr_accessor :overrides

Expand All @@ -27,21 +27,22 @@ class LazySpecification
alias_method :runtime_dependencies, :dependencies

def self.from_spec(s)
lazy_spec = new(s.name, s.version, s.platform, s.source)
lazy_spec = new(s.name, s.version, s.platform, s.source, content_address: s.content_address)
lazy_spec.dependencies = s.runtime_dependencies
lazy_spec.required_ruby_version = s.required_ruby_version
lazy_spec.required_rubygems_version = s.required_rubygems_version
lazy_spec.overrides = s.overrides if s.is_a?(LazySpecification)
lazy_spec
end

def initialize(name, version, platform, source = nil, **materialization_options)
def initialize(name, version, platform, source = nil, content_address: nil, **materialization_options)
@name = name
@version = version
@dependencies = []
@required_ruby_version = Gem::Requirement.default
@required_rubygems_version = Gem::Requirement.default
@platform = platform || Gem::Platform::RUBY
@content_address = content_address

@original_source = source
@source = source
Expand All @@ -65,7 +66,9 @@ def source_changed?
end

def full_name
@full_name ||= if platform == Gem::Platform::RUBY
@full_name ||= if @content_address
"#{@name}-#{@version}-#{@content_address}"
elsif platform == Gem::Platform::RUBY
"#{@name}-#{@version}"
else
"#{@name}-#{@version}-#{platform}"
Expand Down
18 changes: 16 additions & 2 deletions lib/bundler/match_platform.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

module Bundler
module MatchPlatform
def content_address
nil
end

def installable_on_platform?(target_platform) # :nodoc:
return true if [Gem::Platform::RUBY, nil, target_platform].include?(platform)
return true if Gem::Platform.new(platform) === target_platform
Expand All @@ -11,13 +15,24 @@ def installable_on_platform?(target_platform) # :nodoc:

def self.select_best_platform_match(specs, platform, force_ruby: false, prefer_locked: false)
matching = select_all_platform_match(specs, platform, force_ruby: force_ruby, prefer_locked: prefer_locked)
matching = prefer_content_addressable(matching)

Gem::Platform.sort_and_filter_best_platform_match(matching, platform)
end

def self.prefer_content_addressable(matching)
addressable, non_addressable = matching.partition {|s| Gem::ContentAddress.match?(s.content_address) }
return matching if addressable.empty?

compatible = addressable.select(&:matches_current_ruby?)
compatible.any? ? compatible : non_addressable
end

def self.select_best_local_platform_match(specs, force_ruby: false, locked_platforms: nil)
local = Bundler.local_platform
matching = select_all_platform_match(specs, local, force_ruby: force_ruby).filter_map {|spec| spec.materialized_for_installation(locked_platforms) }
matching = select_all_platform_match(specs, local, force_ruby: force_ruby)
matching = prefer_content_addressable(matching)
matching = matching.filter_map {|spec| spec.materialized_for_installation(locked_platforms) }

Gem::Platform.sort_best_platform_match(matching, local)
end
Expand All @@ -31,7 +46,6 @@ def self.select_all_platform_match(specs, platform, force_ruby: false, prefer_lo
locked_originally = matching.select {|spec| spec.is_a?(::Bundler::LazySpecification) }
return locked_originally if locked_originally.any?
end

matching
end

Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ def incompatibilities_for(package, version)

def all_versions_for(package)
name = package.name
results = (@base[name] + filter_specs(@all_specs[name], package)).uniq {|spec| [spec.version.hash, spec.platform] }
results = (@base[name] + filter_specs(@all_specs[name], package)).uniq {|spec| [spec.version.hash, spec.platform, spec.content_address] }

if name == "bundler" && !bundler_pinned_to_current_version?
bundler_spec = Gem.loaded_specs["bundler"]
Expand Down
32 changes: 31 additions & 1 deletion lib/bundler/rubygems_ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,34 @@
# `Gem::Source` from the redefined `Gem::Specification#source`.
require "rubygems/source"

# Can be removed once RubyGems 4.0.0 support is dropped
unless Gem::BasicSpecification.method_defined?(:content_address)
Gem::BasicSpecification.attr_accessor :content_address
end

# Can be removed once RubyGems 4.0.0 support is dropped
unless Gem::NameTuple.method_defined?(:content_address)
Gem::NameTuple.attr_reader :content_address
end

module Gem
# Can be removed once RubyGems 4.0.0 support is dropped
unless defined?(Gem::ContentAddress)
module ContentAddress
def self.match?(token)
false
end

def self.applicable?(spec)
false
end

def self.content_addressed?(spec)
false
end
end
end

# Can be removed once RubyGems 3.5.11 support is dropped
unless Gem.respond_to?(:freebsd_platform?)
def self.freebsd_platform?
Expand Down Expand Up @@ -417,7 +444,8 @@ class NameTuple
unless Gem::NameTuple.new("a", Gem::Version.new("1"), Gem::Platform.new("x86_64-linux")).platform.is_a?(String)
alias_method :initialize_with_platform, :initialize

def initialize(name, version, platform = Gem::Platform::RUBY)
def initialize(name, version, platform = Gem::Platform::RUBY, content_address = nil)
@content_address = content_address
if Gem::Platform === platform
initialize_with_platform(name, version, platform.to_s)
else
Expand All @@ -427,6 +455,8 @@ def initialize(name, version, platform = Gem::Platform::RUBY)
end

def lock_name
return "#{name} (#{version}-#{content_address})" if Gem::ContentAddress.match?(content_address)

if platform == Gem::Platform::RUBY
"#{name} (#{version})"
else
Expand Down
2 changes: 2 additions & 0 deletions lib/bundler/source/rubygems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ def download(spec, options = {})
"the security policy didn't allow it, with the message: #{e.message}"
end

s.content_address = spec.content_address if spec.content_address

spec.__swap__(s)
end

Expand Down
1 change: 1 addition & 0 deletions lib/rubygems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1411,6 +1411,7 @@ def default_gem_load_paths
MARSHAL_SPEC_DIR = "quick/Marshal.#{Gem.marshal_version}/".freeze

autoload :ConfigFile, File.expand_path("rubygems/config_file", __dir__)
autoload :ContentAddress, File.expand_path("rubygems/content_address", __dir__)
autoload :CIDetector, File.expand_path("rubygems/ci_detector", __dir__)
autoload :Dependency, File.expand_path("rubygems/dependency", __dir__)
autoload :DependencyList, File.expand_path("rubygems/dependency_list", __dir__)
Expand Down
14 changes: 11 additions & 3 deletions lib/rubygems/basic_specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,26 @@ def full_gem_path
end

##
# Returns the full name (name-version) of this Gem. Platform information
# is included (name-version-platform) if it is specified and not the
# Returns the full name (name-version) of this Gem.
# Content address is included (name-version-content_address) if the gem
# is content-addressed (eligible and has a valid content address).
# Platform information is included (name-version-platform) if it is specified and not the
# default Ruby platform.

def full_name
if platform == Gem::Platform::RUBY || platform.nil?
if Gem::ContentAddress.content_addressed?(self)
"#{name}-#{version}-#{content_address}"
elsif platform == Gem::Platform::RUBY || platform.nil?
"#{name}-#{version}"
else
"#{name}-#{version}-#{platform}"
end
end

def content_address # :nodoc:
raise NotImplementedError
end

##
# Returns the full name of this Gem (see `Gem::BasicSpecification#full_name`).
# Information about where the gem is installed is also included if not
Expand Down
11 changes: 10 additions & 1 deletion lib/rubygems/commands/build_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ def initialize
add_option "-o", "--output FILE", "output gem with the given filename" do |value, options|
options[:output] = value
end

add_option "--ruby-abi RUBY_ABI", "build a content addressable gem for the given Ruby ABI" do |value, options|
options[:ruby_abi] = value
end
end

def arguments # :nodoc:
Expand Down Expand Up @@ -52,6 +56,10 @@ def description # :nodoc:

$ gem build my_gem-1.0.gemspec --output=release.gem

Platform gems can be built for a single Ruby ABI with the --ruby-abi option:

$ gem build my_gem-1.0.gemspec --ruby-abi=3.4

EOF
end

Expand Down Expand Up @@ -88,7 +96,8 @@ def build_package(gemspec)
spec,
options[:force],
options[:strict],
options[:output]
options[:output],
options[:ruby_abi]
)
else
alert_error "Error loading gemspec. Aborting."
Expand Down
Loading