ruby-changes:69509
From: Frederik <ko1@a...>
Date: Fri, 29 Oct 2021 18:19:10 +0900 (JST)
Subject: [ruby-changes:69509] 17fb785d15 (master): [rubygems/rubygems] Vendor tsort into rubygems
https://git.ruby-lang.org/ruby.git/commit/?id=17fb785d15 From 17fb785d1557d35fc9e28af59bdbef50ddbd08d9 Mon Sep 17 00:00:00 2001 From: Frederik Dudzik <frederik.dudzik@s...> Date: Thu, 21 Oct 2021 12:23:08 -0700 Subject: [rubygems/rubygems] Vendor tsort into rubygems So that it loads a consistent version of the library and `rubygems` is never affected by gem activation conflicts related to `tsort`. Getting CI green required updating one `bundler` spec, because `tsort` is no longer loaded by `bundle clean` until after `BUNDLE_PATH` has been changed, so to ensure it is found, it needs to be installed under `BUNDLE_PATH` as well (which will be different from the global system path on Bundler 3, meaning installing `tsort` to the global system path is not enough there). This spec workaround can be removed once we also vendor `tsort` inside `bundler`. https://github.com/rubygems/rubygems/commit/d326880999 --- lib/rubygems/dependency_list.rb | 4 +- lib/rubygems/request_set.rb | 4 +- .../molinillo/lib/molinillo/dependency_graph.rb | 4 +- lib/rubygems/tsort.rb | 3 + lib/rubygems/tsort/lib/tsort.rb | 454 +++++++++++++++++++++ 5 files changed, 463 insertions(+), 6 deletions(-) create mode 100644 lib/rubygems/tsort.rb create mode 100644 lib/rubygems/tsort/lib/tsort.rb diff --git a/lib/rubygems/dependency_list.rb b/lib/rubygems/dependency_list.rb index 8f61869874d..10e08fc703c 100644 --- a/lib/rubygems/dependency_list.rb +++ b/lib/rubygems/dependency_list.rb @@ -5,7 +5,7 @@ https://github.com/ruby/ruby/blob/trunk/lib/rubygems/dependency_list.rb#L5 # See LICENSE.txt for permissions. #++ -require 'tsort' +require_relative 'tsort' require_relative 'deprecate' ## @@ -20,7 +20,7 @@ class Gem::DependencyList https://github.com/ruby/ruby/blob/trunk/lib/rubygems/dependency_list.rb#L20 attr_reader :specs include Enumerable - include TSort + include Gem::TSort ## # Allows enabling/disabling use of development dependencies diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index 9286c542219..01b01599a8f 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -1,5 +1,5 @@ https://github.com/ruby/ruby/blob/trunk/lib/rubygems/request_set.rb#L1 # frozen_string_literal: true -require 'tsort' +require_relative 'tsort' ## # A RequestSet groups a request to activate a set of dependencies. @@ -15,7 +15,7 @@ require 'tsort' https://github.com/ruby/ruby/blob/trunk/lib/rubygems/request_set.rb#L15 # #=> ["nokogiri-1.6.0", "mini_portile-0.5.1", "pg-0.17.0"] class Gem::RequestSet - include TSort + include Gem::TSort ## # Array of gems to install even if already installed diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb index 16430a79f5b..95f8416b967 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb @@ -1,6 +1,6 @@ https://github.com/ruby/ruby/blob/trunk/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb#L1 # frozen_string_literal: true -require 'tsort' +require_relative '../../../../tsort' require_relative 'dependency_graph/log' require_relative 'dependency_graph/vertex' @@ -17,7 +17,7 @@ module Gem::Resolver::Molinillo https://github.com/ruby/ruby/blob/trunk/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb#L17 vertices.values.each { |v| yield v } end - include TSort + include Gem::TSort # @!visibility private alias tsort_each_node each diff --git a/lib/rubygems/tsort.rb b/lib/rubygems/tsort.rb new file mode 100644 index 00000000000..ebe7c3364b7 --- /dev/null +++ b/lib/rubygems/tsort.rb @@ -0,0 +1,3 @@ https://github.com/ruby/ruby/blob/trunk/lib/rubygems/tsort.rb#L1 +# frozen_string_literal: true + +require_relative 'tsort/lib/tsort' diff --git a/lib/rubygems/tsort/lib/tsort.rb b/lib/rubygems/tsort/lib/tsort.rb new file mode 100644 index 00000000000..f68c5947d30 --- /dev/null +++ b/lib/rubygems/tsort/lib/tsort.rb @@ -0,0 +1,454 @@ https://github.com/ruby/ruby/blob/trunk/lib/rubygems/tsort/lib/tsort.rb#L1 +# frozen_string_literal: true + +#-- +# tsort.rb - provides a module for topological sorting and strongly connected components. +#++ +# + +# +# Gem::TSort implements topological sorting using Tarjan's algorithm for +# strongly connected components. +# +# Gem::TSort is designed to be able to be used with any object which can be +# interpreted as a directed graph. +# +# Gem::TSort requires two methods to interpret an object as a graph, +# tsort_each_node and tsort_each_child. +# +# * tsort_each_node is used to iterate for all nodes over a graph. +# * tsort_each_child is used to iterate for child nodes of a given node. +# +# The equality of nodes are defined by eql? and hash since +# Gem::TSort uses Hash internally. +# +# == A Simple Example +# +# The following example demonstrates how to mix the Gem::TSort module into an +# existing class (in this case, Hash). Here, we're treating each key in +# the hash as a node in the graph, and so we simply alias the required +# #tsort_each_node method to Hash's #each_key method. For each key in the +# hash, the associated value is an array of the node's child nodes. This +# choice in turn leads to our implementation of the required #tsort_each_child +# method, which fetches the array of child nodes and then iterates over that +# array using the user-supplied block. +# +# require 'rubygems/tsort/lib/tsort' +# +# class Hash +# include Gem::TSort +# alias tsort_each_node each_key +# def tsort_each_child(node, &block) +# fetch(node).each(&block) +# end +# end +# +# {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort +# #=> [3, 2, 1, 4] +# +# {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components +# #=> [[4], [2, 3], [1]] +# +# == A More Realistic Example +# +# A very simple `make' like tool can be implemented as follows: +# +# require 'rubygems/tsort/lib/tsort' +# +# class Make +# def initialize +# @dep = {} +# @dep.default = [] +# end +# +# def rule(outputs, inputs=[], &block) +# triple = [outputs, inputs, block] +# outputs.each {|f| @dep[f] = [triple]} +# @dep[triple] = inputs +# end +# +# def build(target) +# each_strongly_connected_component_from(target) {|ns| +# if ns.length != 1 +# fs = ns.delete_if {|n| Array === n} +# raise Gem::TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") +# end +# n = ns.first +# if Array === n +# outputs, inputs, block = n +# inputs_time = inputs.map {|f| File.mtime f}.max +# begin +# outputs_time = outputs.map {|f| File.mtime f}.min +# rescue Errno::ENOENT +# outputs_time = nil +# end +# if outputs_time == nil || +# inputs_time != nil && outputs_time <= inputs_time +# sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i +# block.call +# end +# end +# } +# end +# +# def tsort_each_child(node, &block) +# @dep[node].each(&block) +# end +# include Gem::TSort +# end +# +# def command(arg) +# print arg, "\n" +# system arg +# end +# +# m = Make.new +# m.rule(%w[t1]) { command 'date > t1' } +# m.rule(%w[t2]) { command 'date > t2' } +# m.rule(%w[t3]) { command 'date > t3' } +# m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' } +# m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' } +# m.build('t5') +# +# == Bugs +# +# * 'tsort.rb' is wrong name because this library uses +# Tarjan's algorithm for strongly connected components. +# Although 'strongly_connected_components.rb' is correct but too long. +# +# == References +# +# R. E. Tarjan, "Depth First Search and Linear Graph Algorithms", +# <em>SIAM Journal on Computing</em>, Vol. 1, No. 2, pp. 146-160, June 1972. +# + +module Gem + module TSort + class Cyclic < StandardError + end + + # Returns a topologically sorted array of nodes. + # The array is sorted from children to parents, i.e. + # the first element has no child and the last node has no parent. + # + # If there is a cycle, Gem::TSort::Cyclic is raised. + # + # class G + # include Gem::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # p graph.tsort #=> [4, 2, 3, 1] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # p graph.tsort # raises Gem::TSort::Cyclic + # + def tsort + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + Gem::TSort.tsort(each_node, each_child) + end + + # Returns a topologically sorted array of nodes. + # The array is sorted from children to parents, i.e. + # the first element has no child and the last node has no parent. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # If there is a cycle, Gem::TSort::Cyclic is raised. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Gem::TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Gem::T (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/