diff options
Diffstat (limited to 'lib/rubygems')
-rw-r--r-- | lib/rubygems/commands/pristine_command.rb | 15 | ||||
-rw-r--r-- | lib/rubygems/commands/setup_command.rb | 2 | ||||
-rw-r--r-- | lib/rubygems/commands/uninstall_command.rb | 2 | ||||
-rw-r--r-- | lib/rubygems/dependency.rb | 14 | ||||
-rw-r--r-- | lib/rubygems/deprecate.rb | 156 | ||||
-rw-r--r-- | lib/rubygems/ext/cargo_builder.rb | 17 | ||||
-rw-r--r-- | lib/rubygems/gemcutter_utilities/webauthn_poller.rb | 4 | ||||
-rw-r--r-- | lib/rubygems/installer.rb | 2 | ||||
-rw-r--r-- | lib/rubygems/package.rb | 17 | ||||
-rw-r--r-- | lib/rubygems/package/tar_header.rb | 24 | ||||
-rw-r--r-- | lib/rubygems/platform.rb | 1 | ||||
-rw-r--r-- | lib/rubygems/specification.rb | 152 | ||||
-rw-r--r-- | lib/rubygems/specification_policy.rb | 4 | ||||
-rw-r--r-- | lib/rubygems/specification_record.rb | 213 | ||||
-rw-r--r-- | lib/rubygems/uninstaller.rb | 24 |
15 files changed, 395 insertions, 252 deletions
diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 456d897df2..b272a15b6c 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -57,7 +57,7 @@ class Gem::Commands::PristineCommand < Gem::Command end add_option("-i", "--install-dir DIR", - "Gem repository to get binstubs and plugins installed") do |value, options| + "Gem repository to get gems restored") do |value, options| options[:install_dir] = File.expand_path(value) end @@ -103,21 +103,25 @@ extensions will be restored. end def execute + install_dir = options[:install_dir] + + specification_record = install_dir ? Gem::SpecificationRecord.from_path(install_dir) : Gem::Specification.specification_record + specs = if options[:all] - Gem::Specification.map + specification_record.map # `--extensions` must be explicitly given to pristine only gems # with extensions. elsif options[:extensions_set] && options[:extensions] && options[:args].empty? - Gem::Specification.select do |spec| + specification_record.select do |spec| spec.extensions && !spec.extensions.empty? end elsif options[:only_missing_extensions] - Gem::Specification.select(&:missing_extensions?) + specification_record.select(&:missing_extensions?) else get_all_gem_names.sort.map do |gem_name| - Gem::Specification.find_all_by_name(gem_name, options[:version]).reverse + specification_record.find_all_by_name(gem_name, options[:version]).reverse end.flatten end @@ -176,7 +180,6 @@ extensions will be restored. end bin_dir = options[:bin_dir] if options[:bin_dir] - install_dir = options[:install_dir] if options[:install_dir] installer_options = { wrappers: true, diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 3f38074280..9c633d6ef7 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -585,6 +585,8 @@ abort "#{deprecation_message}" args = %w[--all --only-executables --silent] args << "--bindir=#{bindir}" + args << "--install-dir=#{default_dir}" + if options[:env_shebang] args << "--env-shebang" end diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb index 2a77ec72cf..283bc96ce3 100644 --- a/lib/rubygems/commands/uninstall_command.rb +++ b/lib/rubygems/commands/uninstall_command.rb @@ -184,7 +184,7 @@ that is a dependency of an existing gem. You can use the rescue Gem::GemNotInHomeException => e spec = e.spec alert("In order to remove #{spec.name}, please execute:\n" \ - "\tgem uninstall #{spec.name} --install-dir=#{spec.installation_path}") + "\tgem uninstall #{spec.name} --install-dir=#{spec.base_dir}") rescue Gem::UninstallError => e spec = e.spec alert_error("Error: unable to successfully uninstall '#{spec.name}' which is " \ diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb index d1bf074441..5ce9c5e840 100644 --- a/lib/rubygems/dependency.rb +++ b/lib/rubygems/dependency.rb @@ -271,15 +271,7 @@ class Gem::Dependency end def matching_specs(platform_only = false) - env_req = Gem.env_requirement(name) - matches = Gem::Specification.stubs_for(name).find_all do |spec| - requirement.satisfied_by?(spec.version) && env_req.satisfied_by?(spec.version) - end.map(&:to_spec) - - if prioritizes_bundler? - require_relative "bundler_version_finder" - Gem::BundlerVersionFinder.prioritize!(matches) - end + matches = Gem::Specification.find_all_by_name(name, requirement) if platform_only matches.reject! do |spec| @@ -297,10 +289,6 @@ class Gem::Dependency @requirement.specific? end - def prioritizes_bundler? - name == "bundler" && !specific? - end - def to_specs matches = matching_specs true diff --git a/lib/rubygems/deprecate.rb b/lib/rubygems/deprecate.rb index 58a6c5b7dc..7d24f9cbfc 100644 --- a/lib/rubygems/deprecate.rb +++ b/lib/rubygems/deprecate.rb @@ -69,99 +69,101 @@ # end # end -module Gem::Deprecate - def self.skip # :nodoc: - @skip ||= false - end +module Gem + module Deprecate + def self.skip # :nodoc: + @skip ||= false + end - def self.skip=(v) # :nodoc: - @skip = v - end + def self.skip=(v) # :nodoc: + @skip = v + end - ## - # Temporarily turn off warnings. Intended for tests only. + ## + # Temporarily turn off warnings. Intended for tests only. - def skip_during - original = Gem::Deprecate.skip - Gem::Deprecate.skip = true - yield - ensure - Gem::Deprecate.skip = original - end + def skip_during + original = Gem::Deprecate.skip + Gem::Deprecate.skip = true + yield + ensure + Gem::Deprecate.skip = original + end - def self.next_rubygems_major_version # :nodoc: - Gem::Version.new(Gem.rubygems_version.segments.first).bump - end + def self.next_rubygems_major_version # :nodoc: + Gem::Version.new(Gem.rubygems_version.segments.first).bump + end - ## - # Simple deprecation method that deprecates +name+ by wrapping it up - # in a dummy method. It warns on each call to the dummy method - # telling the user of +repl+ (unless +repl+ is :none) and the - # year/month that it is planned to go away. + ## + # Simple deprecation method that deprecates +name+ by wrapping it up + # in a dummy method. It warns on each call to the dummy method + # telling the user of +repl+ (unless +repl+ is :none) and the + # year/month that it is planned to go away. - def deprecate(name, repl, year, month) - class_eval do - old = "_deprecated_#{name}" - alias_method old, name - define_method name do |*args, &block| - klass = is_a? Module - target = klass ? "#{self}." : "#{self.class}#" - msg = [ - "NOTE: #{target}#{name} is deprecated", - repl == :none ? " with no replacement" : "; use #{repl} instead", - format(". It will be removed on or after %4d-%02d.", year, month), - "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", - ] - warn "#{msg.join}." unless Gem::Deprecate.skip - send old, *args, &block + def deprecate(name, repl, year, month) + class_eval do + old = "_deprecated_#{name}" + alias_method old, name + define_method name do |*args, &block| + klass = is_a? Module + target = klass ? "#{self}." : "#{self.class}#" + msg = [ + "NOTE: #{target}#{name} is deprecated", + repl == :none ? " with no replacement" : "; use #{repl} instead", + format(". It will be removed on or after %4d-%02d.", year, month), + "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", + ] + warn "#{msg.join}." unless Gem::Deprecate.skip + send old, *args, &block + end + ruby2_keywords name if respond_to?(:ruby2_keywords, true) end - ruby2_keywords name if respond_to?(:ruby2_keywords, true) end - end - ## - # Simple deprecation method that deprecates +name+ by wrapping it up - # in a dummy method. It warns on each call to the dummy method - # telling the user of +repl+ (unless +repl+ is :none) and the - # Rubygems version that it is planned to go away. + ## + # Simple deprecation method that deprecates +name+ by wrapping it up + # in a dummy method. It warns on each call to the dummy method + # telling the user of +repl+ (unless +repl+ is :none) and the + # Rubygems version that it is planned to go away. - def rubygems_deprecate(name, replacement=:none) - class_eval do - old = "_deprecated_#{name}" - alias_method old, name - define_method name do |*args, &block| - klass = is_a? Module - target = klass ? "#{self}." : "#{self.class}#" - msg = [ - "NOTE: #{target}#{name} is deprecated", - replacement == :none ? " with no replacement" : "; use #{replacement} instead", - ". It will be removed in Rubygems #{Gem::Deprecate.next_rubygems_major_version}", - "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", - ] - warn "#{msg.join}." unless Gem::Deprecate.skip - send old, *args, &block + def rubygems_deprecate(name, replacement=:none) + class_eval do + old = "_deprecated_#{name}" + alias_method old, name + define_method name do |*args, &block| + klass = is_a? Module + target = klass ? "#{self}." : "#{self.class}#" + msg = [ + "NOTE: #{target}#{name} is deprecated", + replacement == :none ? " with no replacement" : "; use #{replacement} instead", + ". It will be removed in Rubygems #{Gem::Deprecate.next_rubygems_major_version}", + "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", + ] + warn "#{msg.join}." unless Gem::Deprecate.skip + send old, *args, &block + end + ruby2_keywords name if respond_to?(:ruby2_keywords, true) end - ruby2_keywords name if respond_to?(:ruby2_keywords, true) end - end - # Deprecation method to deprecate Rubygems commands - def rubygems_deprecate_command(version = Gem::Deprecate.next_rubygems_major_version) - class_eval do - define_method "deprecated?" do - true - end + # Deprecation method to deprecate Rubygems commands + def rubygems_deprecate_command(version = Gem::Deprecate.next_rubygems_major_version) + class_eval do + define_method "deprecated?" do + true + end - define_method "deprecation_warning" do - msg = [ - "#{command} command is deprecated", - ". It will be removed in Rubygems #{version}.\n", - ] + define_method "deprecation_warning" do + msg = [ + "#{command} command is deprecated", + ". It will be removed in Rubygems #{version}.\n", + ] - alert_warning msg.join.to_s unless Gem::Deprecate.skip + alert_warning msg.join.to_s unless Gem::Deprecate.skip + end end end - end - module_function :rubygems_deprecate, :rubygems_deprecate_command, :skip_during + module_function :rubygems_deprecate, :rubygems_deprecate_command, :skip_during + end end diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb index 86a0e73f28..09ad1407c2 100644 --- a/lib/rubygems/ext/cargo_builder.rb +++ b/lib/rubygems/ext/cargo_builder.rb @@ -184,6 +184,7 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder end def cargo_dylib_path(dest_path, crate_name) + so_ext = RbConfig::CONFIG["SOEXT"] prefix = so_ext == "dll" ? "" : "lib" path_parts = [dest_path] path_parts << ENV["CARGO_BUILD_TARGET"] if ENV["CARGO_BUILD_TARGET"] @@ -312,22 +313,6 @@ EOF deffile_path end - # We have to basically reimplement <code>RbConfig::CONFIG['SOEXT']</code> here to support - # Ruby < 2.5 - # - # @see https://github.com/ruby/ruby/blob/c87c027f18c005460746a74c07cd80ee355b16e4/configure.ac#L3185 - def so_ext - return RbConfig::CONFIG["SOEXT"] if RbConfig::CONFIG.key?("SOEXT") - - if win_target? - "dll" - elsif darwin_target? - "dylib" - else - "so" - end - end - # Corresponds to $(LIBPATH) in mkmf def mkmf_libpath ["-L", "native=#{makefile_config("libdir")}"] diff --git a/lib/rubygems/gemcutter_utilities/webauthn_poller.rb b/lib/rubygems/gemcutter_utilities/webauthn_poller.rb index 0fdd1d5bf4..fe3f163a88 100644 --- a/lib/rubygems/gemcutter_utilities/webauthn_poller.rb +++ b/lib/rubygems/gemcutter_utilities/webauthn_poller.rb @@ -69,8 +69,10 @@ module Gem::GemcutterUtilities rubygems_api_request(:get, "api/v1/webauthn_verification/#{webauthn_token}/status.json") do |request| if credentials.empty? request.add_field "Authorization", api_key + elsif credentials[:identifier] && credentials[:password] + request.basic_auth credentials[:identifier], credentials[:password] else - request.basic_auth credentials[:email], credentials[:password] + raise Gem::WebauthnVerificationError, "Provided missing credentials" end end end diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 8f6f9a5aa8..844f292ba2 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -344,7 +344,7 @@ class Gem::Installer say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil? - Gem::Specification.add_spec(spec) + Gem::Specification.add_spec(spec) unless @install_dir load_plugin diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index 1d5d764237..c855423ed7 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -7,7 +7,6 @@ # rubocop:enable Style/AsciiComments -require_relative "../rubygems" require_relative "security" require_relative "user_interaction" @@ -295,7 +294,6 @@ class Gem::Package Gem.load_yaml - @spec.mark_version @spec.validate true, strict_validation unless skip_validation setup_signer( @@ -528,12 +526,13 @@ EOM # Loads a Gem::Specification from the TarEntry +entry+ def load_spec(entry) # :nodoc: + limit = 10 * 1024 * 1024 case entry.full_name when "metadata" then - @spec = Gem::Specification.from_yaml entry.read + @spec = Gem::Specification.from_yaml limit_read(entry, "metadata", limit) when "metadata.gz" then Zlib::GzipReader.wrap(entry, external_encoding: Encoding::UTF_8) do |gzio| - @spec = Gem::Specification.from_yaml gzio.read + @spec = Gem::Specification.from_yaml limit_read(gzio, "metadata.gz", limit) end end end @@ -557,7 +556,7 @@ EOM @checksums = gem.seek "checksums.yaml.gz" do |entry| Zlib::GzipReader.wrap entry do |gz_io| - Gem::SafeYAML.safe_load gz_io.read + Gem::SafeYAML.safe_load limit_read(gz_io, "checksums.yaml.gz", 10 * 1024 * 1024) end end end @@ -664,7 +663,7 @@ EOM case file_name when /\.sig$/ then - @signatures[$`] = entry.read if @security_policy + @signatures[$`] = limit_read(entry, file_name, 1024 * 1024) if @security_policy return else digest entry @@ -724,6 +723,12 @@ EOM IO.copy_stream(src, dst) end end + + def limit_read(io, name, limit) + bytes = io.read(limit + 1) + raise Gem::Package::FormatError, "#{name} is too big (over #{limit} bytes)" if bytes.size > limit + bytes + end end require_relative "package/digest_io" diff --git a/lib/rubygems/package/tar_header.rb b/lib/rubygems/package/tar_header.rb index 087f13f6c9..dd5e835a1e 100644 --- a/lib/rubygems/package/tar_header.rb +++ b/lib/rubygems/package/tar_header.rb @@ -95,14 +95,14 @@ class Gem::Package::TarHeader attr_reader(*FIELDS) - EMPTY_HEADER = ("\0" * 512).freeze # :nodoc: + EMPTY_HEADER = ("\0" * 512).b.freeze # :nodoc: ## # Creates a tar header from IO +stream+ def self.from(stream) header = stream.read 512 - empty = (header == EMPTY_HEADER) + return EMPTY if header == EMPTY_HEADER fields = header.unpack UNPACK_FORMAT @@ -123,7 +123,7 @@ class Gem::Package::TarHeader devminor: strict_oct(fields.shift), prefix: fields.shift, - empty: empty + empty: false end def self.strict_oct(str) @@ -172,6 +172,22 @@ class Gem::Package::TarHeader @empty = vals[:empty] end + EMPTY = new({ # :nodoc: + checksum: 0, + gname: "", + linkname: "", + magic: "", + mode: 0, + name: "", + prefix: "", + size: 0, + uname: "", + version: 0, + + empty: true, + }).freeze + private_constant :EMPTY + ## # Is the tar entry empty? @@ -241,7 +257,7 @@ class Gem::Package::TarHeader header = header.pack PACK_FORMAT - header << ("\0" * ((512 - header.size) % 512)) + header.ljust 512, "\0" end def oct(num, len) diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb index 48b7344aee..d54ad12880 100644 --- a/lib/rubygems/platform.rb +++ b/lib/rubygems/platform.rb @@ -134,6 +134,7 @@ class Gem::Platform when /netbsdelf/ then ["netbsdelf", nil] when /openbsd(\d+\.\d+)?/ then ["openbsd", $1] when /solaris(\d+\.\d+)?/ then ["solaris", $1] + when /wasi/ then ["wasi", nil] # test when /^(\w+_platform)(\d+)?/ then [$1, $2] else ["unknown", nil] diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 29139cf725..57f9b45cf7 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -11,6 +11,7 @@ require_relative "deprecate" require_relative "basic_specification" require_relative "stub_specification" require_relative "platform" +require_relative "specification_record" require_relative "util/list" require "rbconfig" @@ -179,22 +180,12 @@ 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=].freeze # :nodoc: + REMOVED_METHODS = [:rubyforge_project=, :mark_version].freeze # :nodoc: def removed_method_calls @removed_method_calls ||= [] end @@ -770,7 +761,7 @@ class Gem::Specification < Gem::BasicSpecification 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: @@ -788,26 +779,9 @@ class Gem::Specification < Gem::BasicSpecification 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| @@ -820,13 +794,7 @@ class Gem::Specification < Gem::BasicSpecification # 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 ## @@ -845,13 +813,7 @@ class Gem::Specification < Gem::BasicSpecification # 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 ## @@ -859,12 +821,7 @@ class Gem::Specification < Gem::BasicSpecification # optionally filtering out specs not matching the current platform # def self.stubs_for_pattern(pattern, match_platform = true) # :nodoc: - installed_stubs = installed_stubs(Gem::Specification.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: @@ -893,23 +850,14 @@ class Gem::Specification < Gem::BasicSpecification # 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 ## @@ -923,27 +871,17 @@ class Gem::Specification < Gem::BasicSpecification end ## - # Sets the known specs to +specs+. Not guaranteed to work for you in - # the future. Use at your own risk. Caveat emptor. Doomy doom doom. - # Etc etc. - # - #-- - # Makes +specs+ the known specs - # Listen, time is a river - # Winter comes, code breaks - # - # -- wilsonb + # 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 ## @@ -968,9 +906,7 @@ class Gem::Specification < Gem::BasicSpecification # Return the directories that Specification uses to find specs. def self.dirs - @@dirs ||= Gem.path.collect do |dir| - File.join dir, "specifications" - end + @@dirs ||= Gem::SpecificationRecord.dirs_from(Gem.path) end ## @@ -980,7 +916,7 @@ class Gem::Specification < Gem::BasicSpecification def self.dirs=(dirs) reset - @@dirs = Array(dirs).map {|dir| File.join dir, "specifications" } + @@dirs = Gem::SpecificationRecord.dirs_from(Array(dirs)) end extend Enumerable @@ -989,21 +925,15 @@ class Gem::Specification < Gem::BasicSpecification # Enumerate every known spec. See ::dirs= and ::add_spec to set the list of # specs. - def self.each - return enum_for(:each) unless block_given? - - _all.each do |x| - yield x - end + def self.each(&block) + specification_record.each(&block) end ## # Returns every spec that matches +name+ and optional +requirements+. def self.find_all_by_name(name, *requirements) - requirements = Gem::Requirement.default if requirements.empty? - - Gem::Dependency.new(name, *requirements).matching_specs + specification_record.find_all_by_name(name, *requirements) end ## @@ -1033,12 +963,7 @@ class Gem::Specification < Gem::BasicSpecification # 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 ## @@ -1046,19 +971,15 @@ class Gem::Specification < Gem::BasicSpecification # 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 - 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 + ## + # Return the best specification that contains the file matching +path+, among + # those already activated. - stub.this + def self.find_active_stub_by_path(path) + specification_record.find_active_stub_by_path(path) end ## @@ -1125,14 +1046,14 @@ class Gem::Specification < Gem::BasicSpecification # +prerelease+ is true. def self.latest_specs(prerelease = false) - _latest_specs Gem::Specification.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: @@ -1270,7 +1191,7 @@ class Gem::Specification < Gem::BasicSpecification 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? @@ -1291,6 +1212,13 @@ class Gem::Specification < Gem::BasicSpecification 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 } @@ -1874,8 +1802,6 @@ class Gem::Specification < Gem::BasicSpecification end def encode_with(coder) # :nodoc: - mark_version - coder.add "name", @name coder.add "version", @version platform = case @original_platform @@ -2171,13 +2097,6 @@ class Gem::Specification < Gem::BasicSpecification end ## - # Sets the rubygems_version to the current RubyGems version. - - def mark_version - @rubygems_version = Gem::VERSION - end - - ## # Track removed method calls to warn about during build time. # Warn about unknown attributes while loading a spec. @@ -2494,7 +2413,6 @@ class Gem::Specification < Gem::BasicSpecification # still have their default values are omitted. def to_ruby - mark_version result = [] result << "# -*- encoding: utf-8 -*-" result << "#{Gem::StubSpecification::PREFIX}#{name} #{version} #{platform} #{raw_require_paths.join("\0")}" diff --git a/lib/rubygems/specification_policy.rb b/lib/rubygems/specification_policy.rb index 516c26f53c..812b0f889e 100644 --- a/lib/rubygems/specification_policy.rb +++ b/lib/rubygems/specification_policy.rb @@ -274,7 +274,9 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use: return if rubygems_version == Gem::VERSION - error "expected RubyGems version #{Gem::VERSION}, was #{rubygems_version}" + warning "expected RubyGems version #{Gem::VERSION}, was #{rubygems_version}" + + @specification.rubygems_version = Gem::VERSION end def validate_required_attributes diff --git a/lib/rubygems/specification_record.rb b/lib/rubygems/specification_record.rb new file mode 100644 index 0000000000..dd6aa7eafa --- /dev/null +++ b/lib/rubygems/specification_record.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +module Gem + class SpecificationRecord + def self.dirs_from(paths) + paths.map do |path| + File.join(path, "specifications") + end + end + + def self.from_path(path) + new(dirs_from([path])) + end + + def initialize(dirs) + @all = nil + @stubs = nil + @stubs_by_name = {} + @spec_with_requirable_file = {} + @active_stub_with_requirable_file = {} + + @dirs = dirs + end + + # Sentinel object to represent "not found" stubs + NOT_FOUND = Struct.new(:to_spec, :this).new + private_constant :NOT_FOUND + + ## + # Returns the list of all specifications in the record + + def all + @all ||= Gem.loaded_specs.values | stubs.map(&:to_spec) + end + + ## + # Returns a Gem::StubSpecification for every specification in the record + + def 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 + end + + ## + # Returns a Gem::StubSpecification for every specification in the record + # named +name+ only returns stubs that match Gem.platforms + + def 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 + end + + ## + # Finds stub specifications matching a pattern in the record, optionally + # filtering out specs not matching the current platform + + def stubs_for_pattern(pattern, match_platform = true) + installed_stubs = installed_stubs(pattern) + installed_stubs.select! {|s| Gem::Platform.match_spec? s } if match_platform + stubs = installed_stubs + Gem::Specification.default_stubs(pattern) + stubs = stubs.uniq(&:full_name) + Gem::Specification._resort!(stubs) + stubs + end + + ## + # Adds +spec+ to the the record, keeping the collection properly sorted. + + def add_spec(spec) + return if all.include? spec + + all << spec + stubs << spec + (@stubs_by_name[spec.name] ||= []) << spec + + Gem::Specification._resort!(@stubs_by_name[spec.name]) + Gem::Specification._resort!(stubs) + end + + ## + # Removes +spec+ from the record. + + def remove_spec(spec) + all.delete spec.to_spec + stubs.delete spec + (@stubs_by_name[spec.name] || []).delete spec + end + + ## + # Sets the specs known by the record to +specs+. + + def all=(specs) + @stubs_by_name = specs.group_by(&:name) + @all = @stubs = specs + end + + ## + # Return full names of all specs in the record in sorted order. + + def all_names + all.map(&:full_name) + end + + include Enumerable + + ## + # Enumerate every known spec. + + def each + return enum_for(:each) unless block_given? + + all.each do |x| + yield x + end + end + + ## + # Returns every spec in the record that matches +name+ and optional +requirements+. + + def find_all_by_name(name, *requirements) + req = Gem::Requirement.create(*requirements) + env_req = Gem.env_requirement(name) + + matches = stubs_for(name).find_all do |spec| + req.satisfied_by?(spec.version) && env_req.satisfied_by?(spec.version) + end.map(&:to_spec) + + if name == "bundler" && !req.specific? + require_relative "bundler_version_finder" + Gem::BundlerVersionFinder.prioritize!(matches) + end + + matches + end + + ## + # Return the best specification in the record that contains the file matching +path+. + + def 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 + end + + ## + # Return the best specification in the record that contains the file + # matching +path+ amongst the specs that are not activated. + + def find_inactive_by_path(path) + stub = stubs.find do |s| + next if s.activated? + s.contains_requirable_file? path + end + stub&.to_spec + end + + ## + # Return the best specification in the record that contains the file + # matching +path+, among those already activated. + + def 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 + end + + ## + # Return the latest specs in the record, optionally including prerelease + # specs if +prerelease+ is true. + + def latest_specs(prerelease) + Gem::Specification._latest_specs stubs, prerelease + end + + ## + # Return the latest installed spec in the record for gem +name+. + + def latest_spec_for(name) + latest_specs(true).find {|installed_spec| installed_spec.name == name } + end + + private + + def installed_stubs(pattern) + map_stubs(pattern) do |path, base_dir, gems_dir| + Gem::StubSpecification.gemspec_stub(path, base_dir, gems_dir) + end + end + + def map_stubs(pattern) + @dirs.flat_map do |dir| + base_dir = File.dirname dir + gems_dir = File.join base_dir, "gems" + Gem::Specification.gemspec_stubs_in(dir, pattern) {|path| yield path, base_dir, gems_dir } + end + end + end +end diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb index c96df2a085..4d72f6fd0a 100644 --- a/lib/rubygems/uninstaller.rb +++ b/lib/rubygems/uninstaller.rb @@ -32,7 +32,7 @@ class Gem::Uninstaller attr_reader :bin_dir ## - # The gem repository the gem will be installed into + # The gem repository the gem will be uninstalled from attr_reader :gem_home @@ -49,8 +49,9 @@ class Gem::Uninstaller # TODO: document the valid options @gem = gem @version = options[:version] || Gem::Requirement.default - @gem_home = File.realpath(options[:install_dir] || Gem.dir) - @plugins_dir = Gem.plugindir(@gem_home) + @install_dir = options[:install_dir] + @gem_home = File.realpath(@install_dir || Gem.dir) + @user_dir = File.exist?(Gem.user_dir) ? File.realpath(Gem.user_dir) : Gem.user_dir @force_executables = options[:executables] @force_all = options[:all] @force_ignore = options[:ignore] @@ -70,7 +71,7 @@ class Gem::Uninstaller # only add user directory if install_dir is not set @user_install = false - @user_install = options[:user_install] unless options[:install_dir] + @user_install = options[:user_install] unless @install_dir # Optimization: populated during #uninstall @default_specs_matching_uninstall_params = [] @@ -105,7 +106,7 @@ class Gem::Uninstaller list, other_repo_specs = list.partition do |spec| @gem_home == spec.base_dir || - (@user_install && spec.base_dir == Gem.user_dir) + (@user_install && spec.base_dir == @user_dir) end list.sort! @@ -239,7 +240,7 @@ class Gem::Uninstaller def remove(spec) unless path_ok?(@gem_home, spec) || - (@user_install && path_ok?(Gem.user_dir, spec)) + (@user_install && path_ok?(@user_dir, spec)) e = Gem::GemNotInHomeException.new \ "Gem '#{spec.full_name}' is not installed in directory #{@gem_home}" e.spec = spec @@ -284,17 +285,18 @@ class Gem::Uninstaller def remove_plugins(spec) # :nodoc: return if spec.plugins.empty? - remove_plugins_for(spec, @plugins_dir) + remove_plugins_for(spec, plugin_dir_for(spec)) end ## # Regenerates plugin wrappers after removal. def regenerate_plugins - latest = Gem::Specification.latest_spec_for(@spec.name) + specification_record = @install_dir ? Gem::SpecificationRecord.from_path(@install_dir) : Gem::Specification.specification_record + latest = specification_record.latest_spec_for(@spec.name) return if latest.nil? - regenerate_plugins_for(latest, @plugins_dir) + regenerate_plugins_for(latest, plugin_dir_for(@spec)) end ## @@ -406,4 +408,8 @@ class Gem::Uninstaller say "Gem #{spec.full_name} cannot be uninstalled because it is a default gem" end end + + def plugin_dir_for(spec) + Gem.plugindir(spec.base_dir) + end end |