Project

General

Profile

Bug #13322 ยป rubygems-2610-2611.patch

hsbt (Hiroshi SHIBATA), 03/17/2017 01:10 AM

View differences:

lib/rubygems.rb
10 10
require 'thread'
11 11

  
12 12
module Gem
13
  VERSION = "2.6.10"
13
  VERSION = "2.6.11"
14 14
end
15 15

  
16 16
# Must be first since it unloads the prelude from 1.9.2
lib/rubygems/request_set/lockfile/tokenizer.rb
1 1
# frozen_string_literal: true
2
require 'strscan'
3 2
require 'rubygems/request_set/lockfile/parser'
4 3

  
5 4
class Gem::RequestSet::Lockfile::Tokenizer
......
58 57
  private
59 58

  
60 59
  def tokenize input
60
    require 'strscan'
61 61
    s = StringScanner.new input
62 62

  
63 63
    until s.eos? do
lib/rubygems/resolver.rb
4 4
require 'rubygems/util'
5 5
require 'rubygems/util/list'
6 6

  
7
require 'uri'
8
require 'net/http'
9

  
10 7
##
11 8
# Given a set of Gem::Dependency objects as +needed+ and a way to query the
12 9
# set of available specs via +set+, calculates a set of ActivationRequest
......
256 253
    @soft_missing
257 254
  end
258 255

  
256
  def sort_dependencies(dependencies, activated, conflicts)
257
    dependencies.sort_by do |dependency|
258
      name = name_for(dependency)
259
      [
260
        activated.vertex_named(name).payload ? 0 : 1,
261
        amount_constrained(dependency),
262
        conflicts[name] ? 0 : 1,
263
        activated.vertex_named(name).payload ? 0 : search_for(dependency).count,
264
      ]
265
    end
266
  end
267

  
268
  SINGLE_POSSIBILITY_CONSTRAINT_PENALTY = 1_000_000
269
  private_constant :SINGLE_POSSIBILITY_CONSTRAINT_PENALTY if defined?(private_constant)
270

  
271
  # returns an integer \in (-\infty, 0]
272
  # a number closer to 0 means the dependency is less constraining
273
  #
274
  # dependencies w/ 0 or 1 possibilities (ignoring version requirements)
275
  # are given very negative values, so they _always_ sort first,
276
  # before dependencies that are unconstrained
277
  def amount_constrained(dependency)
278
    @amount_constrained ||= {}
279
    @amount_constrained[dependency.name] ||= begin
280
      name_dependency = Gem::Dependency.new(dependency.name)
281
      dependency_request_for_name = Gem::Resolver::DependencyRequest.new(name_dependency, dependency.requester)
282
      all = @set.find_all(dependency_request_for_name).size
283

  
284
      if all <= 1
285
        all - SINGLE_POSSIBILITY_CONSTRAINT_PENALTY
286
      else
287
        search = search_for(dependency).size
288
        search - all
289
      end
290
    end
291
  end
292
  private :amount_constrained
293

  
259 294
end
260 295

  
261 296
##
lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb
98 98
      "#{self.class}:#{vertices.values.inspect}"
99 99
    end
100 100

  
101
    # @param [Hash] options options for dot output.
101 102
    # @return [String] Returns a dot format representation of the graph
102
    def to_dot
103
    def to_dot(options = {})
104
      edge_label = options.delete(:edge_label)
105
      raise ArgumentError, "Unknown options: #{options.keys}" unless options.empty?
106

  
103 107
      dot_vertices = []
104 108
      dot_edges = []
105 109
      vertices.each do |n, v|
106 110
        dot_vertices << "  #{n} [label=\"{#{n}|#{v.payload}}\"]"
107 111
        v.outgoing_edges.each do |e|
108
          dot_edges << "  #{e.origin.name} -> #{e.destination.name} [label=\"#{e.requirement}\"]"
112
          label = edge_label ? edge_label.call(e) : e.requirement
113
          dot_edges << "  #{e.origin.name} -> #{e.destination.name} [label=#{label.to_s.dump}]"
109 114
        end
110 115
      end
116

  
117
      dot_vertices.uniq!
111 118
      dot_vertices.sort!
119
      dot_edges.uniq!
112 120
      dot_edges.sort!
121

  
113 122
      dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}')
114 123
      dot.join("\n")
115 124
    end
......
123 132
      vertices.each do |name, vertex|
124 133
        other_vertex = other.vertex_named(name)
125 134
        return false unless other_vertex
126
        return false unless other_vertex.successors.map(&:name).to_set == vertex.successors.map(&:name).to_set
135
        return false unless vertex.payload == other_vertex.payload
136
        return false unless other_vertex.successors.to_set == vertex.successors.to_set
127 137
      end
128 138
    end
129 139

  
lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb
23 23
      # (see Action#down)
24 24
      def down(graph)
25 25
        edge = make_edge(graph)
26
        edge.origin.outgoing_edges.delete(edge)
27
        edge.destination.incoming_edges.delete(edge)
26
        delete_first(edge.origin.outgoing_edges, edge)
27
        delete_first(edge.destination.incoming_edges, edge)
28 28
      end
29 29

  
30 30
      # @!group AddEdgeNoCircular
......
53 53
        @destination = destination
54 54
        @requirement = requirement
55 55
      end
56

  
57
      private
58

  
59
      def delete_first(array, item)
60
        return unless index = array.index(item)
61
        array.delete_at(index)
62
      end
56 63
    end
57 64
  end
58 65
end
lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb
10 10
      # @return [Object] the payload the vertex holds
11 11
      attr_accessor :payload
12 12

  
13
      # @return [Arrary<Object>] the explicit requirements that required
13
      # @return [Array<Object>] the explicit requirements that required
14 14
      #   this vertex
15 15
      attr_reader :explicit_requirements
16 16

  
lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb
1 1
# frozen_string_literal: true
2 2
module Gem::Resolver::Molinillo
3 3
  # The version of Gem::Resolver::Molinillo.
4
  VERSION = '0.5.5'.freeze
4
  VERSION = '0.5.7'.freeze
5 5
end
lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb
48 48
      if debug?
49 49
        debug_info = yield
50 50
        debug_info = debug_info.inspect unless debug_info.is_a?(String)
51
        output.puts debug_info.split("\n").map { |s| '  ' * depth + s }
51
        output.puts debug_info.split("\n").map { |s| ' ' * depth + s }
52 52
      end
53 53
    end
54 54

  
lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb
52 52
        @base = base
53 53
        @states = []
54 54
        @iteration_counter = 0
55
        @parent_of = {}
55
        @parents_of = Hash.new { |h, k| h[k] = [] }
56 56
      end
57 57

  
58 58
      # Resolves the {#original_requested} dependencies into a full dependency
......
105 105

  
106 106
        handle_missing_or_push_dependency_state(initial_state)
107 107

  
108
        debug { "Starting resolution (#{@started_at})" }
108
        debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" }
109 109
        resolver_ui.before_resolution
110 110
      end
111 111

  
......
178 178
      # Unwinds the states stack because a conflict has been encountered
179 179
      # @return [void]
180 180
      def unwind_for_conflict
181
        debug(depth) { "Unwinding for conflict: #{requirement}" }
181
        debug(depth) { "Unwinding for conflict: #{requirement} to #{state_index_for_unwind / 2}" }
182 182
        conflicts.tap do |c|
183 183
          sliced_states = states.slice!((state_index_for_unwind + 1)..-1)
184 184
          raise VersionConflict.new(c) unless state
185 185
          activated.rewind_to(sliced_states.first || :initial_state) if sliced_states
186 186
          state.conflicts = c
187 187
          index = states.size - 1
188
          @parent_of.reject! { |_, i| i >= index }
188
          @parents_of.each { |_, a| a.reject! { |i| i >= index } }
189 189
        end
190 190
      end
191 191

  
......
214 214
      #   to the list of requirements.
215 215
      def parent_of(requirement)
216 216
        return unless requirement
217
        return unless index = @parent_of[requirement]
217
        return unless index = @parents_of[requirement].last
218 218
        return unless parent_state = @states[index]
219 219
        parent_state.requirement
220 220
      end
......
356 356
      # Ensures there are no orphaned successors to the given {vertex}.
357 357
      # @param [DependencyGraph::Vertex] vertex the vertex to fix up.
358 358
      # @return [void]
359
      def fixup_swapped_children(vertex)
359
      def fixup_swapped_children(vertex) # rubocop:disable Metrics/CyclomaticComplexity
360 360
        payload = vertex.payload
361 361
        deps = dependencies_for(payload).group_by(&method(:name_for))
362 362
        vertex.outgoing_edges.each do |outgoing_edge|
363
          @parent_of[outgoing_edge.requirement] = states.size - 1
363
          requirement = outgoing_edge.requirement
364
          parent_index = @parents_of[requirement].last
364 365
          succ = outgoing_edge.destination
365 366
          matching_deps = Array(deps[succ.name])
367
          dep_matched = matching_deps.include?(requirement)
368

  
369
          # only push the current index when it was originally required by the
370
          # same named spec
371
          if parent_index && states[parent_index].name == name
372
            @parents_of[requirement].push(states.size - 1)
373
          end
374

  
366 375
          if matching_deps.empty? && !succ.root? && succ.predecessors.to_a == [vertex]
367 376
            debug(depth) { "Removing orphaned spec #{succ.name} after swapping #{name}" }
368
            succ.requirements.each { |r| @parent_of.delete(r) }
377
            succ.requirements.each { |r| @parents_of.delete(r) }
369 378

  
370 379
            removed_names = activated.detach_vertex_named(succ.name).map(&:name)
371 380
            requirements.delete_if do |r|
......
373 382
              # so it's safe to delete only based upon name here
374 383
              removed_names.include?(name_for(r))
375 384
            end
376
          elsif !matching_deps.include?(outgoing_edge.requirement)
385
          elsif !dep_matched
386
            debug(depth) { "Removing orphaned dependency #{requirement} after swapping #{name}" }
387
            # also reset if we're removing the edge, but only if its parent has
388
            # already been fixed up
389
            @parents_of[requirement].push(states.size - 1) if @parents_of[requirement].empty?
390

  
377 391
            activated.delete_edge(outgoing_edge)
378
            requirements.delete(outgoing_edge.requirement)
392
            requirements.delete(requirement)
379 393
          end
380 394
        end
381 395
      end
......
395 409
      # @return [Boolean] whether the current spec is satisfied as a new
396 410
      # possibility.
397 411
      def new_spec_satisfied?
412
        unless requirement_satisfied_by?(requirement, activated, possibility)
413
          debug(depth) { 'Unsatisfied by requested spec' }
414
          return false
415
        end
416

  
398 417
        locked_requirement = locked_requirement_named(name)
399
        requested_spec_satisfied = requirement_satisfied_by?(requirement, activated, possibility)
418

  
400 419
        locked_spec_satisfied = !locked_requirement ||
401 420
          requirement_satisfied_by?(locked_requirement, activated, possibility)
402
        debug(depth) { 'Unsatisfied by requested spec' } unless requested_spec_satisfied
403 421
        debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied
404
        requested_spec_satisfied && locked_spec_satisfied
422

  
423
        locked_spec_satisfied
405 424
      end
406 425

  
407 426
      # @param [String] requirement_name the spec name to search for
......
417 436
      # @return [void]
418 437
      def activate_spec
419 438
        conflicts.delete(name)
420
        debug(depth) { 'Activated ' + name + ' at ' + possibility.to_s }
439
        debug(depth) { "Activated #{name} at #{possibility}" }
421 440
        activated.set_payload(name, possibility)
422 441
        require_nested_dependencies_for(possibility)
423 442
      end
......
432 451
        nested_dependencies.each do |d|
433 452
          activated.add_child_vertex(name_for(d), nil, [name_for(activated_spec)], d)
434 453
          parent_index = states.size - 1
435
          @parent_of[d] ||= parent_index
454
          parents = @parents_of[d]
455
          parents << parent_index if parents.empty?
436 456
        end
437 457

  
438 458
        push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?)
lib/rubygems/resolver/set.rb
21 21
  attr_accessor :prerelease
22 22

  
23 23
  def initialize # :nodoc:
24
    require 'uri'
24 25
    @prerelease = false
25 26
    @remote     = true
26 27
    @errors     = []
......
54 55
  end
55 56

  
56 57
end
57

  
lib/rubygems/test_case.rb
1334 1334
  end
1335 1335

  
1336 1336
  ##
1337
  # create_gemspec creates gem specification in given +direcotry+ or '.'
1337
  # create_gemspec creates gem specification in given +directory+ or '.'
1338 1338
  # for the given +name+ and +version+.
1339 1339
  #
1340 1340
  # Yields the +specification+ to the block, if given
test/rubygems/test_gem.rb
1434 1434

  
1435 1435
    install_specs a, b, c
1436 1436

  
1437
    path = File.join @tempdir, "gem.deps.rb"
1438

  
1439
    File.open path, "w" do |f|
1440
      f.puts "gem 'a'"
1441
      f.puts "gem 'b'"
1442
      f.puts "gem 'c'"
1443
    end
1444

  
1445 1437
    path = File.join(@tempdir, "gd-tmp")
1446 1438
    install_gem a, :install_dir => path
1447 1439
    install_gem b, :install_dir => path
......
1450 1442
    ENV['GEM_PATH'] = path
1451 1443
    ENV['RUBYGEMS_GEMDEPS'] = "-"
1452 1444

  
1453
    out = `#{Gem.ruby.dup.untaint} -I "#{LIB_PATH.untaint}" -rubygems -e "p Gem.loaded_specs.values.map(&:full_name).sort"`
1454
    out.sub!(/, "openssl-#{Gem::Version::VERSION_PATTERN}"/, "")
1445
    path = File.join @tempdir, "gem.deps.rb"
1446
    cmd = [Gem.ruby.dup.untaint, "-I#{LIB_PATH.untaint}", "-rubygems"]
1447
    if RUBY_VERSION < '1.9'
1448
      cmd << "-e 'puts Gem.loaded_specs.values.map(&:full_name).sort'"
1449
      cmd = cmd.join(' ')
1450
    else
1451
      cmd << "-eputs Gem.loaded_specs.values.map(&:full_name).sort"
1452
    end
1455 1453

  
1456
    assert_equal '["a-1", "b-1", "c-1"]', out.strip
1454
    File.open path, "w" do |f|
1455
      f.puts "gem 'a'"
1456
    end
1457
    out0 = IO.popen(cmd, &:read).split(/\n/)
1458

  
1459
    File.open path, "a" do |f|
1460
      f.puts "gem 'b'"
1461
      f.puts "gem 'c'"
1462
    end
1463
    out = IO.popen(cmd, &:read).split(/\n/)
1464

  
1465
    assert_equal ["b-1", "c-1"], out - out0
1457 1466
  end
1458 1467

  
1459 1468
  def test_looks_for_gemdeps_files_automatically_on_start_in_parent_dir
......
1465 1474

  
1466 1475
    install_specs a, b, c
1467 1476

  
1468
    path = File.join @tempdir, "gem.deps.rb"
1469

  
1470
    File.open path, "w" do |f|
1471
      f.puts "gem 'a'"
1472
      f.puts "gem 'b'"
1473
      f.puts "gem 'c'"
1474
    end
1475

  
1476 1477
    path = File.join(@tempdir, "gd-tmp")
1477 1478
    install_gem a, :install_dir => path
1478 1479
    install_gem b, :install_dir => path
......
1482 1483
    ENV['RUBYGEMS_GEMDEPS'] = "-"
1483 1484

  
1484 1485
    Dir.mkdir "sub1"
1485
    out = Dir.chdir "sub1" do
1486
      `#{Gem.ruby.dup.untaint} -I "#{LIB_PATH.untaint}" -rubygems -e "p Gem.loaded_specs.values.map(&:full_name).sort"`
1486

  
1487
    path = File.join @tempdir, "gem.deps.rb"
1488
    cmd = [Gem.ruby.dup.untaint, "-Csub1", "-I#{LIB_PATH.untaint}", "-rubygems"]
1489
    if RUBY_VERSION < '1.9'
1490
      cmd << "-e 'puts Gem.loaded_specs.values.map(&:full_name).sort'"
1491
      cmd = cmd.join(' ')
1492
    else
1493
      cmd << "-eputs Gem.loaded_specs.values.map(&:full_name).sort"
1487 1494
    end
1488
    out.sub!(/, "openssl-#{Gem::Version::VERSION_PATTERN}"/, "")
1495

  
1496
    File.open path, "w" do |f|
1497
      f.puts "gem 'a'"
1498
    end
1499
    out0 = IO.popen(cmd, &:read).split(/\n/)
1500

  
1501
    File.open path, "a" do |f|
1502
      f.puts "gem 'b'"
1503
      f.puts "gem 'c'"
1504
    end
1505
    out = IO.popen(cmd, &:read).split(/\n/)
1489 1506

  
1490 1507
    Dir.rmdir "sub1"
1491 1508

  
1492
    assert_equal '["a-1", "b-1", "c-1"]', out.strip
1509
    assert_equal ["b-1", "c-1"], out - out0
1493 1510
  end
1494 1511

  
1495 1512
  def test_register_default_spec
test/rubygems/test_gem_resolver.rb
521 521
    assert_equal req('>= 0'), dependency.requirement
522 522

  
523 523
    activated = e.conflict.activated
524
    assert_equal 'c-2', activated.full_name
524
    assert_equal 'c-1', activated.full_name
525 525

  
526
    assert_equal dep('c', '>= 2'), activated.request.dependency
526
    assert_equal dep('c', '= 1'), activated.request.dependency
527 527

  
528
    assert_equal [dep('c', '= 1'), dep('c', '>= 2')],
528
    assert_equal [dep('c', '>= 2'), dep('c', '= 1')],
529 529
                 e.conflict.conflicting_dependencies
530 530
  end
531 531