summaryrefslogtreecommitdiff
path: root/lib/bundler/vendor
diff options
context:
space:
mode:
authorhsbt <hsbt@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2017-11-01 23:29:38 +0000
committerhsbt <hsbt@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2017-11-01 23:29:38 +0000
commitbe7b5929126cb3e696ef222339237faba9b8fe5a (patch)
tree51eae376f93c09bc82dde5a657a91df2c89062e4 /lib/bundler/vendor
parentae49dbd392083f69026f2a0fff4a1d5f42d172a7 (diff)
Update bundled bundler to 1.16.0.
* lib/bundler, spec/bundler: Merge bundler-1.16.0. * common.mk: rspec examples of bundler-1.16.0 needs require option. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@60603 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/bundler/vendor')
-rw-r--r--lib/bundler/vendor/fileutils/lib/fileutils.rb1638
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo.rb2
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb26
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb7
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb5
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb7
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/errors.rb75
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb3
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb4
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb639
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/state.rb12
-rw-r--r--lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb2
-rw-r--r--lib/bundler/vendor/thor/lib/thor/runner.rb4
24 files changed, 2267 insertions, 168 deletions
diff --git a/lib/bundler/vendor/fileutils/lib/fileutils.rb b/lib/bundler/vendor/fileutils/lib/fileutils.rb
new file mode 100644
index 0000000000..cc69740845
--- /dev/null
+++ b/lib/bundler/vendor/fileutils/lib/fileutils.rb
@@ -0,0 +1,1638 @@
+# frozen_string_literal: true
+#
+# = fileutils.rb
+#
+# Copyright (c) 2000-2007 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the same terms of ruby.
+#
+# == module Bundler::FileUtils
+#
+# Namespace for several file utility methods for copying, moving, removing, etc.
+#
+# === Module Functions
+#
+# require 'bundler/vendor/fileutils/lib/fileutils'
+#
+# Bundler::FileUtils.cd(dir, options)
+# Bundler::FileUtils.cd(dir, options) {|dir| block }
+# Bundler::FileUtils.pwd()
+# Bundler::FileUtils.mkdir(dir, options)
+# Bundler::FileUtils.mkdir(list, options)
+# Bundler::FileUtils.mkdir_p(dir, options)
+# Bundler::FileUtils.mkdir_p(list, options)
+# Bundler::FileUtils.rmdir(dir, options)
+# Bundler::FileUtils.rmdir(list, options)
+# Bundler::FileUtils.ln(target, link, options)
+# Bundler::FileUtils.ln(targets, dir, options)
+# Bundler::FileUtils.ln_s(target, link, options)
+# Bundler::FileUtils.ln_s(targets, dir, options)
+# Bundler::FileUtils.ln_sf(target, link, options)
+# Bundler::FileUtils.cp(src, dest, options)
+# Bundler::FileUtils.cp(list, dir, options)
+# Bundler::FileUtils.cp_r(src, dest, options)
+# Bundler::FileUtils.cp_r(list, dir, options)
+# Bundler::FileUtils.mv(src, dest, options)
+# Bundler::FileUtils.mv(list, dir, options)
+# Bundler::FileUtils.rm(list, options)
+# Bundler::FileUtils.rm_r(list, options)
+# Bundler::FileUtils.rm_rf(list, options)
+# Bundler::FileUtils.install(src, dest, options)
+# Bundler::FileUtils.chmod(mode, list, options)
+# Bundler::FileUtils.chmod_R(mode, list, options)
+# Bundler::FileUtils.chown(user, group, list, options)
+# Bundler::FileUtils.chown_R(user, group, list, options)
+# Bundler::FileUtils.touch(list, options)
+#
+# The <tt>options</tt> parameter is a hash of options, taken from the list
+# <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and <tt>:verbose</tt>.
+# <tt>:noop</tt> means that no changes are made. The other three are obvious.
+# Each method documents the options that it honours.
+#
+# All methods that have the concept of a "source" file or directory can take
+# either one file or a list of files in that argument. See the method
+# documentation for examples.
+#
+# There are some `low level' methods, which do not accept any option:
+#
+# Bundler::FileUtils.copy_entry(src, dest, preserve = false, dereference = false)
+# Bundler::FileUtils.copy_file(src, dest, preserve = false, dereference = true)
+# Bundler::FileUtils.copy_stream(srcstream, deststream)
+# Bundler::FileUtils.remove_entry(path, force = false)
+# Bundler::FileUtils.remove_entry_secure(path, force = false)
+# Bundler::FileUtils.remove_file(path, force = false)
+# Bundler::FileUtils.compare_file(path_a, path_b)
+# Bundler::FileUtils.compare_stream(stream_a, stream_b)
+# Bundler::FileUtils.uptodate?(file, cmp_list)
+#
+# == module Bundler::FileUtils::Verbose
+#
+# This module has all methods of Bundler::FileUtils module, but it outputs messages
+# before acting. This equates to passing the <tt>:verbose</tt> flag to methods
+# in Bundler::FileUtils.
+#
+# == module Bundler::FileUtils::NoWrite
+#
+# This module has all methods of Bundler::FileUtils module, but never changes
+# files/directories. This equates to passing the <tt>:noop</tt> flag to methods
+# in Bundler::FileUtils.
+#
+# == module Bundler::FileUtils::DryRun
+#
+# This module has all methods of Bundler::FileUtils module, but never changes
+# files/directories. This equates to passing the <tt>:noop</tt> and
+# <tt>:verbose</tt> flags to methods in Bundler::FileUtils.
+#
+
+module Bundler::FileUtils
+
+ def self.private_module_function(name) #:nodoc:
+ module_function name
+ private_class_method name
+ end
+
+ #
+ # Returns the name of the current directory.
+ #
+ def pwd
+ Dir.pwd
+ end
+ module_function :pwd
+
+ alias getwd pwd
+ module_function :getwd
+
+ #
+ # Changes the current directory to the directory +dir+.
+ #
+ # If this method is called with block, resumes to the old
+ # working directory after the block execution finished.
+ #
+ # Bundler::FileUtils.cd('/', :verbose => true) # chdir and report it
+ #
+ # Bundler::FileUtils.cd('/') do # chdir
+ # # ... # do something
+ # end # return to original directory
+ #
+ def cd(dir, verbose: nil, &block) # :yield: dir
+ fu_output_message "cd #{dir}" if verbose
+ Dir.chdir(dir, &block)
+ fu_output_message 'cd -' if verbose and block
+ end
+ module_function :cd
+
+ alias chdir cd
+ module_function :chdir
+
+ #
+ # Returns true if +new+ is newer than all +old_list+.
+ # Non-existent files are older than any file.
+ #
+ # Bundler::FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
+ # system 'make hello.o'
+ #
+ def uptodate?(new, old_list)
+ return false unless File.exist?(new)
+ new_time = File.mtime(new)
+ old_list.each do |old|
+ if File.exist?(old)
+ return false unless new_time > File.mtime(old)
+ end
+ end
+ true
+ end
+ module_function :uptodate?
+
+ def remove_trailing_slash(dir) #:nodoc:
+ dir == '/' ? dir : dir.chomp(?/)
+ end
+ private_module_function :remove_trailing_slash
+
+ #
+ # Creates one or more directories.
+ #
+ # Bundler::FileUtils.mkdir 'test'
+ # Bundler::FileUtils.mkdir %w( tmp data )
+ # Bundler::FileUtils.mkdir 'notexist', :noop => true # Does not really create.
+ # Bundler::FileUtils.mkdir 'tmp', :mode => 0700
+ #
+ def mkdir(list, mode: nil, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message "mkdir #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
+ return if noop
+
+ list.each do |dir|
+ fu_mkdir dir, mode
+ end
+ end
+ module_function :mkdir
+
+ #
+ # Creates a directory and all its parent directories.
+ # For example,
+ #
+ # Bundler::FileUtils.mkdir_p '/usr/local/lib/ruby'
+ #
+ # causes to make following directories, if it does not exist.
+ #
+ # * /usr
+ # * /usr/local
+ # * /usr/local/lib
+ # * /usr/local/lib/ruby
+ #
+ # You can pass several directories at a time in a list.
+ #
+ def mkdir_p(list, mode: nil, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
+ return *list if noop
+
+ list.map {|path| remove_trailing_slash(path)}.each do |path|
+ # optimize for the most common case
+ begin
+ fu_mkdir path, mode
+ next
+ rescue SystemCallError
+ next if File.directory?(path)
+ end
+
+ stack = []
+ until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/"
+ stack.push path
+ path = File.dirname(path)
+ end
+ stack.pop # root directory should exist
+ stack.reverse_each do |dir|
+ begin
+ fu_mkdir dir, mode
+ rescue SystemCallError
+ raise unless File.directory?(dir)
+ end
+ end
+ end
+
+ return *list
+ end
+ module_function :mkdir_p
+
+ alias mkpath mkdir_p
+ alias makedirs mkdir_p
+ module_function :mkpath
+ module_function :makedirs
+
+ def fu_mkdir(path, mode) #:nodoc:
+ path = remove_trailing_slash(path)
+ if mode
+ Dir.mkdir path, mode
+ File.chmod mode, path
+ else
+ Dir.mkdir path
+ end
+ end
+ private_module_function :fu_mkdir
+
+ #
+ # Removes one or more directories.
+ #
+ # Bundler::FileUtils.rmdir 'somedir'
+ # Bundler::FileUtils.rmdir %w(somedir anydir otherdir)
+ # # Does not really remove directory; outputs message.
+ # Bundler::FileUtils.rmdir 'somedir', :verbose => true, :noop => true
+ #
+ def rmdir(list, parents: nil, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if verbose
+ return if noop
+ list.each do |dir|
+ begin
+ Dir.rmdir(dir = remove_trailing_slash(dir))
+ if parents
+ until (parent = File.dirname(dir)) == '.' or parent == dir
+ dir = parent
+ Dir.rmdir(dir)
+ end
+ end
+ rescue Errno::ENOTEMPTY, Errno::EEXIST, Errno::ENOENT
+ end
+ end
+ end
+ module_function :rmdir
+
+ #
+ # :call-seq:
+ # Bundler::FileUtils.ln(target, link, force: nil, noop: nil, verbose: nil)
+ # Bundler::FileUtils.ln(target, dir, force: nil, noop: nil, verbose: nil)
+ # Bundler::FileUtils.ln(targets, dir, force: nil, noop: nil, verbose: nil)
+ #
+ # In the first form, creates a hard link +link+ which points to +target+.
+ # If +link+ already exists, raises Errno::EEXIST.
+ # But if the :force option is set, overwrites +link+.
+ #
+ # Bundler::FileUtils.ln 'gcc', 'cc', verbose: true
+ # Bundler::FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
+ #
+ # In the second form, creates a link +dir/target+ pointing to +target+.
+ # In the third form, creates several hard links in the directory +dir+,
+ # pointing to each item in +targets+.
+ # If +dir+ is not a directory, raises Errno::ENOTDIR.
+ #
+ # Bundler::FileUtils.cd '/sbin'
+ # Bundler::FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked.
+ #
+ def ln(src, dest, force: nil, noop: nil, verbose: nil)
+ fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest0(src, dest) do |s,d|
+ remove_file d, true if force
+ File.link s, d
+ end
+ end
+ module_function :ln
+
+ alias link ln
+ module_function :link
+
+ #
+ # :call-seq:
+ # Bundler::FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil)
+ # Bundler::FileUtils.ln_s(target, dir, force: nil, noop: nil, verbose: nil)
+ # Bundler::FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil)
+ #
+ # In the first form, creates a symbolic link +link+ which points to +target+.
+ # If +link+ already exists, raises Errno::EEXIST.
+ # But if the :force option is set, overwrites +link+.
+ #
+ # Bundler::FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
+ # Bundler::FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true
+ #
+ # In the second form, creates a link +dir/target+ pointing to +target+.
+ # In the third form, creates several symbolic links in the directory +dir+,
+ # pointing to each item in +targets+.
+ # If +dir+ is not a directory, raises Errno::ENOTDIR.
+ #
+ # Bundler::FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin'
+ #
+ def ln_s(src, dest, force: nil, noop: nil, verbose: nil)
+ fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest0(src, dest) do |s,d|
+ remove_file d, true if force
+ File.symlink s, d
+ end
+ end
+ module_function :ln_s
+
+ alias symlink ln_s
+ module_function :symlink
+
+ #
+ # :call-seq:
+ # Bundler::FileUtils.ln_sf(*args)
+ #
+ # Same as
+ #
+ # Bundler::FileUtils.ln_s(*args, force: true)
+ #
+ def ln_sf(src, dest, noop: nil, verbose: nil)
+ ln_s src, dest, force: true, noop: noop, verbose: verbose
+ end
+ module_function :ln_sf
+
+ #
+ # Copies a file content +src+ to +dest+. If +dest+ is a directory,
+ # copies +src+ to +dest/src+.
+ #
+ # If +src+ is a list of files, then +dest+ must be a directory.
+ #
+ # Bundler::FileUtils.cp 'eval.c', 'eval.c.org'
+ # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
+ # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true
+ # Bundler::FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink
+ #
+ def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
+ fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest(src, dest) do |s, d|
+ copy_file s, d, preserve
+ end
+ end
+ module_function :cp
+
+ alias copy cp
+ module_function :copy
+
+ #
+ # Copies +src+ to +dest+. If +src+ is a directory, this method copies
+ # all its contents recursively. If +dest+ is a directory, copies
+ # +src+ to +dest/src+.
+ #
+ # +src+ can be a list of files.
+ #
+ # # Installing Ruby library "mylib" under the site_ruby
+ # Bundler::FileUtils.rm_r site_ruby + '/mylib', :force
+ # Bundler::FileUtils.cp_r 'lib/', site_ruby + '/mylib'
+ #
+ # # Examples of copying several files to target directory.
+ # Bundler::FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
+ # Bundler::FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', :noop => true, :verbose => true
+ #
+ # # If you want to copy all contents of a directory instead of the
+ # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
+ # # use following code.
+ # Bundler::FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src,
+ # # but this doesn't.
+ #
+ def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,
+ dereference_root: true, remove_destination: nil)
+ fu_output_message "cp -r#{preserve ? 'p' : ''}#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest(src, dest) do |s, d|
+ copy_entry s, d, preserve, dereference_root, remove_destination
+ end
+ end
+ module_function :cp_r
+
+ #
+ # Copies a file system entry +src+ to +dest+.
+ # If +src+ is a directory, this method copies its contents recursively.
+ # This method preserves file types, c.f. symlink, directory...
+ # (FIFO, device files and etc. are not supported yet)
+ #
+ # Both of +src+ and +dest+ must be a path name.
+ # +src+ must exist, +dest+ must not exist.
+ #
+ # If +preserve+ is true, this method preserves owner, group, and
+ # modified time. Permissions are copied regardless +preserve+.
+ #
+ # If +dereference_root+ is true, this method dereference tree root.
+ #
+ # If +remove_destination+ is true, this method removes each destination file before copy.
+ #
+ def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
+ Entry_.new(src, nil, dereference_root).wrap_traverse(proc do |ent|
+ destent = Entry_.new(dest, ent.rel, false)
+ File.unlink destent.path if remove_destination && File.file?(destent.path)
+ ent.copy destent.path
+ end, proc do |ent|
+ destent = Entry_.new(dest, ent.rel, false)
+ ent.copy_metadata destent.path if preserve
+ end)
+ end
+ module_function :copy_entry
+
+ #
+ # Copies file contents of +src+ to +dest+.
+ # Both of +src+ and +dest+ must be a path name.
+ #
+ def copy_file(src, dest, preserve = false, dereference = true)
+ ent = Entry_.new(src, nil, dereference)
+ ent.copy_file dest
+ ent.copy_metadata dest if preserve
+ end
+ module_function :copy_file
+
+ #
+ # Copies stream +src+ to +dest+.
+ # +src+ must respond to #read(n) and
+ # +dest+ must respond to #write(str).
+ #
+ def copy_stream(src, dest)
+ IO.copy_stream(src, dest)
+ end
+ module_function :copy_stream
+
+ #
+ # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different
+ # disk partition, the file is copied then the original file is removed.
+ #
+ # Bundler::FileUtils.mv 'badname.rb', 'goodname.rb'
+ # Bundler::FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error
+ #
+ # Bundler::FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/'
+ # Bundler::FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
+ #
+ def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)
+ fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest(src, dest) do |s, d|
+ destent = Entry_.new(d, nil, true)
+ begin
+ if destent.exist?
+ if destent.directory?
+ raise Errno::EEXIST, d
+ else
+ destent.remove_file if rename_cannot_overwrite_file?
+ end
+ end
+ begin
+ File.rename s, d
+ rescue Errno::EXDEV
+ copy_entry s, d, true
+ if secure
+ remove_entry_secure s, force
+ else
+ remove_entry s, force
+ end
+ end
+ rescue SystemCallError
+ raise unless force
+ end
+ end
+ end
+ module_function :mv
+
+ alias move mv
+ module_function :move
+
+ def rename_cannot_overwrite_file? #:nodoc:
+ /emx/ =~ RUBY_PLATFORM
+ end
+ private_module_function :rename_cannot_overwrite_file?
+
+ #
+ # Remove file(s) specified in +list+. This method cannot remove directories.
+ # All StandardErrors are ignored when the :force option is set.
+ #
+ # Bundler::FileUtils.rm %w( junk.txt dust.txt )
+ # Bundler::FileUtils.rm Dir.glob('*.so')
+ # Bundler::FileUtils.rm 'NotExistFile', :force => true # never raises exception
+ #
+ def rm(list, force: nil, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message "rm#{force ? ' -f' : ''} #{list.join ' '}" if verbose
+ return if noop
+
+ list.each do |path|
+ remove_file path, force
+ end
+ end
+ module_function :rm
+
+ alias remove rm
+ module_function :remove
+
+ #
+ # Equivalent to
+ #
+ # Bundler::FileUtils.rm(list, :force => true)
+ #
+ def rm_f(list, noop: nil, verbose: nil)
+ rm list, force: true, noop: noop, verbose: verbose
+ end
+ module_function :rm_f
+
+ alias safe_unlink rm_f
+ module_function :safe_unlink
+
+ #
+ # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
+ # removes its all contents recursively. This method ignores
+ # StandardError when :force option is set.
+ #
+ # Bundler::FileUtils.rm_r Dir.glob('/tmp/*')
+ # Bundler::FileUtils.rm_r 'some_dir', :force => true
+ #
+ # WARNING: This method causes local vulnerability
+ # if one of parent directories or removing directory tree are world
+ # writable (including /tmp, whose permission is 1777), and the current
+ # process has strong privilege such as Unix super user (root), and the
+ # system has symbolic link. For secure removing, read the documentation
+ # of #remove_entry_secure carefully, and set :secure option to true.
+ # Default is :secure=>false.
+ #
+ # NOTE: This method calls #remove_entry_secure if :secure option is set.
+ # See also #remove_entry_secure.
+ #
+ def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)
+ list = fu_list(list)
+ fu_output_message "rm -r#{force ? 'f' : ''} #{list.join ' '}" if verbose
+ return if noop
+ list.each do |path|
+ if secure
+ remove_entry_secure path, force
+ else
+ remove_entry path, force
+ end
+ end
+ end
+ module_function :rm_r
+
+ #
+ # Equivalent to
+ #
+ # Bundler::FileUtils.rm_r(list, :force => true)
+ #
+ # WARNING: This method causes local vulnerability.
+ # Read the documentation of #rm_r first.
+ #
+ def rm_rf(list, noop: nil, verbose: nil, secure: nil)
+ rm_r list, force: true, noop: noop, verbose: verbose, secure: secure
+ end
+ module_function :rm_rf
+
+ alias rmtree rm_rf
+ module_function :rmtree
+
+ #
+ # This method removes a file system entry +path+. +path+ shall be a
+ # regular file, a directory, or something. If +path+ is a directory,
+ # remove it recursively. This method is required to avoid TOCTTOU
+ # (time-of-check-to-time-of-use) local security vulnerability of #rm_r.
+ # #rm_r causes security hole when:
+ #
+ # * Parent directory is world writable (including /tmp).
+ # * Removing directory tree includes world writable directory.
+ # * The system has symbolic link.
+ #
+ # To avoid this security hole, this method applies special preprocess.
+ # If +path+ is a directory, this method chown(2) and chmod(2) all
+ # removing directories. This requires the current process is the
+ # owner of the removing whole directory tree, or is the super user (root).
+ #
+ # WARNING: You must ensure that *ALL* parent directories cannot be
+ # moved by other untrusted users. For example, parent directories
+ # should not be owned by untrusted users, and should not be world
+ # writable except when the sticky bit set.
+ #
+ # WARNING: Only the owner of the removing directory tree, or Unix super
+ # user (root) should invoke this method. Otherwise this method does not
+ # work.
+ #
+ # For details of this security vulnerability, see Perl's case:
+ #
+ # * http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
+ # * http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
+ #
+ # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
+ #
+ def remove_entry_secure(path, force = false)
+ unless fu_have_symlink?
+ remove_entry path, force
+ return
+ end
+ fullpath = File.expand_path(path)
+ st = File.lstat(fullpath)
+ unless st.directory?
+ File.unlink fullpath
+ return
+ end
+ # is a directory.
+ parent_st = File.stat(File.dirname(fullpath))
+ unless parent_st.world_writable?
+ remove_entry path, force
+ return
+ end
+ unless parent_st.sticky?
+ raise ArgumentError, "parent directory is world writable, Bundler::FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
+ end
+ # freeze tree root
+ euid = Process.euid
+ File.open(fullpath + '/.') {|f|
+ unless fu_stat_identical_entry?(st, f.stat)
+ # symlink (TOC-to-TOU attack?)
+ File.unlink fullpath
+ return
+ end
+ f.chown euid, -1
+ f.chmod 0700
+ unless fu_stat_identical_entry?(st, File.lstat(fullpath))
+ # TOC-to-TOU attack?
+ File.unlink fullpath
+ return
+ end
+ }
+ # ---- tree root is frozen ----
+ root = Entry_.new(path)
+ root.preorder_traverse do |ent|
+ if ent.directory?
+ ent.chown euid, -1
+ ent.chmod 0700
+ end
+ end
+ root.postorder_traverse do |ent|
+ begin
+ ent.remove
+ rescue
+ raise unless force
+ end
+ end
+ rescue
+ raise unless force
+ end
+ module_function :remove_entry_secure
+
+ def fu_have_symlink? #:nodoc:
+ File.symlink nil, nil
+ rescue NotImplementedError
+ return false
+ rescue TypeError
+ return true
+ end
+ private_module_function :fu_have_symlink?
+
+ def fu_stat_identical_entry?(a, b) #:nodoc:
+ a.dev == b.dev and a.ino == b.ino
+ end
+ private_module_function :fu_stat_identical_entry?
+
+ #
+ # This method removes a file system entry +path+.
+ # +path+ might be a regular file, a directory, or something.
+ # If +path+ is a directory, remove it recursively.
+ #
+ # See also #remove_entry_secure.
+ #
+ def remove_entry(path, force = false)
+ Entry_.new(path).postorder_traverse do |ent|
+ begin
+ ent.remove
+ rescue
+ raise unless force
+ end
+ end
+ rescue
+ raise unless force
+ end
+ module_function :remove_entry
+
+ #
+ # Removes a file +path+.
+ # This method ignores StandardError if +force+ is true.
+ #
+ def remove_file(path, force = false)
+ Entry_.new(path).remove_file
+ rescue
+ raise unless force
+ end
+ module_function :remove_file
+
+ #
+ # Removes a directory +dir+ and its contents recursively.
+ # This method ignores StandardError if +force+ is true.
+ #
+ def remove_dir(path, force = false)
+ remove_entry path, force # FIXME?? check if it is a directory
+ end
+ module_function :remove_dir
+
+ #
+ # Returns true if the contents of a file +a+ and a file +b+ are identical.
+ #
+ # Bundler::FileUtils.compare_file('somefile', 'somefile') #=> true
+ # Bundler::FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false
+ #
+ def compare_file(a, b)
+ return false unless File.size(a) == File.size(b)
+ File.open(a, 'rb') {|fa|
+ File.open(b, 'rb') {|fb|
+ return compare_stream(fa, fb)
+ }
+ }
+ end
+ module_function :compare_file
+
+ alias identical? compare_file
+ alias cmp compare_file
+ module_function :identical?
+ module_function :cmp
+
+ #
+ # Returns true if the contents of a stream +a+ and +b+ are identical.
+ #
+ def compare_stream(a, b)
+ bsize = fu_stream_blksize(a, b)
+ sa = String.new(capacity: bsize)
+ sb = String.new(capacity: bsize)
+ begin
+ a.read(bsize, sa)
+ b.read(bsize, sb)
+ return true if sa.empty? && sb.empty?
+ end while sa == sb
+ false
+ end
+ module_function :compare_stream
+
+ #
+ # If +src+ is not same as +dest+, copies it and changes the permission
+ # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+.
+ # This method removes destination before copy.
+ #
+ # Bundler::FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true
+ # Bundler::FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true
+ #
+ def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,
+ noop: nil, verbose: nil)
+ if verbose
+ msg = +"install -c"
+ msg << ' -p' if preserve
+ msg << ' -m ' << mode_to_s(mode) if mode
+ msg << " -o #{owner}" if owner
+ msg << " -g #{group}" if group
+ msg << ' ' << [src,dest].flatten.join(' ')
+ fu_output_message msg
+ end
+ return if noop
+ uid = fu_get_uid(owner)
+ gid = fu_get_gid(group)
+ fu_each_src_dest(src, dest) do |s, d|
+ st = File.stat(s)
+ unless File.exist?(d) and compare_file(s, d)
+ remove_file d, true
+ copy_file s, d
+ File.utime st.atime, st.mtime, d if preserve
+ File.chmod fu_mode(mode, st), d if mode
+ File.chown uid, gid, d if uid or gid
+ end
+ end
+ end
+ module_function :install
+
+ def user_mask(target) #:nodoc:
+ target.each_char.inject(0) do |mask, chr|
+ case chr
+ when "u"
+ mask | 04700
+ when "g"
+ mask | 02070
+ when "o"
+ mask | 01007
+ when "a"
+ mask | 07777
+ else
+ raise ArgumentError, "invalid `who' symbol in file mode: #{chr}"
+ end
+ end
+ end
+ private_module_function :user_mask
+
+ def apply_mask(mode, user_mask, op, mode_mask) #:nodoc:
+ case op
+ when '='
+ (mode & ~user_mask) | (user_mask & mode_mask)
+ when '+'
+ mode | (user_mask & mode_mask)
+ when '-'
+ mode & ~(user_mask & mode_mask)
+ end
+ end
+ private_module_function :apply_mask
+
+ def symbolic_modes_to_i(mode_sym, path) #:nodoc:
+ mode = if File::Stat === path
+ path.mode
+ else
+ File.stat(path).mode
+ end
+ mode_sym.split(/,/).inject(mode & 07777) do |current_mode, clause|
+ target, *actions = clause.split(/([=+-])/)
+ raise ArgumentError, "invalid file mode: #{mode_sym}" if actions.empty?
+ target = 'a' if target.empty?
+ user_mask = user_mask(target)
+ actions.each_slice(2) do |op, perm|
+ need_apply = op == '='
+ mode_mask = (perm || '').each_char.inject(0) do |mask, chr|
+ case chr
+ when "r"
+ mask | 0444
+ when "w"
+ mask | 0222
+ when "x"
+ mask | 0111
+ when "X"
+ if FileTest.directory? path
+ mask | 0111
+ else
+ mask
+ end
+ when "s"
+ mask | 06000
+ when "t"
+ mask | 01000
+ when "u", "g", "o"
+ if mask.nonzero?
+ current_mode = apply_mask(current_mode, user_mask, op, mask)
+ end
+ need_apply = false
+ copy_mask = user_mask(chr)
+ (current_mode & copy_mask) / (copy_mask & 0111) * (user_mask & 0111)
+ else
+ raise ArgumentError, "invalid `perm' symbol in file mode: #{chr}"
+ end
+ end
+
+ if mode_mask.nonzero? || need_apply
+ current_mode = apply_mask(current_mode, user_mask, op, mode_mask)
+ end
+ end
+ current_mode
+ end
+ end
+ private_module_function :symbolic_modes_to_i
+
+ def fu_mode(mode, path) #:nodoc:
+ mode.is_a?(String) ? symbolic_modes_to_i(mode, path) : mode
+ end
+ private_module_function :fu_mode
+
+ def mode_to_s(mode) #:nodoc:
+ mode.is_a?(String) ? mode : "%o" % mode
+ end
+ private_module_function :mode_to_s
+
+ #
+ # Changes permission bits on the named files (in +list+) to the bit pattern
+ # represented by +mode+.
+ #
+ # +mode+ is the symbolic and absolute mode can be used.
+ #
+ # Absolute mode is
+ # Bundler::FileUtils.chmod 0755, 'somecommand'
+ # Bundler::FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
+ # Bundler::FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true
+ #
+ # Symbolic mode is
+ # Bundler::FileUtils.chmod "u=wrx,go=rx", 'somecommand'
+ # Bundler::FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb)
+ # Bundler::FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', :verbose => true
+ #
+ # "a" :: is user, group, other mask.
+ # "u" :: is user's mask.
+ # "g" :: is group's mask.
+ # "o" :: is other's mask.
+ # "w" :: is write permission.
+ # "r" :: is read permission.
+ # "x" :: is execute permission.
+ # "X" ::
+ # is execute permission for directories only, must be used in conjunction with "+"
+ # "s" :: is uid, gid.
+ # "t" :: is sticky bit.
+ # "+" :: is added to a class given the specified mode.
+ # "-" :: Is removed from a given class given mode.
+ # "=" :: Is the exact nature of the class will be given a specified mode.
+
+ def chmod(mode, list, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose
+ return if noop
+ list.each do |path|
+ Entry_.new(path).chmod(fu_mode(mode, path))
+ end
+ end
+ module_function :chmod
+
+ #
+ # Changes permission bits on the named files (in +list+)
+ # to the bit pattern represented by +mode+.
+ #
+ # Bundler::FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
+ # Bundler::FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}"
+ #
+ def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)
+ list = fu_list(list)
+ fu_output_message sprintf('chmod -R%s %s %s',
+ (force ? 'f' : ''),
+ mode_to_s(mode), list.join(' ')) if verbose
+ return if noop
+ list.each do |root|
+ Entry_.new(root).traverse do |ent|
+ begin
+ ent.chmod(fu_mode(mode, ent.path))
+ rescue
+ raise unless force
+ end
+ end
+ end
+ end
+ module_function :chmod_R
+
+ #
+ # Changes owner and group on the named files (in +list+)
+ # to the user +user+ and the group +group+. +user+ and +group+
+ # may be an ID (Integer/String) or a name (String).
+ # If +user+ or +group+ is nil, this method does not change
+ # the attribute.
+ #
+ # Bundler::FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
+ # Bundler::FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true
+ #
+ def chown(user, group, list, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message sprintf('chown %s %s',
+ (group ? "#{user}:#{group}" : user || ':'),
+ list.join(' ')) if verbose
+ return if noop
+ uid = fu_get_uid(user)
+ gid = fu_get_gid(group)
+ list.each do |path|
+ Entry_.new(path).chown uid, gid
+ end
+ end
+ module_function :chown
+
+ #
+ # Changes owner and group on the named files (in +list+)
+ # to the user +user+ and the group +group+ recursively.
+ # +user+ and +group+ may be an ID (Integer/String) or
+ # a name (String). If +user+ or +group+ is nil, this
+ # method does not change the attribute.
+ #
+ # Bundler::FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
+ # Bundler::FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true
+ #
+ def chown_R(user, group, list, noop: nil, verbose: nil, force: nil)
+ list = fu_list(list)
+ fu_output_message sprintf('chown -R%s %s %s',
+ (force ? 'f' : ''),
+ (group ? "#{user}:#{group}" : user || ':'),
+ list.join(' ')) if verbose
+ return if noop
+ uid = fu_get_uid(user)
+ gid = fu_get_gid(group)
+ list.each do |root|
+ Entry_.new(root).traverse do |ent|
+ begin
+ ent.chown uid, gid
+ rescue
+ raise unless force
+ end
+ end
+ end
+ end
+ module_function :chown_R
+
+ begin
+ require 'etc'
+ rescue LoadError # rescue LoadError for miniruby
+ end
+
+ def fu_get_uid(user) #:nodoc:
+ return nil unless user
+ case user
+ when Integer
+ user
+ when /\A\d+\z/
+ user.to_i
+ else
+ Etc.getpwnam(user) ? Etc.getpwnam(user).uid : nil
+ end
+ end
+ private_module_function :fu_get_uid
+
+ def fu_get_gid(group) #:nodoc:
+ return nil unless group
+ case group
+ when Integer
+ group
+ when /\A\d+\z/
+ group.to_i
+ else
+ Etc.getgrnam(group) ? Etc.getgrnam(group).gid : nil
+ end
+ end
+ private_module_function :fu_get_gid
+
+ #
+ # Updates modification time (mtime) and access time (atime) of file(s) in
+ # +list+. Files are created if they don't exist.
+ #
+ # Bundler::FileUtils.touch 'timestamp'
+ # Bundler::FileUtils.touch Dir.glob('*.c'); system 'make'
+ #
+ def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)
+ list = fu_list(list)
+ t = mtime
+ if verbose
+ fu_output_message "touch #{nocreate ? '-c ' : ''}#{t ? t.strftime('-t %Y%m%d%H%M.%S ') : ''}#{list.join ' '}"
+ end
+ return if noop
+ list.each do |path|
+ created = nocreate
+ begin
+ File.utime(t, t, path)
+ rescue Errno::ENOENT
+ raise if created
+ File.open(path, 'a') {
+ ;
+ }
+ created = true
+ retry if t
+ end
+ end
+ end
+ module_function :touch
+
+ private
+
+ module StreamUtils_
+ private
+
+ def fu_windows?
+ /mswin|mingw|bccwin|emx/ =~ RUBY_PLATFORM
+ end
+
+ def fu_copy_stream0(src, dest, blksize = nil) #:nodoc:
+ IO.copy_stream(src, dest)
+ end
+
+ def fu_stream_blksize(*streams)
+ streams.each do |s|
+ next unless s.respond_to?(:stat)
+ size = fu_blksize(s.stat)
+ return size if size
+ end
+ fu_default_blksize()
+ end
+
+ def fu_blksize(st)
+ s = st.blksize
+ return nil unless s
+ return nil if s == 0
+ s
+ end
+
+ def fu_default_blksize
+ 1024
+ end
+ end
+
+ include StreamUtils_
+ extend StreamUtils_
+
+ class Entry_ #:nodoc: internal use only
+ include StreamUtils_
+
+ def initialize(a, b = nil, deref = false)
+ @prefix = @rel = @path = nil
+ if b
+ @prefix = a
+ @rel = b
+ else
+ @path = a
+ end
+ @deref = deref
+ @stat = nil
+ @lstat = nil
+ end
+
+ def inspect
+ "\#<#{self.class} #{path()}>"
+ end
+
+ def path
+ if @path
+ File.path(@path)
+ else
+ join(@prefix, @rel)
+ end
+ end
+
+ def prefix
+ @prefix || @path
+ end
+
+ def rel
+ @rel
+ end
+
+ def dereference?
+ @deref
+ end
+
+ def exist?
+ begin
+ lstat
+ true
+ rescue Errno::ENOENT
+ false
+ end
+ end
+
+ def file?
+ s = lstat!
+ s and s.file?
+ end
+
+ def directory?
+ s = lstat!
+ s and s.directory?
+ end
+
+ def symlink?
+ s = lstat!
+ s and s.symlink?
+ end
+
+ def chardev?
+ s = lstat!
+ s and s.chardev?
+ end
+
+ def blockdev?
+ s = lstat!
+ s and s.blockdev?
+ end
+
+ def socket?
+ s = lstat!
+ s and s.socket?
+ end
+
+ def pipe?
+ s = lstat!
+ s and s.pipe?
+ end
+
+ S_IF_DOOR = 0xD000
+
+ def door?
+ s = lstat!
+ s and (s.mode & 0xF000 == S_IF_DOOR)
+ end
+
+ def entries
+ opts = {}
+ opts[:encoding] = ::Encoding::UTF_8 if fu_windows?
+ Dir.entries(path(), opts)\
+ .reject {|n| n == '.' or n == '..' }\
+ .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
+ end
+
+ def stat
+ return @stat if @stat
+ if lstat() and lstat().symlink?
+ @stat = File.stat(path())
+ else
+ @stat = lstat()
+ end
+ @stat
+ end
+
+ def stat!
+ return @stat if @stat
+ if lstat! and lstat!.symlink?
+ @stat = File.stat(path())
+ else
+ @stat = lstat!
+ end
+ @stat
+ rescue SystemCallError
+ nil
+ end
+
+ def lstat
+ if dereference?
+ @lstat ||= File.stat(path())
+ else
+ @lstat ||= File.lstat(path())
+ end
+ end
+
+ def lstat!
+ lstat()
+ rescue SystemCallError
+ nil
+ end
+
+ def chmod(mode)
+ if symlink?
+ File.lchmod mode, path() if have_lchmod?
+ else
+ File.chmod mode, path()
+ end
+ end
+
+ def chown(uid, gid)
+ if symlink?
+ File.lchown uid, gid, path() if have_lchown?
+ else
+ File.chown uid, gid, path()
+ end
+ end
+
+ def copy(dest)
+ lstat
+ case
+ when file?
+ copy_file dest
+ when directory?
+ if !File.exist?(dest) and descendant_directory?(dest, path)
+ raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest]
+ end
+ begin
+ Dir.mkdir dest
+ rescue
+ raise unless File.directory?(dest)
+ end
+ when symlink?
+ File.symlink File.readlink(path()), dest
+ when chardev?
+ raise "cannot handle device file" unless File.respond_to?(:mknod)
+ mknod dest, ?c, 0666, lstat().rdev
+ when blockdev?
+ raise "cannot handle device file" unless File.respond_to?(:mknod)
+ mknod dest, ?b, 0666, lstat().rdev
+ when socket?
+ raise "cannot handle socket" unless File.respond_to?(:mknod)
+ mknod dest, nil, lstat().mode, 0
+ when pipe?
+ raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
+ mkfifo dest, 0666
+ when door?
+ raise "cannot handle door: #{path()}"
+ else
+ raise "unknown file type: #{path()}"
+ end
+ end
+
+ def copy_file(dest)
+ File.open(path()) do |s|
+ File.open(dest, 'wb', s.stat.mode) do |f|
+ IO.copy_stream(s, f)
+ end
+ end
+ end
+
+ def copy_metadata(path)
+ st = lstat()
+ if !st.symlink?
+ File.utime st.atime, st.mtime, path
+ end
+ mode = st.mode
+ begin
+ if st.symlink?
+ begin
+ File.lchown st.uid, st.gid, path
+ rescue NotImplementedError
+ end
+ else
+ File.chown st.uid, st.gid, path
+ end
+ rescue Errno::EPERM, Errno::EACCES
+ # clear setuid/setgid
+ mode &= 01777
+ end
+ if st.symlink?
+ begin
+ File.lchmod mode, path
+ rescue NotImplementedError
+ end
+ else
+ File.chmod mode, path
+ end
+ end
+
+ def remove
+ if directory?
+ remove_dir1
+ else
+ remove_file
+ end
+ end
+
+ def remove_dir1
+ platform_support {
+ Dir.rmdir path().chomp(?/)
+ }
+ end
+
+ def remove_file
+ platform_support {
+ File.unlink path
+ }
+ end
+
+ def platform_support
+ return yield unless fu_windows?
+ first_time_p = true
+ begin
+ yield
+ rescue Errno::ENOENT
+ raise
+ rescue => err
+ if first_time_p
+ first_time_p = false
+ begin
+ File.chmod 0700, path() # Windows does not have symlink
+ retry
+ rescue SystemCallError
+ end
+ end
+ raise err
+ end
+ end
+
+ def preorder_traverse
+ stack = [self]
+ while ent = stack.pop
+ yield ent
+ stack.concat ent.entries.reverse if ent.directory?
+ end
+ end
+
+ alias traverse preorder_traverse
+
+ def postorder_traverse
+ if directory?
+ entries().each do |ent|
+ ent.postorder_traverse do |e|
+ yield e
+ end
+ end
+ end
+ ensure
+ yield self
+ end
+
+ def wrap_traverse(pre, post)
+ pre.call self
+ if directory?
+ entries.each do |ent|
+ ent.wrap_traverse pre, post
+ end
+ end
+ post.call self
+ end
+
+ private
+
+ $fileutils_rb_have_lchmod = nil
+
+ def have_lchmod?
+ # This is not MT-safe, but it does not matter.
+ if $fileutils_rb_have_lchmod == nil
+ $fileutils_rb_have_lchmod = check_have_lchmod?
+ end
+ $fileutils_rb_have_lchmod
+ end
+
+ def check_have_lchmod?
+ return false unless File.respond_to?(:lchmod)
+ File.lchmod 0
+ return true
+ rescue NotImplementedError
+ return false
+ end
+
+ $fileutils_rb_have_lchown = nil
+
+ def have_lchown?
+ # This is not MT-safe, but it does not matter.
+ if $fileutils_rb_have_lchown == nil
+ $fileutils_rb_have_lchown = check_have_lchown?
+ end
+ $fileutils_rb_have_lchown
+ end
+
+ def check_have_lchown?
+ return false unless File.respond_to?(:lchown)
+ File.lchown nil, nil
+ return true
+ rescue NotImplementedError
+ return false
+ end
+
+ def join(dir, base)
+ return File.path(dir) if not base or base == '.'
+ return File.path(base) if not dir or dir == '.'
+ File.join(dir, base)
+ end
+
+ if File::ALT_SEPARATOR
+ DIRECTORY_TERM = "(?=[/#{Regexp.quote(File::ALT_SEPARATOR)}]|\\z)"
+ else
+ DIRECTORY_TERM = "(?=/|\\z)"
+ end
+ SYSCASE = File::FNM_SYSCASE.nonzero? ? "-i" : ""
+
+ def descendant_directory?(descendant, ascendant)
+ /\A(?#{SYSCASE}:#{Regexp.quote(ascendant)})#{DIRECTORY_TERM}/ =~ File.dirname(descendant)
+ end
+ end # class Entry_
+
+ def fu_list(arg) #:nodoc:
+ [arg].flatten.map {|path| File.path(path) }
+ end
+ private_module_function :fu_list
+
+ def fu_each_src_dest(src, dest) #:nodoc:
+ fu_each_src_dest0(src, dest) do |s, d|
+ raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
+ yield s, d
+ end
+ end
+ private_module_function :fu_each_src_dest
+
+ def fu_each_src_dest0(src, dest) #:nodoc:
+ if tmp = Array.try_convert(src)
+ tmp.each do |s|
+ s = File.path(s)
+ yield s, File.join(dest, File.basename(s))
+ end
+ else
+ src = File.path(src)
+ if File.directory?(dest)
+ yield src, File.join(dest, File.basename(src))
+ else
+ yield src, File.path(dest)
+ end
+ end
+ end
+ private_module_function :fu_each_src_dest0
+
+ def fu_same?(a, b) #:nodoc:
+ File.identical?(a, b)
+ end
+ private_module_function :fu_same?
+
+ @fileutils_output = $stderr
+ @fileutils_label = ''
+
+ def fu_output_message(msg) #:nodoc:
+ @fileutils_output ||= $stderr
+ @fileutils_label ||= ''
+ @fileutils_output.puts @fileutils_label + msg
+ end
+ private_module_function :fu_output_message
+
+ # This hash table holds command options.
+ OPT_TABLE = {} #:nodoc: internal use only
+ (private_instance_methods & methods(false)).inject(OPT_TABLE) {|tbl, name|
+ (tbl[name.to_s] = instance_method(name).parameters).map! {|t, n| n if t == :key}.compact!
+ tbl
+ }
+
+ #
+ # Returns an Array of method names which have any options.
+ #
+ # p Bundler::FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...]
+ #
+ def self.commands
+ OPT_TABLE.keys
+ end
+
+ #
+ # Returns an Array of option names.
+ #
+ # p Bundler::FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"]
+ #
+ def self.options
+ OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
+ end
+
+ #
+ # Returns true if the method +mid+ have an option +opt+.
+ #
+ # p Bundler::FileUtils.have_option?(:cp, :noop) #=> true
+ # p Bundler::FileUtils.have_option?(:rm, :force) #=> true
+ # p Bundler::FileUtils.have_option?(:rm, :preserve) #=> false
+ #
+ def self.have_option?(mid, opt)
+ li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
+ li.include?(opt)
+ end
+
+ #
+ # Returns an Array of option names of the method +mid+.
+ #
+ # p Bundler::FileUtils.options_of(:rm) #=> ["noop", "verbose", "force"]
+ #
+ def self.options_of(mid)
+ OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
+ end
+
+ #
+ # Returns an Array of method names which have the option +opt+.
+ #
+ # p Bundler::FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
+ #
+ def self.collect_method(opt)
+ OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
+ end
+
+ LOW_METHODS = singleton_methods(false) - collect_method(:noop).map(&:intern)
+ module LowMethods
+ private
+ def _do_nothing(*)end
+ ::Bundler::FileUtils::LOW_METHODS.map {|name| alias_method name, :_do_nothing}
+ end
+
+ METHODS = singleton_methods() - [:private_module_function,
+ :commands, :options, :have_option?, :options_of, :collect_method]
+
+ #
+ # This module has all methods of Bundler::FileUtils module, but it outputs messages
+ # before acting. This equates to passing the <tt>:verbose</tt> flag to
+ # methods in Bundler::FileUtils.
+ #
+ module Verbose
+ include Bundler::FileUtils
+ @fileutils_output = $stderr
+ @fileutils_label = ''
+ names = ::Bundler::FileUtils.collect_method(:verbose)
+ names.each do |name|
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{name}(*args, **options)
+ super(*args, **options, verbose: true)
+ end
+ EOS
+ end
+ private(*names)
+ extend self
+ class << self
+ public(*::Bundler::FileUtils::METHODS)
+ end
+ end
+
+ #
+ # This module has all methods of Bundler::FileUtils module, but never changes
+ # files/directories. This equates to passing the <tt>:noop</tt> flag
+ # to methods in Bundler::FileUtils.
+ #
+ module NoWrite
+ include Bundler::FileUtils
+ include LowMethods
+ @fileutils_output = $stderr
+ @fileutils_label = ''
+ names = ::Bundler::FileUtils.collect_method(:noop)
+ names.each do |name|
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{name}(*args, **options)
+ super(*args, **options, noop: true)
+ end
+ EOS
+ end
+ private(*names)
+ extend self
+ class << self
+ public(*::Bundler::FileUtils::METHODS)
+ end
+ end
+
+ #
+ # This module has all methods of Bundler::FileUtils module, but never changes
+ # files/directories, with printing message before acting.
+ # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
+ # to methods in Bundler::FileUtils.
+ #
+ module DryRun
+ include Bundler::FileUtils
+ include LowMethods
+ @fileutils_output = $stderr
+ @fileutils_label = ''
+ names = ::Bundler::FileUtils.collect_method(:noop)
+ names.each do |name|
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{name}(*args, **options)
+ super(*args, **options, noop: true, verbose: true)
+ end
+ EOS
+ end
+ private(*names)
+ extend self
+ class << self
+ public(*::Bundler::FileUtils::METHODS)
+ end
+ end
+
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo.rb b/lib/bundler/vendor/molinillo/lib/molinillo.rb
index 134bf1d720..9e2867144f 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo.rb
@@ -1,4 +1,6 @@
# frozen_string_literal: true
+
+require 'bundler/vendor/molinillo/lib/molinillo/compatibility'
require 'bundler/vendor/molinillo/lib/molinillo/gem_metadata'
require 'bundler/vendor/molinillo/lib/molinillo/errors'
require 'bundler/vendor/molinillo/lib/molinillo/resolver'
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb b/lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb
new file mode 100644
index 0000000000..3eba8e4083
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Bundler::Molinillo
+ # Hacks needed for old Ruby versions.
+ module Compatibility
+ module_function
+
+ if [].respond_to?(:flat_map)
+ # Flat map
+ # @param [Enumerable] enum an enumerable object
+ # @block the block to flat-map with
+ # @return The enum, flat-mapped
+ def flat_map(enum, &blk)
+ enum.flat_map(&blk)
+ end
+ else
+ # Flat map
+ # @param [Enumerable] enum an enumerable object
+ # @block the block to flat-map with
+ # @return The enum, flat-mapped
+ def flat_map(enum, &blk)
+ enum.map(&blk).flatten(1)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb
index 253c18764f..bcacf35243 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
# @!visibility private
module Delegates
@@ -45,6 +46,12 @@ module Bundler::Molinillo
current_state = state || Bundler::Molinillo::ResolutionState.empty
current_state.conflicts
end
+
+ # (see Bundler::Molinillo::ResolutionState#unused_unwind_options)
+ def unused_unwind_options
+ current_state = state || Bundler::Molinillo::ResolutionState.empty
+ current_state.unused_unwind_options
+ end
end
end
end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb
index 29f48d5b3c..ec9c770a28 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
module Delegates
# Delegates all {Bundler::Molinillo::SpecificationProvider} methods to a
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb
index 76e84ab7e6..677a8bd916 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'set'
require 'tsort'
@@ -147,8 +148,8 @@ module Bundler::Molinillo
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)
+ parent_vertex = vertex_named(parent_name)
+ add_edge(parent_vertex, vertex, requirement)
end
vertex
end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb
index e0dfe6cbbd..c04c7eec9c 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
class DependencyGraph
# An action that modifies a {DependencyGraph} that is reversible.
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb
index 9092e4d546..9849aea2fe 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
module Bundler::Molinillo
class DependencyGraph
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb
index eda4251801..0a1e08255b 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
module Bundler::Molinillo
class DependencyGraph
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb
index e9125a59c6..1d9f4b327d 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
module Bundler::Molinillo
class DependencyGraph
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb
index d20b2cb0e0..385dcbdd06 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
module Bundler::Molinillo
class DependencyGraph
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb
index 72a705e023..8582dd19c1 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular'
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex'
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge'
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb
index 8d8e10fedf..37286d104a 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
module Bundler::Molinillo
class DependencyGraph
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb
index 53524d36ad..d6ad16e07a 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
module Bundler::Molinillo
class DependencyGraph
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb
index eab989e7bc..e4d016de24 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
class DependencyGraph
# A vertex in a {DependencyGraph} that encapsulates a {#name} and a
@@ -32,7 +33,7 @@ module Bundler::Molinillo
# @return [Array<Object>] all of the requirements that required
# this vertex
def requirements
- incoming_edges.map(&:requirement) + explicit_requirements
+ (incoming_edges.map(&:requirement) + explicit_requirements).uniq
end
# @return [Array<Edge>] the edges of {#graph} that have `self` as their
@@ -53,7 +54,7 @@ module Bundler::Molinillo
# {#descendent?}
def recursive_predecessors
vertices = predecessors
- vertices += vertices.map(&:recursive_predecessors).flatten(1)
+ vertices += Compatibility.flat_map(vertices, &:recursive_predecessors)
vertices.uniq!
vertices
end
@@ -68,7 +69,7 @@ module Bundler::Molinillo
# {#ancestor?}
def recursive_successors
vertices = successors
- vertices += vertices.map(&:recursive_successors).flatten(1)
+ vertices += Compatibility.flat_map(vertices, &:recursive_successors)
vertices.uniq!
vertices
end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb b/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb
index f904bd0814..fb343250b1 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
# An error that occurred during the resolution process
class ResolverError < StandardError; end
@@ -41,11 +42,11 @@ module Bundler::Molinillo
attr_reader :dependencies
# Initializes a new error with the given circular vertices.
- # @param [Array<DependencyGraph::Vertex>] nodes the nodes in the dependency
+ # @param [Array<DependencyGraph::Vertex>] vertices the vertices in the dependency
# that caused the error
- def initialize(nodes)
- super "There is a circular dependency between #{nodes.map(&:name).join(' and ')}"
- @dependencies = nodes.map(&:payload).to_set
+ def initialize(vertices)
+ super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}"
+ @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set
end
end
@@ -55,11 +56,16 @@ module Bundler::Molinillo
# resolution to fail
attr_reader :conflicts
+ # @return [SpecificationProvider] the specification provider used during
+ # resolution
+ attr_reader :specification_provider
+
# Initializes a new error with the given version conflicts.
# @param [{String => Resolution::Conflict}] conflicts see {#conflicts}
- def initialize(conflicts)
+ # @param [SpecificationProvider] specification_provider see {#specification_provider}
+ def initialize(conflicts, specification_provider)
pairs = []
- conflicts.values.flatten.map(&:requirements).flatten.each do |conflicting|
+ Compatibility.flat_map(conflicts.values.flatten, &:requirements).each do |conflicting|
conflicting.each do |source, conflict_requirements|
conflict_requirements.each do |c|
pairs << [c, source]
@@ -69,7 +75,64 @@ module Bundler::Molinillo
super "Unable to satisfy the following requirements:\n\n" \
"#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}"
+
@conflicts = conflicts
+ @specification_provider = specification_provider
+ end
+
+ require 'bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider'
+ include Delegates::SpecificationProvider
+
+ # @return [String] An error message that includes requirement trees,
+ # which is much more detailed & customizable than the default message
+ # @param [Hash] opts the options to create a message with.
+ # @option opts [String] :solver_name The user-facing name of the solver
+ # @option opts [String] :possibility_type The generic name of a possibility
+ # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees
+ # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements
+ # @option opts [Proc] :additional_message_for_conflict A proc that appends additional
+ # messages for each conflict
+ # @option opts [Proc] :version_for_spec A proc that returns the version number for a
+ # possibility
+ def message_with_trees(opts = {})
+ solver_name = opts.delete(:solver_name) { self.class.name.split('::').first }
+ possibility_type = opts.delete(:possibility_type) { 'possibility named' }
+ reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } }
+ printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } }
+ additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} }
+ version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) }
+
+ conflicts.sort.reduce(''.dup) do |o, (name, conflict)|
+ o << %(\n#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":\n)
+ if conflict.locked_requirement
+ o << %( In snapshot (#{name_for_locking_dependency_source}):\n)
+ o << %( #{printable_requirement.call(conflict.locked_requirement)}\n)
+ o << %(\n)
+ end
+ o << %( In #{name_for_explicit_dependency_source}:\n)
+ trees = reduce_trees.call(conflict.requirement_trees)
+
+ o << trees.map do |tree|
+ t = ''.dup
+ depth = 2
+ tree.each do |req|
+ t << ' ' * depth << req.to_s
+ unless tree.last == req
+ if spec = conflict.activated_by_name[name_for(req)]
+ t << %( was resolved to #{version_for_spec.call(spec)}, which)
+ end
+ t << %( depends on)
+ end
+ t << %(\n)
+ depth += 1
+ end
+ t
+ end.join("\n")
+
+ additional_message_for_conflict.call(o, name, conflict)
+
+ o
+ end.strip
end
end
end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb
index a4fb6dd68e..3feb7be9b5 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
# The version of Bundler::Molinillo.
- VERSION = '0.5.7'.freeze
+ VERSION = '0.6.4'.freeze
end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb b/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb
index 0f1ad195f2..fa094c1981 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
# Provides information about specifcations and dependencies to the resolver,
# allowing the {Resolver} class to remain generic while still providing power
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb b/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb
index d47cfa2928..a166bc6991 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
# Conveys information about the resolution process to a user.
module UI
@@ -48,7 +49,8 @@ module Bundler::Molinillo
if debug?
debug_info = yield
debug_info = debug_info.inspect unless debug_info.is_a?(String)
- output.puts debug_info.split("\n").map { |s| ' ' * depth + s }
+ debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" }
+ output.puts debug_info
end
end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb b/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb
index 1845966a75..0eb665d17a 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
class Resolver
# A specific resolution from a given {Resolver}
@@ -8,22 +9,125 @@ module Bundler::Molinillo
# @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict
# @attr [Object, nil] existing the existing spec that was in conflict with
# the {#possibility}
- # @attr [Object] possibility the spec that was unable to be activated due
- # to a conflict
+ # @attr [Object] possibility_set the set of specs that was unable to be
+ # activated due to a conflict.
# @attr [Object] locked_requirement the relevant locking requirement.
# @attr [Array<Array<Object>>] requirement_trees the different requirement
# trees that led to every requirement for the conflicting name.
# @attr [{String=>Object}] activated_by_name the already-activated specs.
+ # @attr [Object] underlying_error an error that has occurred during resolution, and
+ # will be raised at the end of it if no resolution is found.
Conflict = Struct.new(
:requirement,
:requirements,
:existing,
- :possibility,
+ :possibility_set,
:locked_requirement,
:requirement_trees,
- :activated_by_name
+ :activated_by_name,
+ :underlying_error
)
+ class Conflict
+ # @return [Object] a spec that was unable to be activated due to a conflict
+ def possibility
+ possibility_set && possibility_set.latest_version
+ end
+ end
+
+ # A collection of possibility states that share the same dependencies
+ # @attr [Array] dependencies the dependencies for this set of possibilities
+ # @attr [Array] possibilities the possibilities
+ PossibilitySet = Struct.new(:dependencies, :possibilities)
+
+ class PossibilitySet
+ # String representation of the possibility set, for debugging
+ def to_s
+ "[#{possibilities.join(', ')}]"
+ end
+
+ # @return [Object] most up-to-date dependency in the possibility set
+ def latest_version
+ possibilities.last
+ end
+ end
+
+ # Details of the state to unwind to when a conflict occurs, and the cause of the unwind
+ # @attr [Integer] state_index the index of the state to unwind to
+ # @attr [Object] state_requirement the requirement of the state we're unwinding to
+ # @attr [Array] requirement_tree for the requirement we're relaxing
+ # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict
+ # @attr [Array] requirement_trees for the conflict
+ # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind
+ UnwindDetails = Struct.new(
+ :state_index,
+ :state_requirement,
+ :requirement_tree,
+ :conflicting_requirements,
+ :requirement_trees,
+ :requirements_unwound_to_instead
+ )
+
+ class UnwindDetails
+ include Comparable
+
+ # We compare UnwindDetails when choosing which state to unwind to. If
+ # two options have the same state_index we prefer the one most
+ # removed from a requirement that caused the conflict. Both options
+ # would unwind to the same state, but a `grandparent` option will
+ # filter out fewer of its possibilities after doing so - where a state
+ # is both a `parent` and a `grandparent` to requirements that have
+ # caused a conflict this is the correct behaviour.
+ # @param [UnwindDetail] other UnwindDetail to be compared
+ # @return [Integer] integer specifying ordering
+ def <=>(other)
+ if state_index > other.state_index
+ 1
+ elsif state_index == other.state_index
+ reversed_requirement_tree_index <=> other.reversed_requirement_tree_index
+ else
+ -1
+ end
+ end
+
+ # @return [Integer] index of state requirement in reversed requirement tree
+ # (the conflicting requirement itself will be at position 0)
+ def reversed_requirement_tree_index
+ @reversed_requirement_tree_index ||=
+ if state_requirement
+ requirement_tree.reverse.index(state_requirement)
+ else
+ 999_999
+ end
+ end
+
+ # @return [Boolean] where the requirement of the state we're unwinding
+ # to directly caused the conflict. Note: in this case, it is
+ # impossible for the state we're unwinding to to be a parent of
+ # any of the other conflicting requirements (or we would have
+ # circularity)
+ def unwinding_to_primary_requirement?
+ requirement_tree.last == state_requirement
+ end
+
+ # @return [Array] array of sub-dependencies to avoid when choosing a
+ # new possibility for the state we've unwound to. Only relevant for
+ # non-primary unwinds
+ def sub_dependencies_to_avoid
+ @requirements_to_avoid ||=
+ requirement_trees.map do |tree|
+ index = tree.index(state_requirement)
+ tree[index + 1] if index
+ end.compact
+ end
+
+ # @return [Array] array of all the requirements that led to the need for
+ # this unwind
+ def all_requirements
+ @all_requirements ||= requirement_trees.flatten(1)
+ end
+ end
+
# @return [SpecificationProvider] the provider that knows about
# dependencies, requirements, specifications, versions, etc.
attr_reader :specification_provider
@@ -64,7 +168,7 @@ module Bundler::Molinillo
start_resolution
while state
- break unless state.requirements.any? || state.requirement
+ break if !state.requirement && state.requirements.empty?
indicate_progress
if state.respond_to?(:pop_possibility_state) # DependencyState
debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" }
@@ -78,7 +182,7 @@ module Bundler::Molinillo
process_topmost_state
end
- activated.freeze
+ resolve_activated_specs
ensure
end_resolution
end
@@ -109,6 +213,19 @@ module Bundler::Molinillo
resolver_ui.before_resolution
end
+ def resolve_activated_specs
+ activated.vertices.each do |_, vertex|
+ next unless vertex.payload
+
+ latest_version = vertex.payload.possibilities.reverse_each.find do |possibility|
+ vertex.requirements.all? { |req| requirement_satisfied_by?(req, activated, possibility) }
+ end
+
+ activated.set_payload(vertex.name, latest_version)
+ end
+ activated.freeze
+ end
+
# Ends the resolution process
# @return [void]
def end_resolution
@@ -136,9 +253,12 @@ module Bundler::Molinillo
if possibility
attempt_to_activate
else
- create_conflict if state.is_a? PossibilityState
- unwind_for_conflict until possibility && state.is_a?(DependencyState)
+ create_conflict
+ unwind_for_conflict
end
+ rescue CircularDependencyError => underlying_error
+ create_conflict(underlying_error)
+ unwind_for_conflict
end
# @return [Object] the current possibility that the resolution is trying
@@ -158,7 +278,10 @@ module Bundler::Molinillo
# @return [DependencyState] the initial state for the resolution
def initial_state
graph = DependencyGraph.new.tap do |dg|
- original_requested.each { |r| dg.add_vertex(name_for(r), nil, true).tap { |v| v.explicit_requirements << r } }
+ original_requested.each do |requested|
+ vertex = dg.add_vertex(name_for(requested), nil, true)
+ vertex.explicit_requirements << requested
+ end
dg.tag(:initial_state)
end
@@ -169,45 +292,280 @@ module Bundler::Molinillo
requirements,
graph,
initial_requirement,
- initial_requirement && search_for(initial_requirement),
+ possibilities_for_requirement(initial_requirement, graph),
0,
- {}
+ {},
+ []
)
end
# Unwinds the states stack because a conflict has been encountered
# @return [void]
def unwind_for_conflict
- debug(depth) { "Unwinding for conflict: #{requirement} to #{state_index_for_unwind / 2}" }
+ details_for_unwind = build_details_for_unwind
+ unwind_options = unused_unwind_options
+ debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" }
conflicts.tap do |c|
- sliced_states = states.slice!((state_index_for_unwind + 1)..-1)
- raise VersionConflict.new(c) unless state
+ sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1)
+ raise_error_unless_state(c)
activated.rewind_to(sliced_states.first || :initial_state) if sliced_states
state.conflicts = c
+ state.unused_unwind_options = unwind_options
+ filter_possibilities_after_unwind(details_for_unwind)
index = states.size - 1
@parents_of.each { |_, a| a.reject! { |i| i >= index } }
+ state.unused_unwind_options.reject! { |uw| uw.state_index >= index }
+ end
+ end
+
+ # Raises a VersionConflict error, or any underlying error, if there is no
+ # current state
+ # @return [void]
+ def raise_error_unless_state(conflicts)
+ return if state
+
+ error = conflicts.values.map(&:underlying_error).compact.first
+ raise error || VersionConflict.new(conflicts, specification_provider)
+ end
+
+ # @return [UnwindDetails] Details of the nearest index to which we could unwind
+ def build_details_for_unwind
+ # Get the possible unwinds for the current conflict
+ current_conflict = conflicts[name]
+ binding_requirements = binding_requirements_for_conflict(current_conflict)
+ unwind_details = unwind_options_for_requirements(binding_requirements)
+
+ last_detail_for_current_unwind = unwind_details.sort.last
+ current_detail = last_detail_for_current_unwind
+
+ # Look for past conflicts that could be unwound to affect the
+ # requirement tree for the current conflict
+ relevant_unused_unwinds = unused_unwind_options.select do |alternative|
+ intersecting_requirements =
+ last_detail_for_current_unwind.all_requirements &
+ alternative.requirements_unwound_to_instead
+ next if intersecting_requirements.empty?
+ # Find the highest index unwind whilst looping through
+ current_detail = alternative if alternative > current_detail
+ alternative
+ end
+
+ # Add the current unwind options to the `unused_unwind_options` array.
+ # The "used" option will be filtered out during `unwind_for_conflict`.
+ state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 }
+
+ # Update the requirements_unwound_to_instead on any relevant unused unwinds
+ relevant_unused_unwinds.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement }
+ unwind_details.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement }
+
+ current_detail
+ end
+
+ # @param [Array<Object>] array of requirements that combine to create a conflict
+ # @return [Array<UnwindDetails>] array of UnwindDetails that have a chance
+ # of resolving the passed requirements
+ def unwind_options_for_requirements(binding_requirements)
+ unwind_details = []
+
+ trees = []
+ binding_requirements.reverse_each do |r|
+ partial_tree = [r]
+ trees << partial_tree
+ unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, [])
+
+ # If this requirement has alternative possibilities, check if any would
+ # satisfy the other requirements that created this conflict
+ requirement_state = find_state_for(r)
+ if conflict_fixing_possibilities?(requirement_state, binding_requirements)
+ unwind_details << UnwindDetails.new(
+ states.index(requirement_state),
+ r,
+ partial_tree,
+ binding_requirements,
+ trees,
+ []
+ )
+ end
+
+ # Next, look at the parent of this requirement, and check if the requirement
+ # could have been avoided if an alternative PossibilitySet had been chosen
+ parent_r = parent_of(r)
+ next if parent_r.nil?
+ partial_tree.unshift(parent_r)
+ requirement_state = find_state_for(parent_r)
+ if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) }
+ unwind_details << UnwindDetails.new(
+ states.index(requirement_state),
+ parent_r,
+ partial_tree,
+ binding_requirements,
+ trees,
+ []
+ )
+ end
+
+ # Finally, look at the grandparent and up of this requirement, looking
+ # for any possibilities that wouldn't create their parent requirement
+ grandparent_r = parent_of(parent_r)
+ until grandparent_r.nil?
+ partial_tree.unshift(grandparent_r)
+ requirement_state = find_state_for(grandparent_r)
+ if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) }
+ unwind_details << UnwindDetails.new(
+ states.index(requirement_state),
+ grandparent_r,
+ partial_tree,
+ binding_requirements,
+ trees,
+ []
+ )
+ end
+ parent_r = grandparent_r
+ grandparent_r = parent_of(parent_r)
+ end
+ end
+
+ unwind_details
+ end
+
+ # @param [DependencyState] state
+ # @param [Array] array of requirements
+ # @return [Boolean] whether or not the given state has any possibilities
+ # that could satisfy the given requirements
+ def conflict_fixing_possibilities?(state, binding_requirements)
+ return false unless state
+
+ state.possibilities.any? do |possibility_set|
+ possibility_set.possibilities.any? do |poss|
+ possibility_satisfies_requirements?(poss, binding_requirements)
+ end
+ end
+ end
+
+ # Filter's a state's possibilities to remove any that would not fix the
+ # conflict we've just rewound from
+ # @param [UnwindDetails] details of the conflict just unwound from
+ # @return [void]
+ def filter_possibilities_after_unwind(unwind_details)
+ return unless state && !state.possibilities.empty?
+
+ if unwind_details.unwinding_to_primary_requirement?
+ filter_possibilities_for_primary_unwind(unwind_details)
+ else
+ filter_possibilities_for_parent_unwind(unwind_details)
+ end
+ end
+
+ # Filter's a state's possibilities to remove any that would not satisfy
+ # the requirements in the conflict we've just rewound from
+ # @param [UnwindDetails] details of the conflict just unwound from
+ # @return [void]
+ def filter_possibilities_for_primary_unwind(unwind_details)
+ unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index }
+ unwinds_to_state << unwind_details
+ unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements)
+
+ state.possibilities.reject! do |possibility_set|
+ possibility_set.possibilities.none? do |poss|
+ unwind_requirement_sets.any? do |requirements|
+ possibility_satisfies_requirements?(poss, requirements)
+ end
+ end
end
end
- # @return [Integer] The index to which the resolution should unwind in the
- # case of conflict.
- def state_index_for_unwind
- current_requirement = requirement
- existing_requirement = requirement_for_existing_name(name)
- 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
+ # @param [Object] possibility a single possibility
+ # @param [Array] requirements an array of requirements
+ # @return [Boolean] whether the possibility satisfies all of the
+ # given requirements
+ def possibility_satisfies_requirements?(possibility, requirements)
+ name = name_for(possibility)
+
+ activated.tag(:swap)
+ activated.set_payload(name, possibility) if activated.vertex_named(name)
+ satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) }
+ activated.rewind_to(:swap)
+
+ satisfied
+ end
+
+ # Filter's a state's possibilities to remove any that would (eventually)
+ # create a requirement in the conflict we've just rewound from
+ # @param [UnwindDetails] details of the conflict just unwound from
+ # @return [void]
+ def filter_possibilities_for_parent_unwind(unwind_details)
+ unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index }
+ unwinds_to_state << unwind_details
+
+ primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq
+ parent_unwinds = unwinds_to_state.uniq - primary_unwinds
+
+ allowed_possibility_sets = Compatibility.flat_map(primary_unwinds) do |unwind|
+ states[unwind.state_index].possibilities.select do |possibility_set|
+ possibility_set.possibilities.any? do |poss|
+ possibility_satisfies_requirements?(poss, unwind.conflicting_requirements)
end
- r = parent_of(r)
end
end
- index
+ requirements_to_avoid = Compatibility.flat_map(parent_unwinds, &:sub_dependencies_to_avoid)
+
+ state.possibilities.reject! do |possibility_set|
+ !allowed_possibility_sets.include?(possibility_set) &&
+ (requirements_to_avoid - possibility_set.dependencies).empty?
+ end
+ end
+
+ # @param [Conflict] conflict
+ # @return [Array] minimal array of requirements that would cause the passed
+ # conflict to occur.
+ def binding_requirements_for_conflict(conflict)
+ return [conflict.requirement] if conflict.possibility.nil?
+
+ possible_binding_requirements = conflict.requirements.values.flatten(1).uniq
+
+ # When there’s a `CircularDependency` error the conflicting requirement
+ # (the one causing the circular) won’t be `conflict.requirement`
+ # (which won’t be for the right state, because we won’t have created it,
+ # because it’s circular).
+ # We need to make sure we have that requirement in the conflict’s list,
+ # otherwise we won’t be able to unwind properly, so we just return all
+ # the requirements for the conflict.
+ return possible_binding_requirements if conflict.underlying_error
+
+ possibilities = search_for(conflict.requirement)
+
+ # If all the requirements together don't filter out all possibilities,
+ # then the only two requirements we need to consider are the initial one
+ # (where the dependency's version was first chosen) and the last
+ if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities)
+ return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact
+ end
+
+ # Loop through the possible binding requirements, removing each one
+ # that doesn't bind. Use a `reverse_each` as we want the earliest set of
+ # binding requirements, and don't use `reject!` as we wish to refine the
+ # array *on each iteration*.
+ binding_requirements = possible_binding_requirements.dup
+ possible_binding_requirements.reverse_each do |req|
+ next if req == conflict.requirement
+ unless binding_requirement_in_set?(req, binding_requirements, possibilities)
+ binding_requirements -= [req]
+ end
+ end
+
+ binding_requirements
+ end
+
+ # @param [Object] requirement we wish to check
+ # @param [Array] array of requirements
+ # @param [Array] array of possibilities the requirements will be used to filter
+ # @return [Boolean] whether or not the given requirement is required to filter
+ # out all elements of the array of possibilities.
+ def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities)
+ possibilities.any? do |poss|
+ possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement])
+ end
end
# @return [Object] the requirement that led to `requirement` being added
@@ -222,7 +580,8 @@ module Bundler::Molinillo
# @return [Object] the requirement that led to a version of a possibility
# with the given name being activated.
def requirement_for_existing_name(name)
- return nil unless activated.vertex_named(name).payload
+ return nil unless vertex = activated.vertex_named(name)
+ return nil unless vertex.payload
states.find { |s| s.name == name }.requirement
end
@@ -230,18 +589,12 @@ module Bundler::Molinillo
# `requirement`.
def find_state_for(requirement)
return nil unless requirement
- states.reverse_each.find { |i| requirement == i.requirement && i.is_a?(DependencyState) }
- end
-
- # @return [Boolean] whether or not the given state has any possibilities
- # left.
- def state_any?(state)
- state && state.possibilities.any?
+ states.find { |i| requirement == i.requirement }
end
# @return [Conflict] a {Conflict} that reflects the failure to activate
# the {#possibility} in conjunction with the current {#state}
- def create_conflict
+ def create_conflict(underlying_error = nil)
vertex = activated.vertex_named(name)
locked_requirement = locked_requirement_named(name)
@@ -250,18 +603,21 @@ module Bundler::Molinillo
requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements
end
requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement
- vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(edge.requirement) }
+ vertex.incoming_edges.each do |edge|
+ (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement)
+ end
activated_by_name = {}
- activated.each { |v| activated_by_name[v.name] = v.payload if v.payload }
+ activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload }
conflicts[name] = Conflict.new(
requirement,
requirements,
- vertex.payload,
+ vertex.payload && vertex.payload.latest_version,
possibility,
locked_requirement,
requirement_trees,
- activated_by_name
+ activated_by_name,
+ underlying_error
)
end
@@ -311,116 +667,48 @@ module Bundler::Molinillo
# @return [void]
def attempt_to_activate
debug(depth) { 'Attempting to activate ' + possibility.to_s }
- existing_node = activated.vertex_named(name)
- if existing_node.payload
- debug(depth) { "Found existing spec (#{existing_node.payload})" }
- attempt_to_activate_existing_spec(existing_node)
- else
- attempt_to_activate_new_spec
- end
- end
-
- # Attempts to activate the current {#possibility} (given that it has
- # already been activated)
- # @return [void]
- def attempt_to_activate_existing_spec(existing_node)
- existing_spec = existing_node.payload
- if requirement_satisfied_by?(requirement, activated, existing_spec)
- new_requirements = requirements.dup
- push_state_for_requirements(new_requirements, false)
+ existing_vertex = activated.vertex_named(name)
+ if existing_vertex.payload
+ debug(depth) { "Found existing spec (#{existing_vertex.payload})" }
+ attempt_to_filter_existing_spec(existing_vertex)
else
- return if attempt_to_swap_possibility
- create_conflict
- debug(depth) { "Unsatisfied by existing spec (#{existing_node.payload})" }
- unwind_for_conflict
- end
- end
-
- # Attempts to swp the current {#possibility} with the already-activated
- # spec with the given name
- # @return [Boolean] Whether the possibility was swapped into {#activated}
- def attempt_to_swap_possibility
- activated.tag(:swap)
- vertex = activated.vertex_named(name)
- activated.set_payload(name, possibility)
- if !vertex.requirements.
- all? { |r| requirement_satisfied_by?(r, activated, possibility) } ||
- !new_spec_satisfied?
- activated.rewind_to(:swap)
- return
- end
- fixup_swapped_children(vertex)
- activate_spec
- end
-
- # Ensures there are no orphaned successors to the given {vertex}.
- # @param [DependencyGraph::Vertex] vertex the vertex to fix up.
- # @return [void]
- def fixup_swapped_children(vertex) # rubocop:disable Metrics/CyclomaticComplexity
- payload = vertex.payload
- deps = dependencies_for(payload).group_by(&method(:name_for))
- vertex.outgoing_edges.each do |outgoing_edge|
- requirement = outgoing_edge.requirement
- parent_index = @parents_of[requirement].last
- succ = outgoing_edge.destination
- matching_deps = Array(deps[succ.name])
- dep_matched = matching_deps.include?(requirement)
-
- # only push the current index when it was originally required by the
- # same named spec
- if parent_index && states[parent_index].name == name
- @parents_of[requirement].push(states.size - 1)
+ latest = possibility.latest_version
+ # use reject!(!satisfied) for 1.8.7 compatibility
+ possibility.possibilities.reject! do |possibility|
+ !requirement_satisfied_by?(requirement, activated, possibility)
end
-
- 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| @parents_of.delete(r) }
-
- 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 !dep_matched
- debug(depth) { "Removing orphaned dependency #{requirement} after swapping #{name}" }
- # also reset if we're removing the edge, but only if its parent has
- # already been fixed up
- @parents_of[requirement].push(states.size - 1) if @parents_of[requirement].empty?
-
- activated.delete_edge(outgoing_edge)
- requirements.delete(requirement)
+ if possibility.latest_version.nil?
+ # ensure there's a possibility for better error messages
+ possibility.possibilities << latest if latest
+ create_conflict
+ unwind_for_conflict
+ else
+ activate_new_spec
end
end
end
- # Attempts to activate the current {#possibility} (given that it hasn't
- # already been activated)
+ # Attempts to update the existing vertex's `PossibilitySet` with a filtered version
# @return [void]
- def attempt_to_activate_new_spec
- if new_spec_satisfied?
- activate_spec
+ def attempt_to_filter_existing_spec(vertex)
+ filtered_set = filtered_possibility_set(vertex)
+ if !filtered_set.possibilities.empty?
+ activated.set_payload(name, filtered_set)
+ new_requirements = requirements.dup
+ push_state_for_requirements(new_requirements, false)
else
create_conflict
+ debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" }
unwind_for_conflict
end
end
- # @return [Boolean] whether the current spec is satisfied as a new
- # possibility.
- def new_spec_satisfied?
- unless requirement_satisfied_by?(requirement, activated, possibility)
- debug(depth) { 'Unsatisfied by requested spec' }
- return false
- end
-
- locked_requirement = locked_requirement_named(name)
-
- locked_spec_satisfied = !locked_requirement ||
- requirement_satisfied_by?(locked_requirement, activated, possibility)
- debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied
-
- locked_spec_satisfied
+ # Generates a filtered version of the existing vertex's `PossibilitySet` using the
+ # current state's `requirement`
+ # @param [Object] existing vertex
+ # @return [PossibilitySet] filtered possibility set
+ def filtered_possibility_set(vertex)
+ PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities)
end
# @param [String] requirement_name the spec name to search for
@@ -434,7 +722,7 @@ module Bundler::Molinillo
# Add the current {#possibility} to the dependency graph of the current
# {#state}
# @return [void]
- def activate_spec
+ def activate_new_spec
conflicts.delete(name)
debug(depth) { "Activated #{name} at #{possibility}" }
activated.set_payload(name, possibility)
@@ -442,14 +730,14 @@ module Bundler::Molinillo
end
# Requires the dependencies that the recently activated spec has
- # @param [Object] activated_spec the specification that has just been
+ # @param [Object] activated_possibility the PossibilitySet that has just been
# activated
# @return [void]
- def require_nested_dependencies_for(activated_spec)
- nested_dependencies = dependencies_for(activated_spec)
+ def require_nested_dependencies_for(possibility_set)
+ nested_dependencies = dependencies_for(possibility_set.latest_version)
debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" }
nested_dependencies.each do |d|
- activated.add_child_vertex(name_for(d), nil, [name_for(activated_spec)], d)
+ activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d)
parent_index = states.size - 1
parents = @parents_of[d]
parents << parent_index if parents.empty?
@@ -464,20 +752,75 @@ module Bundler::Molinillo
# @return [void]
def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated)
new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort
- new_requirement = new_requirements.shift
+ new_requirement = nil
+ loop do
+ new_requirement = new_requirements.shift
+ break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement }
+ end
new_name = new_requirement ? name_for(new_requirement) : ''.freeze
- possibilities = new_requirement ? search_for(new_requirement) : []
+ possibilities = possibilities_for_requirement(new_requirement)
handle_missing_or_push_dependency_state DependencyState.new(
new_name, new_requirements, new_activated,
- new_requirement, possibilities, depth, conflicts.dup
+ new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup
)
end
+ # Checks a proposed requirement with any existing locked requirement
+ # before generating an array of possibilities for it.
+ # @param [Object] the proposed requirement
+ # @return [Array] possibilities
+ def possibilities_for_requirement(requirement, activated = self.activated)
+ return [] unless requirement
+ if locked_requirement_named(name_for(requirement))
+ return locked_requirement_possibility_set(requirement, activated)
+ end
+
+ group_possibilities(search_for(requirement))
+ end
+
+ # @param [Object] the proposed requirement
+ # @return [Array] possibility set containing only the locked requirement, if any
+ def locked_requirement_possibility_set(requirement, activated = self.activated)
+ all_possibilities = search_for(requirement)
+ locked_requirement = locked_requirement_named(name_for(requirement))
+
+ # Longwinded way to build a possibilities array with either the locked
+ # requirement or nothing in it. Required, since the API for
+ # locked_requirement isn't guaranteed.
+ locked_possibilities = all_possibilities.select do |possibility|
+ requirement_satisfied_by?(locked_requirement, activated, possibility)
+ end
+
+ group_possibilities(locked_possibilities)
+ end
+
+ # Build an array of PossibilitySets, with each element representing a group of
+ # dependency versions that all have the same sub-dependency version constraints
+ # and are contiguous.
+ # @param [Array] an array of possibilities
+ # @return [Array] an array of possibility sets
+ def group_possibilities(possibilities)
+ possibility_sets = []
+ current_possibility_set = nil
+
+ possibilities.reverse_each do |possibility|
+ dependencies = dependencies_for(possibility)
+ if current_possibility_set && current_possibility_set.dependencies == dependencies
+ current_possibility_set.possibilities.unshift(possibility)
+ else
+ possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility]))
+ current_possibility_set = possibility_sets.first
+ end
+ end
+
+ possibility_sets
+ end
+
# Pushes a new {DependencyState}.
# If the {#specification_provider} says to
# {SpecificationProvider#allow_missing?} that particular requirement, and
# there are no possibilities for that requirement, then `state` is not
- # pushed, and the node in {#activated} is removed, and we continue
+ # pushed, and the vertex in {#activated} is removed, and we continue
# resolving the remaining requirements.
# @param [DependencyState] state
# @return [void]
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb b/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb
index 50d853b146..7d36858778 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph'
module Bundler::Molinillo
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/state.rb b/lib/bundler/vendor/molinillo/lib/molinillo/state.rb
index 3a8107cf1a..68fa1f54e3 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/state.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/state.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Bundler::Molinillo
# A state that a {Resolution} can be in
# @attr [String] name the name of the current requirement
@@ -7,7 +8,8 @@ module Bundler::Molinillo
# @attr [Object] requirement the current requirement
# @attr [Object] possibilities the possibilities to satisfy the current requirement
# @attr [Integer] depth the depth of the resolution
- # @attr [Set<Object>] conflicts unresolved conflicts
+ # @attr [Hash] conflicts unresolved conflicts, indexed by dependency name
+ # @attr [Array<UnwindDetails>] unused_unwind_options unwinds for previous conflicts that weren't explored
ResolutionState = Struct.new(
:name,
:requirements,
@@ -15,14 +17,15 @@ module Bundler::Molinillo
:requirement,
:possibilities,
:depth,
- :conflicts
+ :conflicts,
+ :unused_unwind_options
)
class ResolutionState
# Returns an empty resolution state
# @return [ResolutionState] an empty state
def self.empty
- new(nil, [], DependencyGraph.new, nil, nil, 0, Set.new)
+ new(nil, [], DependencyGraph.new, nil, nil, 0, {}, [])
end
end
@@ -40,7 +43,8 @@ module Bundler::Molinillo
requirement,
[possibilities.pop],
depth + 1,
- conflicts.dup
+ conflicts.dup,
+ unused_unwind_options.dup
).tap do |state|
state.activated.tag(state)
end
diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
index c872a79c13..7cbca5bc06 100644
--- a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
+++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
@@ -814,7 +814,7 @@ class Bundler::Persistent::Net::HTTP::Persistent
##
# Pipelines +requests+ to the HTTP server at +uri+ yielding responses if a
- # block is given. Returns all responses recieved.
+ # block is given. Returns all responses received.
#
# See
# Net::HTTP::Pipeline[http://docs.seattlerb.org/net-http-pipeline/Net/HTTP/Pipeline.html]
diff --git a/lib/bundler/vendor/thor/lib/thor/runner.rb b/lib/bundler/vendor/thor/lib/thor/runner.rb
index 65ae422d7f..b110b8d478 100644
--- a/lib/bundler/vendor/thor/lib/thor/runner.rb
+++ b/lib/bundler/vendor/thor/lib/thor/runner.rb
@@ -3,7 +3,7 @@ require "bundler/vendor/thor/lib/thor/group"
require "bundler/vendor/thor/lib/thor/core_ext/io_binary_read"
require "yaml"
-require "digest/md5"
+require "digest"
require "pathname"
class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLength
@@ -90,7 +90,7 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng
end
thor_yaml[as] = {
- :filename => Digest::MD5.hexdigest(name + as),
+ :filename => Digest(:MD5).hexdigest(name + as),
:location => location,
:namespaces => Bundler::Thor::Util.namespaces_in_content(contents, base)
}