diff options
author | Samuel Giddins <segiddins@segiddins.me> | 2017-04-25 17:07:45 +0200 |
---|---|---|
committer | Samuel Giddins <segiddins@segiddins.me> | 2017-06-28 13:48:45 +0200 |
commit | 699d5184c1d500b60a25819977f1b286a84b3337 (patch) | |
tree | dca8d2e74e8b9e2d6a97f8e75968b7d08bd69c15 | |
parent | 34234e5e67425fa5d1e857d436846175b6a3b3df (diff) | |
download | bundler-699d5184c1d500b60a25819977f1b286a84b3337.tar.gz |
Vendor fileutils since it is now a gem
-rw-r--r-- | Rakefile | 8 | ||||
-rw-r--r-- | lib/bundler.rb | 5 | ||||
-rw-r--r-- | lib/bundler/compact_index_client/updater.rb | 4 | ||||
-rw-r--r-- | lib/bundler/source/git.rb | 2 | ||||
-rw-r--r-- | lib/bundler/ssl_certs/certificate_manager.rb | 2 | ||||
-rw-r--r-- | lib/bundler/vendor/fileutils/lib/fileutils.rb | 1638 | ||||
-rw-r--r-- | lib/bundler/vendored_fileutils.rb | 3 | ||||
-rw-r--r-- | spec/commands/pristine_spec.rb | 2 | ||||
-rw-r--r-- | spec/plugins/source/example_spec.rb | 2 | ||||
-rw-r--r-- | spec/spec_helper.rb | 2 |
10 files changed, 1659 insertions, 9 deletions
@@ -340,6 +340,13 @@ begin lib.vendor_lib = "lib/bundler/vendor/thor" end + Automatiek::RakeTask.new("fileutils") do |lib| + lib.download = { :github => "https://github.com/ruby/fileutils" } + lib.namespace = "FileUtils" + lib.prefix = "Bundler" + lib.vendor_lib = "lib/bundler/vendor/fileutils" + end + Automatiek::RakeTask.new("net-http-persistent") do |lib| lib.download = { :github => "https://github.com/drbrain/net-http-persistent" } lib.namespace = "Net::HTTP::Persistent" @@ -358,6 +365,7 @@ begin end rescue LoadError namespace :vendor do + task(:fileutils) { abort "Install the automatiek gem to be able to vendor gems." } task(:molinillo) { abort "Install the automatiek gem to be able to vendor gems." } task(:thor) { abort "Install the automatiek gem to be able to vendor gems." } task("net-http-persistent") { abort "Install the automatiek gem to be able to vendor gems." } diff --git a/lib/bundler.rb b/lib/bundler.rb index c9d8846ce5..4516379411 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -require "fileutils" +require "bundler/vendored_fileutils" require "pathname" require "rbconfig" require "thread" -require "tmpdir" require "bundler/errors" require "bundler/environment_preserver" @@ -169,6 +168,7 @@ module Bundler def tmp_home_path(login, warning) login ||= "unknown" + require "tmpdir" path = Pathname.new(Dir.tmpdir).join("bundler", "home") SharedHelpers.filesystem_access(path) do |tmp_home_path| unless tmp_home_path.exist? @@ -229,6 +229,7 @@ module Bundler end def tmp(name = Process.pid.to_s) + require "tmpdir" Pathname.new(Dir.mktmpdir(["bundler", name])) end diff --git a/lib/bundler/compact_index_client/updater.rb b/lib/bundler/compact_index_client/updater.rb index 5aa480fdcc..adf013de04 100644 --- a/lib/bundler/compact_index_client/updater.rb +++ b/lib/bundler/compact_index_client/updater.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require "fileutils" +require "bundler/vendored_fileutils" require "stringio" -require "tmpdir" require "zlib" module Bundler @@ -22,6 +21,7 @@ module Bundler def initialize(fetcher) @fetcher = fetcher + require "tmpdir" end def update(local_path, remote_path, retrying = nil) diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index dc204a2b33..c331af967e 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require "fileutils" +require "bundler/vendored_fileutils" require "uri" require "digest/sha1" diff --git a/lib/bundler/ssl_certs/certificate_manager.rb b/lib/bundler/ssl_certs/certificate_manager.rb index a5e5d84b64..05de6410be 100644 --- a/lib/bundler/ssl_certs/certificate_manager.rb +++ b/lib/bundler/ssl_certs/certificate_manager.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require "fileutils" +require "bundler/vendored_fileutils" require "net/https" require "openssl" 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/vendored_fileutils.rb b/lib/bundler/vendored_fileutils.rb new file mode 100644 index 0000000000..aab232f097 --- /dev/null +++ b/lib/bundler/vendored_fileutils.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true +module Bundler; end +require "bundler/vendor/fileutils/lib/fileutils" diff --git a/spec/commands/pristine_spec.rb b/spec/commands/pristine_spec.rb index 9c3918f51e..e4e8df204f 100644 --- a/spec/commands/pristine_spec.rb +++ b/spec/commands/pristine_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require "fileutils" +require "bundler/vendored_fileutils" RSpec.describe "bundle pristine" do before :each do diff --git a/spec/plugins/source/example_spec.rb b/spec/plugins/source/example_spec.rb index 748f083456..d956a0571b 100644 --- a/spec/plugins/source/example_spec.rb +++ b/spec/plugins/source/example_spec.rb @@ -6,7 +6,7 @@ RSpec.describe "real source plugins" do build_repo2 do build_plugin "bundler-source-mpath" do |s| s.write "plugins.rb", <<-RUBY - require "fileutils" + require "bundler/vendored_fileutils" require "bundler-source-mpath" class MPath < Bundler::Plugin::API diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ee7777a4bd..5c7fcbffa5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,7 +3,7 @@ $:.unshift File.expand_path("..", __FILE__) $:.unshift File.expand_path("../../lib", __FILE__) require "bundler/psyched_yaml" -require "fileutils" +require "bundler/vendored_fileutils" require "uri" require "digest/sha1" |