Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix fileutils double load when using bundler/inline #3991

Merged
merged 1 commit into from Oct 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions Manifest.txt
Expand Up @@ -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
Expand All @@ -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
Expand Down
29 changes: 23 additions & 6 deletions bundler/Rakefile
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions bundler/lib/bundler/compact_index_client/updater.rb
Expand Up @@ -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
Expand Down
154 changes: 154 additions & 0 deletions 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, <i>prefix_suffix</i>.
# - 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 <i>tmpdir</i> 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
4 changes: 4 additions & 0 deletions bundler/lib/bundler/vendored_tmpdir.rb
@@ -0,0 +1,4 @@
# frozen_string_literal: true

module Bundler; end
require_relative "vendor/tmpdir/lib/tmpdir"
3 changes: 2 additions & 1 deletion bundler/spec/bundler/compact_index_client/updater_spec.rb
Expand Up @@ -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) }
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions bundler/spec/install/gemfile/path_spec.rb
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions bundler/spec/plugins/source/example_spec.rb
Expand Up @@ -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")
Expand Down
30 changes: 30 additions & 0 deletions bundler/spec/runtime/inline_spec.rb
Expand Up @@ -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
1 change: 1 addition & 0 deletions lib/rubygems/ext/builder.rb
Expand Up @@ -181,6 +181,7 @@ def build_extensions

dest_path = @spec.extension_dir

require "fileutils"
FileUtils.rm_f @spec.gem_build_complete_path

@spec.extensions.each do |extension|
Expand Down
7 changes: 5 additions & 2 deletions lib/rubygems/installer.rb
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down
1 change: 0 additions & 1 deletion lib/rubygems/security.rb
Expand Up @@ -6,7 +6,6 @@
#++

require 'rubygems/exceptions'
require 'fileutils'
require_relative 'openssl'

##
Expand Down