diff --git lib/rubygems.rb lib/rubygems.rb index 246956bb14..0c698b2813 100644 --- lib/rubygems.rb +++ lib/rubygems.rb @@ -10,7 +10,7 @@ require 'thread' module Gem - VERSION = '2.6.8' + VERSION = "2.6.10" end # Must be first since it unloads the prelude from 1.9.2 diff --git lib/rubygems/commands/setup_command.rb lib/rubygems/commands/setup_command.rb index ebb08d24d7..2c29786224 100644 --- lib/rubygems/commands/setup_command.rb +++ lib/rubygems/commands/setup_command.rb @@ -47,7 +47,7 @@ def initialize end add_option '--[no-]document [TYPES]', Array, - 'Generate documentation for RubyGems.', + 'Generate documentation for RubyGems', 'List the documentation types you wish to', 'generate. For example: rdoc,ri' do |value, options| options[:document] = case value diff --git lib/rubygems/core_ext/kernel_require.rb lib/rubygems/core_ext/kernel_require.rb index aa56ab5ec4..71a301c323 100755 --- lib/rubygems/core_ext/kernel_require.rb +++ lib/rubygems/core_ext/kernel_require.rb @@ -44,7 +44,7 @@ def require path spec = Gem.find_unresolved_default_spec(path) if spec Gem.remove_unresolved_default_spec(spec) - gem(spec.name) + Kernel.send(:gem, spec.name) end # If there are no unresolved deps, then we can use just try diff --git lib/rubygems/ext/ext_conf_builder.rb lib/rubygems/ext/ext_conf_builder.rb index 59e243b972..b6dde01950 100644 --- lib/rubygems/ext/ext_conf_builder.rb +++ lib/rubygems/ext/ext_conf_builder.rb @@ -48,9 +48,11 @@ def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil) run cmd, results ensure if File.exist? 'mkmf.log' - results << "To see why this extension failed to compile, please check" \ - " the mkmf.log which can be found here:\n" - results << " " + File.join(dest_path, 'mkmf.log') + "\n" + unless $?.success? then + results << "To see why this extension failed to compile, please check" \ + " the mkmf.log which can be found here:\n" + results << " " + File.join(dest_path, 'mkmf.log') + "\n" + end FileUtils.mv 'mkmf.log', dest_path end siteconf.unlink diff --git lib/rubygems/ext/rake_builder.rb lib/rubygems/ext/rake_builder.rb index 682f1253e1..ec904e6c11 100644 --- lib/rubygems/ext/rake_builder.rb +++ lib/rubygems/ext/rake_builder.rb @@ -9,7 +9,7 @@ class Gem::Ext::RakeBuilder < Gem::Ext::Builder def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil) if File.basename(extension) =~ /mkrf_conf/i then - cmd = "#{Gem.ruby} #{File.basename extension}" + cmd = "#{Gem.ruby} #{File.basename extension}".dup cmd << " #{args.join " "}" unless args.empty? run cmd, results end diff --git lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb index 40136eb894..139165102e 100644 --- lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb +++ lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb @@ -119,6 +119,7 @@ def to_dot # {Vertex#successors} def ==(other) return false unless other + return true if equal?(other) vertices.each do |name, vertex| other_vertex = other.vertex_named(name) return false unless other_vertex @@ -134,6 +135,7 @@ def ==(other) def add_child_vertex(name, payload, parent_names, requirement) root = !parent_names.delete(nil) { true } vertex = add_vertex(name, payload, root) + vertex.explicit_requirements << requirement if root parent_names.each do |parent_name| parent_node = vertex_named(parent_name) add_edge(parent_node, vertex, requirement) @@ -152,7 +154,7 @@ def add_vertex(name, payload, root = false) # Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively # removing any non-root vertices that were orphaned in the process # @param [String] name - # @return [void] + # @return [Array] the vertices which have been detached def detach_vertex_named(name) log.detach_vertex_named(self, name) end diff --git lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb index 9ad34e8fe0..fa03e2d365 100644 --- lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb +++ lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb @@ -14,16 +14,23 @@ def self.action_name # (see Action#up) def up(graph) - return unless @vertex = graph.vertices.delete(name) + return [] unless @vertex = graph.vertices.delete(name) + + removed_vertices = [@vertex] @vertex.outgoing_edges.each do |e| v = e.destination v.incoming_edges.delete(e) - graph.detach_vertex_named(v.name) unless v.root? || v.predecessors.any? + if !v.root? && v.incoming_edges.empty? + removed_vertices.concat graph.detach_vertex_named(v.name) + end end + @vertex.incoming_edges.each do |e| v = e.origin v.outgoing_edges.delete(e) end + + removed_vertices end # (see Action#down) diff --git lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb index 88d109c94f..b5a0688a32 100644 --- lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb +++ lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb @@ -81,6 +81,7 @@ def inspect # @return [Boolean] whether the two vertices are equal, determined # by a recursive traversal of each {Vertex#successors} def ==(other) + return true if equal?(other) shallow_eql?(other) && successors.to_set == other.successors.to_set end @@ -89,6 +90,7 @@ def ==(other) # @return [Boolean] whether the two vertices are equal, determined # solely by {#name} and {#payload} equality def shallow_eql?(other) + return true if equal?(other) other && name == other.name && payload == other.payload diff --git lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb index c50fa77bd5..dfddafe993 100644 --- lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb +++ lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Gem::Resolver::Molinillo # The version of Gem::Resolver::Molinillo. - VERSION = '0.5.3'.freeze + VERSION = '0.5.5'.freeze end diff --git lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb index 54a6c3fc17..ea497ddcaf 100644 --- lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb +++ lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb @@ -194,18 +194,20 @@ def unwind_for_conflict def state_index_for_unwind current_requirement = requirement existing_requirement = requirement_for_existing_name(name) - until current_requirement.nil? - current_state = find_state_for(current_requirement) - return states.index(current_state) if state_any?(current_state) - current_requirement = parent_of(current_requirement) + index = -1 + [current_requirement, existing_requirement].each do |r| + until r.nil? + current_state = find_state_for(r) + if state_any?(current_state) + current_index = states.index(current_state) + index = current_index if current_index > index + break + end + r = parent_of(r) + end end - until existing_requirement.nil? - existing_state = find_state_for(existing_requirement) - return states.index(existing_state) if state_any?(existing_state) - existing_requirement = parent_of(existing_requirement) - end - -1 + index end # @return [Object] the requirement that led to `requirement` being added @@ -364,19 +366,17 @@ def fixup_swapped_children(vertex) if matching_deps.empty? && !succ.root? && succ.predecessors.to_a == [vertex] debug(depth) { "Removing orphaned spec #{succ.name} after swapping #{name}" } succ.requirements.each { |r| @parent_of.delete(r) } - activated.detach_vertex_named(succ.name) - all_successor_names = succ.recursive_successors.map(&:name) - - requirements.delete_if do |requirement| - requirement_name = name_for(requirement) - (requirement_name == succ.name) || all_successor_names.include?(requirement_name) + removed_names = activated.detach_vertex_named(succ.name).map(&:name) + requirements.delete_if do |r| + # the only removed vertices are those with no other requirements, + # so it's safe to delete only based upon name here + removed_names.include?(name_for(r)) end elsif !matching_deps.include?(outgoing_edge.requirement) activated.delete_edge(outgoing_edge) requirements.delete(outgoing_edge.requirement) end - matching_deps.delete(outgoing_edge.requirement) end end diff --git lib/rubygems/server.rb lib/rubygems/server.rb index 63dfe31b35..81df0e608e 100644 --- lib/rubygems/server.rb +++ lib/rubygems/server.rb @@ -2,6 +2,7 @@ require 'webrick' require 'zlib' require 'erb' +require 'uri' require 'rubygems' require 'rubygems/rdoc' @@ -68,7 +69,7 @@ class Gem::Server

Summary

There are <%=values["gem_count"]%> gems installed:

- <%= values["specs"].map { |v| "#{h v["name"]}" }.join ', ' %>. + <%= values["specs"].map { |v| "#{h v["name"]}" }.join ', ' %>.

Gems

@@ -81,20 +82,20 @@ class Gem::Server <%=h spec["name"]%> <%=h spec["version"]%> <% if spec["ri_installed"] || spec["rdoc_installed"] then %> - ">[rdoc] + ">[rdoc] <% else %> [rdoc] <% end %> <% if spec["homepage"] then %> - " title="<%=h spec["homepage"]%>">[www] + " title="<%=h spec["homepage"]%>">[www] <% else %> [www] <% end %> <% if spec["has_deps"] then %> - depends on - <%= spec["dependencies"].map { |v| "#{h v["name"]}" }.join ', ' %>. + <%= spec["dependencies"].map { |v| "#{h v["name"]}" }.join ', ' %>. <% end %>
@@ -455,6 +456,12 @@ def add_date res end.max end + def uri_encode(str) + str.gsub(URI::UNSAFE) do |match| + match.each_byte.map { |c| sprintf('%%%02X', c.ord) }.join + end + end + def doc_root gem_name if have_rdoc_4_plus? then "/doc_root/#{u gem_name}/" diff --git lib/rubygems/stub_specification.rb lib/rubygems/stub_specification.rb index 1c56a102c5..b741843124 100644 --- lib/rubygems/stub_specification.rb +++ lib/rubygems/stub_specification.rb @@ -39,7 +39,12 @@ class StubLine # :nodoc: all def initialize data, extensions parts = data[PREFIX.length..-1].split(" ".freeze, 4) @name = parts[0].freeze - @version = Gem::Version.new parts[1] + @version = if Gem::Version.correct?(parts[1]) + Gem::Version.new(parts[1]) + else + Gem::Version.new(0) + end + @platform = Gem::Platform.new parts[2] @extensions = extensions @full_name = if platform == Gem::Platform::RUBY diff --git lib/rubygems/version.rb lib/rubygems/version.rb index 2f6cfae6ed..17dd7b0795 100644 --- lib/rubygems/version.rb +++ lib/rubygems/version.rb @@ -204,8 +204,12 @@ def self.new version # :nodoc: # series of digits or ASCII letters separated by dots. def initialize version - raise ArgumentError, "Malformed version number string #{version}" unless - self.class.correct?(version) + unless self.class.correct?(version) + raise ArgumentError, "Malformed version number string #{version}" + end + + # If version is an empty string convert it to 0 + version = 0 if version =~ /\A\s*\Z/ @version = version.to_s.strip.gsub("-",".pre.") @segments = nil diff --git test/rubygems/test_gem.rb test/rubygems/test_gem.rb index 787350727f..e1ebebffb5 100644 --- test/rubygems/test_gem.rb +++ test/rubygems/test_gem.rb @@ -1451,6 +1451,7 @@ def test_looks_for_gemdeps_files_automatically_on_start ENV['RUBYGEMS_GEMDEPS'] = "-" out = `#{Gem.ruby.dup.untaint} -I "#{LIB_PATH.untaint}" -rubygems -e "p Gem.loaded_specs.values.map(&:full_name).sort"` + out.sub!(/, "openssl-#{Gem::Version::VERSION_PATTERN}"/, "") assert_equal '["a-1", "b-1", "c-1"]', out.strip end @@ -1484,6 +1485,7 @@ def test_looks_for_gemdeps_files_automatically_on_start_in_parent_dir out = Dir.chdir "sub1" do `#{Gem.ruby.dup.untaint} -I "#{LIB_PATH.untaint}" -rubygems -e "p Gem.loaded_specs.values.map(&:full_name).sort"` end + out.sub!(/, "openssl-#{Gem::Version::VERSION_PATTERN}"/, "") Dir.rmdir "sub1" diff --git test/rubygems/test_gem_ext_ext_conf_builder.rb test/rubygems/test_gem_ext_ext_conf_builder.rb index f5d10fae50..b43ebf00d9 100644 --- test/rubygems/test_gem_ext_ext_conf_builder.rb +++ test/rubygems/test_gem_ext_ext_conf_builder.rb @@ -111,6 +111,29 @@ def test_class_build_extconf_fail assert_match(/^#{Gem.ruby}.* extconf.rb/, output[1]) assert_match(File.join(@dest_path, 'mkmf.log'), output[4]) + assert_includes(output, "To see why this extension failed to compile, please check the mkmf.log which can be found here:\n") + + assert_path_exists File.join @dest_path, 'mkmf.log' + end + + def test_class_build_extconf_success_without_warning + if vc_windows? && !nmake_found? + skip("test_class_build_extconf_fail skipped - nmake not found") + end + + File.open File.join(@ext, 'extconf.rb'), 'w' do |extconf| + extconf.puts "require 'mkmf'" + extconf.puts "File.open('mkmf.log', 'w'){|f| f.write('a')}" + extconf.puts "create_makefile 'foo'" + end + + output = [] + + Dir.chdir @ext do + Gem::Ext::ExtConfBuilder.build 'extconf.rb', nil, @dest_path, output + end + + refute_includes(output, "To see why this extension failed to compile, please check the mkmf.log which can be found here:\n") assert_path_exists File.join @dest_path, 'mkmf.log' end diff --git test/rubygems/test_gem_ext_rake_builder.rb test/rubygems/test_gem_ext_rake_builder.rb index b728f8f30a..9f59f51660 100644 --- test/rubygems/test_gem_ext_rake_builder.rb +++ test/rubygems/test_gem_ext_rake_builder.rb @@ -14,14 +14,7 @@ def setup end def test_class_build - File.open File.join(@ext, 'mkrf_conf.rb'), 'w' do |mkrf_conf| - mkrf_conf.puts <<-EO_MKRF - File.open("Rakefile","w") do |f| - f.puts "task :default" - end - EO_MKRF - end - + create_temp_mkrf_file('task :default') output = [] realdir = nil # HACK /tmp vs. /private/tmp @@ -39,15 +32,31 @@ def test_class_build end end + # https://github.com/rubygems/rubygems/pull/1819 + # + # It should not fail with a non-empty args list either + def test_class_build_with_args + create_temp_mkrf_file('task :default') + output = [] + realdir = nil # HACK /tmp vs. /private/tmp + + build_rake_in do |rake| + Dir.chdir @ext do + realdir = Dir.pwd + non_empty_args_list = [''] + Gem::Ext::RakeBuilder.build 'mkrf_conf.rb', nil, @dest_path, output, non_empty_args_list + end + + output = output.join "\n" + + refute_match %r%^rake failed:%, output + assert_match %r%^#{Regexp.escape @@ruby} mkrf_conf\.rb%, output + assert_match %r%^#{Regexp.escape rake} RUBYARCHDIR=#{Regexp.escape @dest_path} RUBYLIBDIR=#{Regexp.escape @dest_path}%, output + end + end + def test_class_build_fail - File.open File.join(@ext, 'mkrf_conf.rb'), 'w' do |mkrf_conf| - mkrf_conf.puts <<-EO_MKRF - File.open("Rakefile","w") do |f| - f.puts "task :default do abort 'fail' end" - end - EO_MKRF - end - + create_temp_mkrf_file("task :default do abort 'fail' end") output = [] build_rake_in(false) do |rake| @@ -60,6 +69,14 @@ def test_class_build_fail assert_match %r%^rake failed%, error.message end end - + + def create_temp_mkrf_file(rakefile_content) + File.open File.join(@ext, 'mkrf_conf.rb'), 'w' do |mkrf_conf| + mkrf_conf.puts <<-EO_MKRF + File.open("Rakefile","w") do |f| + f.puts "#{rakefile_content}" + end + EO_MKRF + end + end end - diff --git test/rubygems/test_gem_remote_fetcher.rb test/rubygems/test_gem_remote_fetcher.rb index ca62a8342e..cb994462cd 100644 --- test/rubygems/test_gem_remote_fetcher.rb +++ test/rubygems/test_gem_remote_fetcher.rb @@ -163,7 +163,7 @@ def test_fetch_size_socket_error fetcher = Gem::RemoteFetcher.new nil @fetcher = fetcher def fetcher.request(uri, request_class, last_modified = nil) - raise SocketError, "tarded" + raise SocketError, "oops" end uri = 'http://gems.example.com/yaml' @@ -171,7 +171,7 @@ def fetcher.request(uri, request_class, last_modified = nil) fetcher.fetch_size uri end - assert_equal "SocketError: tarded (#{uri})", e.message + assert_equal "SocketError: oops (#{uri})", e.message end def test_no_proxy diff --git test/rubygems/test_gem_server.rb test/rubygems/test_gem_server.rb index 0ece6d67ef..4873fac5b6 100644 --- test/rubygems/test_gem_server.rb +++ test/rubygems/test_gem_server.rb @@ -392,6 +392,22 @@ def test_specs_gz Marshal.load(Gem.gunzip(@res.body)) end + def test_uri_encode + url_safe = @server.uri_encode 'http://rubyonrails.org/">malicious_content' + assert_equal url_safe, 'http://rubyonrails.org/%22%3Emalicious_content%3C/a%3E' + end + + # Regression test for issue #1793: incorrect URL encoding. + # Checking that no URLs have had '://' incorrectly encoded + def test_regression_1793 + data = StringIO.new "GET / HTTP/1.0\r\n\r\n" + @req.parse data + + @server.root @req, @res + + refute_match %r|%3A%2F%2F|, @res.body + end + def util_listen webrick = Object.new webrick.instance_variable_set :@listeners, [] diff --git test/rubygems/test_gem_specification.rb test/rubygems/test_gem_specification.rb index 2c0ea770b5..d43289d745 100644 --- test/rubygems/test_gem_specification.rb +++ test/rubygems/test_gem_specification.rb @@ -1260,7 +1260,7 @@ def test_initialize_copy_broken s.version = '1' end - spec.instance_variable_set :@licenses, Object.new.singleton_class + spec.instance_variable_set :@licenses, (class << (Object.new);self;end) spec.loaded_from = '/path/to/file' e = assert_raises Gem::FormatException do diff --git test/rubygems/test_gem_stub_specification.rb test/rubygems/test_gem_stub_specification.rb index 4cba13e139..5316449fba 100644 --- test/rubygems/test_gem_stub_specification.rb +++ test/rubygems/test_gem_stub_specification.rb @@ -33,6 +33,20 @@ def test_initialize_extension assert_equal %w[ext/stub_e/extconf.rb], stub.extensions end + def test_initialize_version + stub = stub_with_version + + assert_equal 'stub_v', stub.name + assert_equal v(2), stub.version + end + + def test_initialize_with_empty_version + stub = stub_without_version + + assert_equal 'stub_v', stub.name + assert_equal v(0), stub.version + end + def test_initialize_missing_stubline stub = Gem::StubSpecification.gemspec_stub(BAR, @base_dir, @gems_dir) assert_equal "bar", stub.name @@ -164,6 +178,53 @@ def test_to_spec_missing_extensions assert stub.to_spec.instance_variable_get :@ignored end + def stub_with_version + spec = File.join @gemhome, 'specifications', 'stub_e-2.gemspec' + open spec, 'w' do |io| + io.write <<-STUB +# -*- encoding: utf-8 -*- +# stub: stub_v 2 ruby lib + +Gem::Specification.new do |s| + s.name = 'stub_v' + s.version = Gem::Version.new '2' +end + STUB + + io.flush + + stub = Gem::StubSpecification.gemspec_stub io.path, @gemhome, File.join(@gemhome, 'gems') + + yield stub if block_given? + + return stub + end + end + + def stub_without_version + spec = File.join @gemhome, 'specifications', 'stub-2.gemspec' + open spec, 'w' do |io| + io.write <<-STUB +# -*- encoding: utf-8 -*- +# stub: stub_v ruby lib + +Gem::Specification.new do |s| + s.name = 'stub_v' + s.version = "" +end + STUB + + io.flush + + stub = Gem::StubSpecification.gemspec_stub io.path, @gemhome, File.join(@gemhome, 'gems') + + yield stub if block_given? + + return stub + end + + end + def stub_with_extension spec = File.join @gemhome, 'specifications', 'stub_e-2.gemspec' open spec, 'w' do |io| diff --git test/rubygems/test_gem_version.rb test/rubygems/test_gem_version.rb index 9898669ce6..1897d44905 100644 --- test/rubygems/test_gem_version.rb +++ test/rubygems/test_gem_version.rb @@ -91,6 +91,12 @@ def test_initialize_bad end end + def test_empty_version + ["", " ", " "].each do |empty| + assert_equal "0", Gem::Version.new(empty).version + end + end + def test_prerelease assert_prerelease "1.2.0.a" assert_prerelease "2.9.b" diff --git test/rubygems/test_require.rb test/rubygems/test_require.rb index 04cbc094d8..dd606e44d4 100644 --- test/rubygems/test_require.rb +++ test/rubygems/test_require.rb @@ -341,6 +341,31 @@ class << ::Gem Kernel::RUBYGEMS_ACTIVATION_MONITOR.exit end + def test_require_when_gem_defined + default_gem_spec = new_default_spec("default", "2.0.0.0", + nil, "default/gem.rb") + install_default_specs(default_gem_spec) + c = Class.new do + def self.gem(*args) + raise "received #gem with #{args.inspect}" + end + end + assert c.send(:require, "default/gem") + assert_equal %w(default-2.0.0.0), loaded_spec_names + end + + def test_require_default_when_gem_defined + a = new_spec("a", "1", nil, "lib/a.rb") + install_specs a + c = Class.new do + def self.gem(*args) + raise "received #gem with #{args.inspect}" + end + end + assert c.send(:require, "a") + assert_equal %w(a-1), loaded_spec_names + end + def silence_warnings old_verbose, $VERBOSE = $VERBOSE, false yield