diff --git a/Manifest.txt b/Manifest.txt index 9c244e6ca05c..5641d2fe4258 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -229,6 +229,7 @@ bundler/lib/bundler/vendor/thor/lib/thor/shell/color.rb bundler/lib/bundler/vendor/thor/lib/thor/shell/html.rb bundler/lib/bundler/vendor/thor/lib/thor/util.rb bundler/lib/bundler/vendor/thor/lib/thor/version.rb +bundler/lib/bundler/vendor/tmpdir/lib/tmpdir.rb bundler/lib/bundler/vendor/uri/lib/uri.rb bundler/lib/bundler/vendor/uri/lib/uri/common.rb bundler/lib/bundler/vendor/uri/lib/uri/file.rb @@ -246,6 +247,7 @@ bundler/lib/bundler/vendored_fileutils.rb bundler/lib/bundler/vendored_molinillo.rb bundler/lib/bundler/vendored_persistent.rb bundler/lib/bundler/vendored_thor.rb +bundler/lib/bundler/vendored_tmpdir.rb bundler/lib/bundler/vendored_uri.rb bundler/lib/bundler/version.rb bundler/lib/bundler/version_ranges.rb diff --git a/bundler/Rakefile b/bundler/Rakefile index e40f8f1965f7..ffe3aaa208cf 100644 --- a/bundler/Rakefile +++ b/bundler/Rakefile @@ -175,13 +175,30 @@ Automatiek::RakeTask.new("thor") do |lib| lib.vendor_lib = "lib/bundler/vendor/thor" end -desc "Vendor a specific version of fileutils" -Automatiek::RakeTask.new("fileutils") do |lib| - lib.version = "v1.4.1" - lib.download = { :github => "https://github.com/ruby/fileutils" } - lib.namespace = "FileUtils" +# We currently include the official version as of +# https://github.com/ruby/tmpdir/tree/a3e06bd49829dc49c346aa10f8b91116fd7e17ad, +# with the following changes on top: +# * require fileutils relatively to use our vendored version. +# * Inherit from `Dir` so that code assuming we're inside the +# `Dir` class still works. Also change the `systmpdir` class variable to an +# instance variable since otherwise inheriting from dir doesn't work. +# * Remove a "block variable shadowing outer variable" warning on older rubies +# that was breaking some specs. +desc "Vendor a specific version of tmpdir" +Automatiek::RakeTask.new("tmpdir") do |lib| + lib.version = "master" + lib.download = { :github => "https://github.com/ruby/tmpdir" } + lib.namespace = "Dir" lib.prefix = "Bundler" - lib.vendor_lib = "lib/bundler/vendor/fileutils" + lib.vendor_lib = "lib/bundler/vendor/tmpdir" + + lib.dependency("fileutils") do |sublib| + sublib.version = "v1.4.1" + sublib.download = { :github => "https://github.com/ruby/fileutils" } + sublib.namespace = "FileUtils" + sublib.prefix = "Bundler" + sublib.vendor_lib = "lib/bundler/vendor/fileutils" + end end # We currently include the following changes over the official version: diff --git a/bundler/lib/bundler/compact_index_client/updater.rb b/bundler/lib/bundler/compact_index_client/updater.rb index 24266f2fe65d..66d1735583b0 100644 --- a/bundler/lib/bundler/compact_index_client/updater.rb +++ b/bundler/lib/bundler/compact_index_client/updater.rb @@ -22,13 +22,13 @@ def message def initialize(fetcher) @fetcher = fetcher - require "tmpdir" + require_relative "../vendored_tmpdir" end def update(local_path, remote_path, retrying = nil) headers = {} - Dir.mktmpdir("bundler-compact-index-") do |local_temp_dir| + Bundler::Dir.mktmpdir("bundler-compact-index-") do |local_temp_dir| local_temp_path = Pathname.new(local_temp_dir).join(local_path.basename) # first try to fetch any new bytes on the existing file diff --git a/bundler/lib/bundler/vendor/tmpdir/lib/tmpdir.rb b/bundler/lib/bundler/vendor/tmpdir/lib/tmpdir.rb new file mode 100644 index 000000000000..a00496687cc1 --- /dev/null +++ b/bundler/lib/bundler/vendor/tmpdir/lib/tmpdir.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true +# +# tmpdir - retrieve temporary directory path +# +# $Id$ +# + +require_relative '../../fileutils/lib/fileutils' +begin + require 'etc.so' +rescue LoadError # rescue LoadError for miniruby +end + +class Bundler::Dir < Dir + + @systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp' + + ## + # Returns the operating system's temporary file path. + + def self.tmpdir + tmp = nil + ['TMPDIR', 'TMP', 'TEMP', ['system temporary path', @systmpdir], ['/tmp']*2, ['.']*2].each do |name, dir = ENV[name]| + next if !dir + dir = File.expand_path(dir) + stat = File.stat(dir) rescue next + case + when !stat.directory? + warn "#{name} is not a directory: #{dir}" + when !stat.writable? + warn "#{name} is not writable: #{dir}" + when stat.world_writable? && !stat.sticky? + warn "#{name} is world-writable: #{dir}" + else + tmp = dir + break + end + end + raise ArgumentError, "could not find a temporary directory" unless tmp + tmp + end + + # Bundler::Dir.mktmpdir creates a temporary directory. + # + # The directory is created with 0700 permission. + # Application should not change the permission to make the temporary directory accessible from other users. + # + # The prefix and suffix of the name of the directory is specified by + # the optional first argument, prefix_suffix. + # - If it is not specified or nil, "d" is used as the prefix and no suffix is used. + # - If it is a string, it is used as the prefix and no suffix is used. + # - If it is an array, first element is used as the prefix and second element is used as a suffix. + # + # Bundler::Dir.mktmpdir {|dir| dir is ".../d..." } + # Bundler::Dir.mktmpdir("foo") {|dir| dir is ".../foo..." } + # Bundler::Dir.mktmpdir(["foo", "bar"]) {|dir| dir is ".../foo...bar" } + # + # The directory is created under Bundler::Dir.tmpdir or + # the optional second argument tmpdir if non-nil value is given. + # + # Bundler::Dir.mktmpdir {|dir| dir is "#{Bundler::Dir.tmpdir}/d..." } + # Bundler::Dir.mktmpdir(nil, "/var/tmp") {|dir| dir is "/var/tmp/d..." } + # + # If a block is given, + # it is yielded with the path of the directory. + # The directory and its contents are removed + # using Bundler::FileUtils.remove_entry before Bundler::Dir.mktmpdir returns. + # The value of the block is returned. + # + # Bundler::Dir.mktmpdir {|dir| + # # use the directory... + # open("#{dir}/foo", "w") { ... } + # } + # + # If a block is not given, + # The path of the directory is returned. + # In this case, Bundler::Dir.mktmpdir doesn't remove the directory. + # + # dir = Bundler::Dir.mktmpdir + # begin + # # use the directory... + # open("#{dir}/foo", "w") { ... } + # ensure + # # remove the directory. + # Bundler::FileUtils.remove_entry dir + # end + # + def self.mktmpdir(prefix_suffix=nil, *rest, **options) + base = nil + path = Tmpname.create(prefix_suffix || "d", *rest, **options) {|p, _, _, d| + base = d + mkdir(p, 0700) + } + if block_given? + begin + yield path.dup + ensure + unless base + stat = File.stat(File.dirname(path)) + if stat.world_writable? and !stat.sticky? + raise ArgumentError, "parent directory is world writable but not sticky" + end + end + Bundler::FileUtils.remove_entry path + end + else + path + end + end + + module Tmpname # :nodoc: + module_function + + def tmpdir + Bundler::Dir.tmpdir + end + + UNUSABLE_CHARS = [File::SEPARATOR, File::ALT_SEPARATOR, File::PATH_SEPARATOR, ":"].uniq.join("").freeze + + class << (RANDOM = Random.new) + MAX = 36**6 # < 0x100000000 + def next + rand(MAX).to_s(36) + end + end + private_constant :RANDOM + + def create(basename, tmpdir=nil, max_try: nil, **opts) + origdir = tmpdir + tmpdir ||= tmpdir() + n = nil + prefix, suffix = basename + prefix = (String.try_convert(prefix) or + raise ArgumentError, "unexpected prefix: #{prefix.inspect}") + prefix = prefix.delete(UNUSABLE_CHARS) + suffix &&= (String.try_convert(suffix) or + raise ArgumentError, "unexpected suffix: #{suffix.inspect}") + suffix &&= suffix.delete(UNUSABLE_CHARS) + begin + t = Time.now.strftime("%Y%m%d") + path = "#{prefix}#{t}-#{$$}-#{RANDOM.next}"\ + "#{n ? %[-#{n}] : ''}#{suffix||''}" + path = File.join(tmpdir, path) + yield(path, n, opts, origdir) + rescue Errno::EEXIST + n ||= 0 + n += 1 + retry if !max_try or n < max_try + raise "cannot generate temporary name using `#{basename}' under `#{tmpdir}'" + end + path + end + end +end diff --git a/bundler/lib/bundler/vendored_tmpdir.rb b/bundler/lib/bundler/vendored_tmpdir.rb new file mode 100644 index 000000000000..43b4fa75fe07 --- /dev/null +++ b/bundler/lib/bundler/vendored_tmpdir.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +module Bundler; end +require_relative "vendor/tmpdir/lib/tmpdir" diff --git a/bundler/spec/bundler/compact_index_client/updater_spec.rb b/bundler/spec/bundler/compact_index_client/updater_spec.rb index 5ba180a5748c..acb312edb3ac 100644 --- a/bundler/spec/bundler/compact_index_client/updater_spec.rb +++ b/bundler/spec/bundler/compact_index_client/updater_spec.rb @@ -3,6 +3,7 @@ require "net/http" require "bundler/compact_index_client" require "bundler/compact_index_client/updater" +require "tmpdir" RSpec.describe Bundler::CompactIndexClient::Updater do let(:fetcher) { double(:fetcher) } @@ -40,7 +41,7 @@ context "when bundler doesn't have permissions on Dir.tmpdir" do it "Errno::EACCES is raised" do local_path # create local path before stubbing mktmpdir - allow(Dir).to receive(:mktmpdir) { raise Errno::EACCES } + allow(Bundler::Dir).to receive(:mktmpdir) { raise Errno::EACCES } expect do updater.update(local_path, remote_path) diff --git a/bundler/spec/install/gemfile/path_spec.rb b/bundler/spec/install/gemfile/path_spec.rb index 07722d09dd8d..f19fe3972134 100644 --- a/bundler/spec/install/gemfile/path_spec.rb +++ b/bundler/spec/install/gemfile/path_spec.rb @@ -718,11 +718,11 @@ expect(bar_file).not_to be_file build_lib "foo" do |s| - s.write("lib/rubygems_plugin.rb", "FileUtils.touch('#{foo_file}')") + s.write("lib/rubygems_plugin.rb", "require 'fileutils'; FileUtils.touch('#{foo_file}')") end build_git "bar" do |s| - s.write("lib/rubygems_plugin.rb", "FileUtils.touch('#{bar_file}')") + s.write("lib/rubygems_plugin.rb", "require 'fileutils'; FileUtils.touch('#{bar_file}')") end install_gemfile <<-G diff --git a/bundler/spec/plugins/source/example_spec.rb b/bundler/spec/plugins/source/example_spec.rb index 60ed051ec381..fa3ca48cb0e6 100644 --- a/bundler/spec/plugins/source/example_spec.rb +++ b/bundler/spec/plugins/source/example_spec.rb @@ -33,6 +33,7 @@ def fetch_gemspec_files def install(spec, opts) mkdir_p(install_path.parent) + require 'fileutils' FileUtils.cp_r(path, install_path) spec_path = install_path.join("\#{spec.full_name}.gemspec") diff --git a/bundler/spec/runtime/inline_spec.rb b/bundler/spec/runtime/inline_spec.rb index 1ba50c0e9776..fadf3eb98910 100644 --- a/bundler/spec/runtime/inline_spec.rb +++ b/bundler/spec/runtime/inline_spec.rb @@ -369,4 +369,34 @@ def confirm(msg, newline = nil) expect(out).to eq("WIN") expect(err).to be_empty end + + it "when requiring fileutils after does not show redefinition warnings" do + dependency_installer_loads_fileutils = ruby "require 'rubygems/dependency_installer'; puts $LOADED_FEATURES.grep(/fileutils/)", :raise_on_error => false + skip "does not work if rubygems/dependency_installer loads fileutils, which happens until rubygems 3.2.0" unless dependency_installer_loads_fileutils.empty? + + skip "does not work on ruby 3.0 because it changes the path to look for default gems, tsort is a default gem there, and we can't install it either like we do with fiddle because it doesn't yet exist" unless RUBY_VERSION < "3.0.0" + + Dir.mkdir tmp("path_without_gemfile") + + default_fileutils_version = ruby "gem 'fileutils', '< 999999'; require 'fileutils'; puts FileUtils::VERSION", :raise_on_error => false + skip "fileutils isn't a default gem" if default_fileutils_version.empty? + + realworld_system_gems "fileutils --version 1.4.1" + + realworld_system_gems "fiddle" # not sure why, but this is needed on Windows to boot rubygems succesfully + + realworld_system_gems "timeout uri" # this spec uses net/http which requires these default gems + + script <<-RUBY, :dir => tmp("path_without_gemfile"), :env => { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } + require "bundler/inline" + + gemfile(true) do + source "#{file_uri_for(gem_repo2)}" + end + + require "fileutils" + RUBY + + expect(err).to eq("The Gemfile specifies no dependencies") + end end diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index 59bba9f385c6..30a01ca2db33 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -201,6 +201,7 @@ def build_extensions dest_path = @spec.extension_dir + require "fileutils" FileUtils.rm_f @spec.gem_build_complete_path @spec.extensions.each do |extension| diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 5dd17bfc2dbd..44f2c9146c88 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -12,7 +12,6 @@ require 'rubygems/package' require 'rubygems/ext' require 'rubygems/user_interaction' -require 'fileutils' ## # The installer installs the files contained in the .gem into the Gem.home. @@ -492,7 +491,11 @@ def generate_bin # :nodoc: mode = File.stat(bin_path).mode dir_mode = options[:prog_mode] || (mode | 0111) - FileUtils.chmod dir_mode, bin_path unless dir_mode == mode + + unless dir_mode == mode + require 'fileutils' + FileUtils.chmod dir_mode, bin_path + end check_executable_overwrite filename diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb index bd6d6ff8b9fc..3f12b1121f88 100644 --- a/lib/rubygems/security.rb +++ b/lib/rubygems/security.rb @@ -6,7 +6,6 @@ #++ require 'rubygems/exceptions' -require 'fileutils' require_relative 'openssl' ##