Skip to content

Commit

Permalink
[rubygems/rubygems] Extract a Gem::SpecificationRecord class
Browse files Browse the repository at this point in the history
This class handles all logic to handle the list of specifications, given
a set of GEM_PATH directories. Makes `Gem::Specification` has less
responsibilities and will help with fixing some bugs next.

rubygems/rubygems@df280dbbed
  • Loading branch information
deivid-rodriguez authored and matzbot committed May 14, 2024
1 parent c219ee0 commit bd84236
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 87 deletions.
107 changes: 24 additions & 83 deletions lib/rubygems/specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
require_relative "basic_specification"
require_relative "stub_specification"
require_relative "platform"
require_relative "specification_record"
require_relative "util/list"

require "rbconfig"
Expand Down Expand Up @@ -179,19 +180,9 @@ class Gem::Specification < Gem::BasicSpecification
@@default_value[k].nil?
end

def self.clear_specs # :nodoc:
@@all = nil
@@stubs = nil
@@stubs_by_name = {}
@@spec_with_requirable_file = {}
@@active_stub_with_requirable_file = {}
end
private_class_method :clear_specs

clear_specs

# Sentinel object to represent "not found" stubs
NOT_FOUND = Struct.new(:to_spec, :this).new # :nodoc:
deprecate_constant :NOT_FOUND

# Tracking removed method calls to warn users during build time.
REMOVED_METHODS = [:rubyforge_project=, :mark_version].freeze # :nodoc:
Expand Down Expand Up @@ -770,7 +761,7 @@ def extensions_dir
attr_accessor :specification_version

def self._all # :nodoc:
@@all ||= Gem.loaded_specs.values | stubs.map(&:to_spec)
specification_record.all
end

def self.clear_load_cache # :nodoc:
Expand All @@ -788,26 +779,9 @@ def self.each_gemspec(dirs) # :nodoc:
end
end

def self.gemspec_stubs_in(dir, pattern)
def self.gemspec_stubs_in(dir, pattern) # :nodoc:
Gem::Util.glob_files_in_dir(pattern, dir).map {|path| yield path }.select(&:valid?)
end
private_class_method :gemspec_stubs_in

def self.installed_stubs(dirs, pattern)
map_stubs(dirs, pattern) do |path, base_dir, gems_dir|
Gem::StubSpecification.gemspec_stub(path, base_dir, gems_dir)
end
end
private_class_method :installed_stubs

def self.map_stubs(dirs, pattern) # :nodoc:
dirs.flat_map do |dir|
base_dir = File.dirname dir
gems_dir = File.join base_dir, "gems"
gemspec_stubs_in(dir, pattern) {|path| yield path, base_dir, gems_dir }
end
end
private_class_method :map_stubs

def self.each_spec(dirs) # :nodoc:
each_gemspec(dirs) do |path|
Expand All @@ -820,13 +794,7 @@ def self.each_spec(dirs) # :nodoc:
# Returns a Gem::StubSpecification for every installed gem

def self.stubs
@@stubs ||= begin
pattern = "*.gemspec"
stubs = stubs_for_pattern(pattern, false)

@@stubs_by_name = stubs.select {|s| Gem::Platform.match_spec? s }.group_by(&:name)
stubs
end
specification_record.stubs
end

##
Expand All @@ -845,26 +813,15 @@ def self.default_stubs(pattern = "*.gemspec")
# only returns stubs that match Gem.platforms

def self.stubs_for(name)
if @@stubs
@@stubs_by_name[name] || []
else
@@stubs_by_name[name] ||= stubs_for_pattern("#{name}-*.gemspec").select do |s|
s.name == name
end
end
specification_record.stubs_for(name)
end

##
# Finds stub specifications matching a pattern from the standard locations,
# optionally filtering out specs not matching the current platform
#
def self.stubs_for_pattern(pattern, match_platform = true) # :nodoc:
installed_stubs = installed_stubs(dirs, pattern)
installed_stubs.select! {|s| Gem::Platform.match_spec? s } if match_platform
stubs = installed_stubs + default_stubs(pattern)
stubs = stubs.uniq(&:full_name)
_resort!(stubs)
stubs
specification_record.stubs_for_pattern(pattern, match_platform)
end

def self._resort!(specs) # :nodoc:
Expand Down Expand Up @@ -893,23 +850,14 @@ def self.load_defaults
# properly sorted.

def self.add_spec(spec)
return if _all.include? spec

_all << spec
stubs << spec
(@@stubs_by_name[spec.name] ||= []) << spec

_resort!(@@stubs_by_name[spec.name])
_resort!(stubs)
specification_record.add_spec(spec)
end

##
# Removes +spec+ from the known specs.

def self.remove_spec(spec)
_all.delete spec.to_spec
stubs.delete spec
(@@stubs_by_name[spec.name] || []).delete spec
specification_record.remove_spec(spec)
end

##
Expand All @@ -926,15 +874,14 @@ def self.all
# Sets the known specs to +specs+.

def self.all=(specs)
@@stubs_by_name = specs.group_by(&:name)
@@all = @@stubs = specs
specification_record.all = specs
end

##
# Return full names of all specs in sorted order.

def self.all_names
_all.map(&:full_name)
specification_record.all_names
end

##
Expand Down Expand Up @@ -1024,36 +971,23 @@ def self.find_by_full_name(full_name)
# Return the best specification that contains the file matching +path+.

def self.find_by_path(path)
path = path.dup.freeze
spec = @@spec_with_requirable_file[path] ||= stubs.find do |s|
s.contains_requirable_file? path
end || NOT_FOUND

spec.to_spec
specification_record.find_by_path(path)
end

##
# Return the best specification that contains the file matching +path+
# amongst the specs that are not activated.

def self.find_inactive_by_path(path)
stub = stubs.find do |s|
next if s.activated?
s.contains_requirable_file? path
end
stub&.to_spec
specification_record.find_inactive_by_path(path)
end

##
# Return the best specification that contains the file matching +path+, among
# those already activated.

def self.find_active_stub_by_path(path)
stub = @@active_stub_with_requirable_file[path] ||= stubs.find do |s|
s.activated? && s.contains_requirable_file?(path)
end || NOT_FOUND

stub.this
specification_record.find_active_stub_by_path(path)
end

##
Expand Down Expand Up @@ -1120,14 +1054,14 @@ def self.from_yaml(input)
# +prerelease+ is true.

def self.latest_specs(prerelease = false)
_latest_specs stubs, prerelease
specification_record.latest_specs(prerelease)
end

##
# Return the latest installed spec for gem +name+.

def self.latest_spec_for(name)
latest_specs(true).find {|installed_spec| installed_spec.name == name }
specification_record.latest_spec_for(name)
end

def self._latest_specs(specs, prerelease = false) # :nodoc:
Expand Down Expand Up @@ -1265,7 +1199,7 @@ def self.required_attributes
def self.reset
@@dirs = nil
Gem.pre_reset_hooks.each(&:call)
clear_specs
@specification_record = nil
clear_load_cache
unresolved = unresolved_deps
unless unresolved.empty?
Expand All @@ -1286,6 +1220,13 @@ def self.reset
Gem.post_reset_hooks.each(&:call)
end

##
# Keeps track of all currently known specifications

def self.specification_record
@specification_record ||= Gem::SpecificationRecord.new(dirs)
end

# DOC: This method needs documented or nodoc'd
def self.unresolved_deps
@unresolved_deps ||= Hash.new {|h, n| h[n] = Gem::Dependency.new n }
Expand Down

0 comments on commit bd84236

Please sign in to comment.