1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/lib/bundler/rubygems_gem_installer.rb
David Rodríguez 7f9eb888a3 [rubygems/rubygems] Reuse package from the installer for extracting the specification
Previously we would instantiate two different packages and extract the
specification from the package twice for each gem installed. We can
reuse the installer for this so that we just need to do it once.

https://github.com/rubygems/rubygems/commit/e454f850b1
2022-06-11 18:43:28 +09:00

163 lines
5.1 KiB
Ruby

# frozen_string_literal: true
require "rubygems/installer"
module Bundler
class RubyGemsGemInstaller < Gem::Installer
def check_executable_overwrite(filename)
# Bundler needs to install gems regardless of binstub overwriting
end
def install
pre_install_checks
run_pre_install_hooks
spec.loaded_from = spec_file
# Completely remove any previous gem files
strict_rm_rf gem_dir
strict_rm_rf spec.extension_dir
SharedHelpers.filesystem_access(gem_dir, :create) do
FileUtils.mkdir_p gem_dir, :mode => 0o755
end
extract_files
build_extensions
write_build_info_file
run_post_build_hooks
generate_bin
generate_plugins
write_spec
SharedHelpers.filesystem_access("#{gem_home}/cache", :write) do
write_cache_file
end
say spec.post_install_message unless spec.post_install_message.nil?
run_post_install_hooks
spec
end
def generate_plugins
return unless Gem::Installer.instance_methods(false).include?(:generate_plugins)
latest = Gem::Specification.stubs_for(spec.name).first
return if latest && latest.version > spec.version
ensure_writable_dir @plugins_dir
if spec.plugins.empty?
remove_plugins_for(spec, @plugins_dir)
else
regenerate_plugins_for(spec, @plugins_dir)
end
end
def pre_install_checks
super && validate_bundler_checksum(options[:bundler_expected_checksum])
end
def build_extensions
extension_cache_path = options[:bundler_extension_cache_path]
unless extension_cache_path && extension_dir = spec.extension_dir
require "shellwords" unless Bundler.rubygems.provides?(">= 3.2.25")
return super
end
extension_dir = Pathname.new(extension_dir)
build_complete = SharedHelpers.filesystem_access(extension_cache_path.join("gem.build_complete"), :read, &:file?)
if build_complete && !options[:force]
SharedHelpers.filesystem_access(extension_dir.parent, &:mkpath)
SharedHelpers.filesystem_access(extension_cache_path) do
FileUtils.cp_r extension_cache_path, spec.extension_dir
end
else
require "shellwords" # compensate missing require in rubygems before version 3.2.25
super
if extension_dir.directory? # not made for gems without extensions
SharedHelpers.filesystem_access(extension_cache_path.parent, &:mkpath)
SharedHelpers.filesystem_access(extension_cache_path) do
FileUtils.cp_r extension_dir, extension_cache_path
end
end
end
end
def spec
if Bundler.rubygems.provides?("< 3.3.12") # RubyGems implementation rescues and re-raises errors before 3.3.12 and we don't want that
@package.spec
else
super
end
end
private
def strict_rm_rf(dir)
Bundler.rm_rf dir
rescue Errno::ENOTEMPTY => e
raise DirectoryRemovalError.new(e.cause, "Could not delete previous installation of `#{dir}`")
end
def validate_bundler_checksum(checksum)
return true if Bundler.settings[:disable_checksum_validation]
return true unless checksum
return true unless source = @package.instance_variable_get(:@gem)
return true unless source.respond_to?(:with_read_io)
digest = source.with_read_io do |io|
digest = SharedHelpers.digest(:SHA256).new
digest << io.read(16_384) until io.eof?
io.rewind
send(checksum_type(checksum), digest)
end
unless digest == checksum
raise SecurityError, <<-MESSAGE
Bundler cannot continue installing #{spec.name} (#{spec.version}).
The checksum for the downloaded `#{spec.full_name}.gem` does not match \
the checksum given by the server. This means the contents of the downloaded \
gem is different from what was uploaded to the server, and could be a potential security issue.
To resolve this issue:
1. delete the downloaded gem located at: `#{spec.gem_dir}/#{spec.full_name}.gem`
2. run `bundle install`
If you wish to continue installing the downloaded gem, and are certain it does not pose a \
security issue despite the mismatching checksum, do the following:
1. run `bundle config set --local disable_checksum_validation true` to turn off checksum verification
2. run `bundle install`
(More info: The expected SHA256 checksum was #{checksum.inspect}, but the \
checksum for the downloaded gem was #{digest.inspect}.)
MESSAGE
end
true
end
def checksum_type(checksum)
case checksum.length
when 64 then :hexdigest!
when 44 then :base64digest!
else raise InstallError, "The given checksum for #{spec.full_name} (#{checksum.inspect}) is not a valid SHA256 hexdigest nor base64digest"
end
end
def hexdigest!(digest)
digest.hexdigest!
end
def base64digest!(digest)
if digest.respond_to?(:base64digest!)
digest.base64digest!
else
[digest.digest!].pack("m0")
end
end
end
end