diff options
author | Hiroshi SHIBATA <hsbt@ruby-lang.org> | 2021-11-11 11:00:30 +0900 |
---|---|---|
committer | nagachika <nagachika@ruby-lang.org> | 2021-11-22 10:51:35 +0900 |
commit | e27381d289cbdbdca434bcc957c2cd1beab1c82c (patch) | |
tree | c50fe7f308ee10afc2e797b2f4760acc49ea64fc | |
parent | e262272b6a50c1a92cdcfee684e82f9242ef8171 (diff) |
Merge RubyGems 3.2.30 and Bundler 2.2.30
73 files changed, 1147 insertions, 807 deletions
diff --git a/lib/bundler.rb b/lib/bundler.rb index a7676cd497..1ab87ac299 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -37,12 +37,13 @@ module Bundler environment_preserver = EnvironmentPreserver.from_env ORIGINAL_ENV = environment_preserver.restore environment_preserver.replace_with_backup - SUDO_MUTEX = Mutex.new + SUDO_MUTEX = Thread::Mutex.new autoload :Definition, File.expand_path("bundler/definition", __dir__) autoload :Dependency, File.expand_path("bundler/dependency", __dir__) autoload :DepProxy, File.expand_path("bundler/dep_proxy", __dir__) autoload :Deprecate, File.expand_path("bundler/deprecate", __dir__) + autoload :Digest, File.expand_path("bundler/digest", __dir__) autoload :Dsl, File.expand_path("bundler/dsl", __dir__) autoload :EndpointSpecification, File.expand_path("bundler/endpoint_specification", __dir__) autoload :Env, File.expand_path("bundler/env", __dir__) diff --git a/lib/bundler/cli/info.rb b/lib/bundler/cli/info.rb index 3111b64a33..3afed89ceb 100644 --- a/lib/bundler/cli/info.rb +++ b/lib/bundler/cli/info.rb @@ -40,12 +40,13 @@ module Bundler end def print_gem_path(spec) - if spec.name == "bundler" + name = spec.name + if name == "bundler" path = File.expand_path("../../../..", __FILE__) else path = spec.full_gem_path - unless File.directory?(path) - return Bundler.ui.warn "The gem #{gem_name} has been deleted. It was installed at: #{path}" + if spec.deleted_gem? + return Bundler.ui.warn "The gem #{name} has been deleted. It was installed at: #{path}" end end @@ -54,8 +55,9 @@ module Bundler def print_gem_info(spec) metadata = spec.metadata + name = spec.name gem_info = String.new - gem_info << " * #{spec.name} (#{spec.version}#{spec.git_version})\n" + gem_info << " * #{name} (#{spec.version}#{spec.git_version})\n" gem_info << "\tSummary: #{spec.summary}\n" if spec.summary gem_info << "\tHomepage: #{spec.homepage}\n" if spec.homepage gem_info << "\tDocumentation: #{metadata["documentation_uri"]}\n" if metadata.key?("documentation_uri") @@ -67,6 +69,11 @@ module Bundler gem_info << "\tMailing List: #{metadata["mailing_list_uri"]}\n" if metadata.key?("mailing_list_uri") gem_info << "\tPath: #{spec.full_gem_path}\n" gem_info << "\tDefault Gem: yes" if spec.respond_to?(:default_gem?) && spec.default_gem? + + if spec.deleted_gem? + return Bundler.ui.warn "The gem #{name} has been deleted. Gemspec information is still available though:\n#{gem_info}" + end + Bundler.ui.info gem_info end end diff --git a/lib/bundler/cli/issue.rb b/lib/bundler/cli/issue.rb index f4cd5ac4df..b891ecb1d2 100644 --- a/lib/bundler/cli/issue.rb +++ b/lib/bundler/cli/issue.rb @@ -20,9 +20,10 @@ module Bundler Hopefully the troubleshooting steps above resolved your problem! If things still aren't working the way you expect them to, please let us know so - that we can diagnose and help fix the problem you're having. Please - view the Filing Issues guide for more information: - https://github.com/rubygems/rubygems/blob/master/bundler/doc/contributing/ISSUES.md + that we can diagnose and help fix the problem you're having, by filling + in the new issue form located at + https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md, + and copy and pasting the information below. EOS diff --git a/lib/bundler/compact_index_client.rb b/lib/bundler/compact_index_client.rb index cf67f0e7a0..d5dbeb3b10 100644 --- a/lib/bundler/compact_index_client.rb +++ b/lib/bundler/compact_index_client.rb @@ -5,7 +5,7 @@ require "set" module Bundler class CompactIndexClient - DEBUG_MUTEX = Mutex.new + DEBUG_MUTEX = Thread::Mutex.new def self.debug return unless ENV["DEBUG_COMPACT_INDEX"] DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") } @@ -25,7 +25,7 @@ module Bundler @endpoints = Set.new @info_checksums_by_name = {} @parsed_checksums = false - @mutex = Mutex.new + @mutex = Thread::Mutex.new end def execution_mode=(block) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 4a27c438f5..9736d6551b 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -73,7 +73,6 @@ module Bundler @lockfile_contents = String.new @locked_bundler_version = nil @locked_ruby_version = nil - @locked_specs_incomplete_for_platform = false @new_platform = nil if lockfile && File.exist?(lockfile) @@ -139,6 +138,8 @@ module Bundler @dependency_changes = converge_dependencies @local_changes = converge_locals + @locked_specs_incomplete_for_platform = !@locked_specs.for(expand_dependencies(requested_dependencies & locked_dependencies), true, true) + @requires = compute_requires end @@ -228,6 +229,10 @@ module Bundler end end + def locked_dependencies + @locked_deps.values + end + def specs_for(groups) groups = requested_groups if groups.empty? deps = dependencies_for(groups) @@ -367,8 +372,8 @@ module Bundler new_sources = gemfile_sources - @locked_sources deleted_sources = @locked_sources - gemfile_sources - new_deps = @dependencies - @locked_deps.values - deleted_deps = @locked_deps.values - @dependencies + new_deps = @dependencies - locked_dependencies + deleted_deps = locked_dependencies - @dependencies # Check if it is possible that the source is only changed thing if (new_deps.empty? && deleted_deps.empty?) && (!new_sources.empty? && !deleted_sources.empty?) @@ -560,7 +565,7 @@ module Bundler def dependencies_for_source_changed?(source, locked_source = source) deps_for_source = @dependencies.select {|s| s.source == source } - locked_deps_for_source = @locked_deps.values.select {|dep| dep.source == locked_source } + locked_deps_for_source = locked_dependencies.select {|dep| dep.source == locked_source } deps_for_source.uniq.sort != locked_deps_for_source.sort end @@ -644,7 +649,7 @@ module Bundler def converge_dependencies frozen = Bundler.frozen_bundle? - (@dependencies + @locked_deps.values).each do |dep| + (@dependencies + locked_dependencies).each do |dep| locked_source = @locked_deps[dep.name] # This is to make sure that if bundler is installing in deployment mode and # after locked_source and sources don't match, we still use locked_source. @@ -751,7 +756,6 @@ module Bundler end resolve = SpecSet.new(converged) - @locked_specs_incomplete_for_platform = !resolve.for(expand_dependencies(requested_dependencies & deps), true, true) resolve = SpecSet.new(resolve.for(expand_dependencies(deps, true), false, false).reject{|s| @unlock[:gems].include?(s.name) }) diff = nil diff --git a/lib/bundler/digest.rb b/lib/bundler/digest.rb new file mode 100644 index 0000000000..759f609416 --- /dev/null +++ b/lib/bundler/digest.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +# This code was extracted from https://github.com/Solistra/ruby-digest which is under public domain +module Bundler + module Digest + # The initial constant values for the 32-bit constant words A, B, C, D, and + # E, respectively. + SHA1_WORDS = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0].freeze + + # The 8-bit field used for bitwise `AND` masking. Defaults to `0xFFFFFFFF`. + SHA1_MASK = 0xFFFFFFFF + + class << self + def sha1(string) + unless string.is_a?(String) + raise TypeError, "can't convert #{string.class.inspect} into String" + end + + buffer = string.b + + words = SHA1_WORDS.dup + generate_split_buffer(buffer) do |chunk| + w = [] + chunk.each_slice(4) do |a, b, c, d| + w << (((a << 8 | b) << 8 | c) << 8 | d) + end + a, b, c, d, e = *words + (16..79).each do |i| + w[i] = SHA1_MASK & rotate((w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]), 1) + end + 0.upto(79) do |i| + case i + when 0..19 + f = ((b & c) | (~b & d)) + k = 0x5A827999 + when 20..39 + f = (b ^ c ^ d) + k = 0x6ED9EBA1 + when 40..59 + f = ((b & c) | (b & d) | (c & d)) + k = 0x8F1BBCDC + when 60..79 + f = (b ^ c ^ d) + k = 0xCA62C1D6 + end + t = SHA1_MASK & (SHA1_MASK & rotate(a, 5) + f + e + k + w[i]) + a, b, c, d, e = t, a, SHA1_MASK & rotate(b, 30), c, d # rubocop:disable Style/ParallelAssignment + end + mutated = [a, b, c, d, e] + words.map!.with_index {|word, index| SHA1_MASK & (word + mutated[index]) } + end + + words.pack("N*").unpack("H*").first + end + + private + + def generate_split_buffer(string, &block) + size = string.bytesize * 8 + buffer = string.bytes << 128 + buffer << 0 while buffer.size % 64 != 56 + buffer.concat([size].pack("Q>").bytes) + buffer.each_slice(64, &block) + end + + def rotate(value, spaces) + value << spaces | value >> (32 - spaces) + end + end + end +end diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb index 565aaeeb9d..9ad7460e58 100644 --- a/lib/bundler/errors.rb +++ b/lib/bundler/errors.rb @@ -75,10 +75,26 @@ module Bundler end end + def permission_type + case @permission_type + when :create + "executable permissions for all parent directories and write permissions for `#{parent_folder}`" + when :delete + permissions = "executable permissions for all parent directories and write permissions for `#{parent_folder}`" + permissions += ", and the same thing for all subdirectories inside #{@path}" if File.directory?(@path) + permissions + else + "#{@permission_type} permissions for that path" + end + end + + def parent_folder + File.dirname(@path) + end + def message "There was an error while trying to #{action} `#{@path}`. " \ - "It is likely that you need to grant #{@permission_type} permissions " \ - "for that path." + "It is likely that you need to grant #{permission_type}." end status_code(23) diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index 2bbe53aa25..e3253a942f 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -28,7 +28,8 @@ module Bundler " is a chance you are experiencing a man-in-the-middle attack, but" \ " most likely your system doesn't have the CA certificates needed" \ " for verification. For information about OpenSSL certificates, see" \ - " http://bit.ly/ruby-ssl. To connect without using SSL, edit your Gemfile" \ + " https://railsapps.github.io/openssl-certificate-verify-failed.html." \ + " To connect without using SSL, edit your Gemfile" \ " sources and change 'https' to 'http'." end end diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb index db43e0f654..cc615db60c 100644 --- a/lib/bundler/friendly_errors.rb +++ b/lib/bundler/friendly_errors.rb @@ -63,34 +63,6 @@ module Bundler def request_issue_report_for(e) Bundler.ui.error <<-EOS.gsub(/^ {8}/, ""), nil, nil --- ERROR REPORT TEMPLATE ------------------------------------------------------- - # Error Report - - ## Questions - - Please fill out answers to these questions, it'll help us figure out - why things are going wrong. - - - **What did you do?** - - I ran the command `#{$PROGRAM_NAME} #{ARGV.join(" ")}` - - - **What did you expect to happen?** - - I expected Bundler to... - - - **What happened instead?** - - Instead, what happened was... - - - **Have you tried any solutions posted on similar issues in our issue tracker, stack overflow, or google?** - - I tried... - - - **Have you read our issues document, https://github.com/rubygems/rubygems/blob/master/bundler/doc/contributing/ISSUES.md?** - - ... - - ## Backtrace ``` #{e.class}: #{e.message} @@ -109,8 +81,7 @@ module Bundler First, try this link to see if there are any existing issue reports for this error: #{issues_url(e)} - If there aren't any reports for this error yet, please copy and paste the report template above into a new issue. Don't forget to anonymize any private data! The new issue form is located at: - https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md + If there aren't any reports for this error yet, please fill in the new issue form located at #{new_issue_url}, and copy and paste the report template above in there. EOS end @@ -121,6 +92,10 @@ module Bundler "https://github.com/rubygems/rubygems/search?q=" \ "#{CGI.escape(message)}&type=Issues" end + + def new_issue_url + "https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md" + end end def self.with_friendly_errors diff --git a/lib/bundler/gem_helper.rb b/lib/bundler/gem_helper.rb index 8e1dd7c4f2..60b9e57887 100644 --- a/lib/bundler/gem_helper.rb +++ b/lib/bundler/gem_helper.rb @@ -98,10 +98,7 @@ module Bundler built_gem_path ||= build_gem cmd = [*gem_command, "install", built_gem_path.to_s] cmd << "--local" if local - _, status = sh_with_status(cmd) - unless status.success? - raise "Couldn't install gem, run `gem install #{built_gem_path}' for more detailed output" - end + sh(cmd) Bundler.ui.confirm "#{name} (#{version}) installed." end @@ -110,7 +107,7 @@ module Bundler SharedHelpers.filesystem_access(File.join(base, "checksums")) {|p| FileUtils.mkdir_p(p) } file_name = "#{File.basename(built_gem_path)}.sha512" require "digest/sha2" - checksum = Digest::SHA512.new.hexdigest(built_gem_path.to_s) + checksum = ::Digest::SHA512.new.hexdigest(built_gem_path.to_s) target = File.join(base, "checksums", file_name) File.write(target, checksum) Bundler.ui.confirm "#{name} #{version} checksum written to checksums/#{file_name}." @@ -132,8 +129,8 @@ module Bundler def git_push(remote = nil) remote ||= default_remote - perform_git_push "#{remote} refs/heads/#{current_branch}" - perform_git_push "#{remote} refs/tags/#{version_tag}" + sh(%W[git push #{remote} refs/heads/#{current_branch}]) + sh(%W[git push #{remote} refs/tags/#{version_tag}]) Bundler.ui.confirm "Pushed git commits and release tag." end @@ -161,13 +158,6 @@ module Bundler allowed_push_host || env_rubygems_host || "rubygems.org" end - def perform_git_push(options = "") - cmd = "git push #{options}" - out, status = sh_with_status(cmd.shellsplit) - return if status.success? - raise "Couldn't git push. `#{cmd}' failed with the following output:\n\n#{out}\n" - end - def already_tagged? return false unless sh(%w[git tag]).split(/\n/).include?(version_tag) Bundler.ui.confirm "Tag #{version_tag} has already been created." @@ -218,8 +208,7 @@ module Bundler def sh(cmd, &block) out, status = sh_with_status(cmd, &block) unless status.success? - cmd = cmd.shelljoin if cmd.respond_to?(:shelljoin) - raise(out.empty? ? "Running `#{cmd}` failed. Run this command directly for more detailed output." : out) + raise("Running `#{cmd.shelljoin}` failed with the following output:\n\n#{out}\n") end out end diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index a59ffe2dec..a3efff1e86 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -85,6 +85,10 @@ module Gem dependencies - development_dependencies end + def deleted_gem? + !default_gem? && !File.directory?(full_gem_path) + end + private def dependencies_to_gemfile(dependencies, group = nil) diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb index 8890582318..bb9f1cb3f5 100644 --- a/lib/bundler/rubygems_gem_installer.rb +++ b/lib/bundler/rubygems_gem_installer.rb @@ -16,10 +16,12 @@ module Bundler spec.loaded_from = spec_file # Completely remove any previous gem files - FileUtils.rm_rf gem_dir - FileUtils.rm_rf spec.extension_dir + strict_rm_rf gem_dir + strict_rm_rf spec.extension_dir - FileUtils.mkdir_p gem_dir, :mode => 0o755 + SharedHelpers.filesystem_access(gem_dir, :create) do + FileUtils.mkdir_p gem_dir, :mode => 0o755 + end extract_files @@ -31,7 +33,10 @@ module Bundler generate_plugins write_spec - write_cache_file + + SharedHelpers.filesystem_access("#{gem_home}/cache", :write) do + write_cache_file + end say spec.post_install_message unless spec.post_install_message.nil? @@ -87,6 +92,17 @@ module Bundler private + def strict_rm_rf(dir) + # FileUtils.rm_rf should probably rise in case of permission issues like + # `rm -rf` does. However, it fails to delete the folder silently due to + # https://github.com/ruby/fileutils/issues/57. It should probably be fixed + # inside `fileutils` but for now I`m checking whether the folder was + # removed after it completes, and raising otherwise. + FileUtils.rm_rf dir + + raise PermissionError.new(dir, :delete) if File.directory?(dir) + end + def validate_bundler_checksum(checksum) return true if Bundler.settings[:disable_checksum_validation] return true unless checksum diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index 98982b0f07..f6d59baf15 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -86,16 +86,12 @@ module Bundler def spec_missing_extensions?(spec, default = true) return spec.missing_extensions? if spec.respond_to?(:missing_extensions?) - return false if spec_default_gem?(spec) + return false if spec.default_gem? return false if spec.extensions.empty? default end - def spec_default_gem?(spec) - spec.respond_to?(:default_gem?) && spec.default_gem? - end - def spec_matches_for_glob(spec, glob) return spec.matches_for_glob(glob) if spec.respond_to?(:matches_for_glob) @@ -504,14 +500,15 @@ module Bundler end def fetch_specs(remote, name) + require "rubygems/remote_fetcher" path = remote.uri.to_s + "#{name}.#{Gem.marshal_version}.gz" fetcher = gem_remote_fetcher fetcher.headers = { "X-Gemfile-Source" => remote.original_uri.to_s } if remote.original_uri string = fetcher.fetch_path(path) Bundler.load_marshal(string) - rescue Gem::RemoteFetcher::FetchError => e + rescue Gem::RemoteFetcher::FetchError # it's okay for prerelease to fail - raise e unless name == "prerelease_specs" + raise unless name == "prerelease_specs" end def fetch_all_remote_specs(remote) @@ -521,12 +518,32 @@ module Bundler specs.concat(pres) end - def download_gem(spec, uri, path) + def download_gem(spec, uri, cache_dir) + require "rubygems/remote_fetcher" uri = Bundler.settings.mirror_for(uri) fetcher = gem_remote_fetcher fetcher.headers = { "X-Gemfile-Source" => spec.remote.original_uri.to_s } if spec.remote.original_uri Bundler::Retry.new("download gem from #{uri}").attempts do - fetcher.download(spec, uri, path) + gem_file_name = spec.file_name + local_gem_path = File.join cache_dir, gem_file_name + return if File.exist? local_gem_path + + begin + remote_gem_path = uri + "gems/#{gem_file_name}" + remote_gem_path = remote_gem_path.to_s if provides?("< 3.2.0.rc.1") + + SharedHelpers.filesystem_access(local_gem_path) do + fetcher.cache_update_path remote_gem_path, local_gem_path + end + rescue Gem::RemoteFetcher::FetchError + raise if spec.original_platform == spec.platform + + original_gem_file_name = "#{spec.original_name}.gem" + raise if gem_file_name == original_gem_file_name + + gem_file_name = original_gem_file_name + retry + end end rescue Gem::RemoteFetcher::FetchError => e raise Bundler::HTTPError, "Could not download gem from #{uri} due to underlying error <#{e.message}>" diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index fbb8833cfb..31e71388a6 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -291,7 +291,7 @@ module Bundler return unless activated_spec = Bundler.rubygems.loaded_specs(spec.name) return if activated_spec.version == spec.version - suggestion = if Bundler.rubygems.spec_default_gem?(activated_spec) + suggestion = if activated_spec.default_gem? "Since #{spec.name} is a default gem, you can either remove your dependency on it" \ " or try updating to a newer version of bundler that supports #{spec.name} as a default gem." else diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index 679fb22574..a41a2f23e9 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -307,7 +307,9 @@ module Bundler # If there is no URI scheme, assume it is an ssh/git URI input = uri end - SharedHelpers.digest(:SHA1).hexdigest(input) + # We use SHA1 here for historical reason and to preserve backward compatibility. + # But a transition to a simpler mangling algorithm would be welcome. + Bundler::Digest.sha1(input) end def cached_revision diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 7f8699b91c..11b506b324 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -135,7 +135,7 @@ module Bundler force = opts[:force] ensure_builtin_gems_cached = opts[:ensure_builtin_gems_cached] - if ensure_builtin_gems_cached && builtin_gem?(spec) + if ensure_builtin_gems_cached && spec.default_gem? if !cached_path(spec) cached_built_in_gem(spec) unless spec.remote force = true @@ -174,6 +174,7 @@ module Bundler Bundler.ui.confirm message path = cached_gem(spec) + raise GemNotFound, "Could not find #{spec.file_name} for installation" unless path if requires_sudo? install_path = Bundler.tmp(spec.full_name) bin_path = install_path.join("bin") @@ -233,12 +234,8 @@ module Bundler end def cache(spec, custom_path = nil) - if builtin_gem?(spec) - cached_path = cached_built_in_gem(spec) - else - cached_path = cached_gem(spec) - end - raise GemNotFound, "Missing gem file '#{spec.full_name}.gem'." unless cached_path + cached_path = cached_gem(spec) + raise GemNotFound, "Missing gem file '#{spec.file_name}'." unless cached_path return if File.dirname(cached_path) == Bundler.app_cache.to_s Bundler.ui.info " * #{File.basename(cached_path)}" FileUtils.cp(cached_path, Bundler.app_cache(custom_path)) @@ -352,14 +349,17 @@ module Bundler end def cached_gem(spec) - cached_gem = cached_path(spec) - unless cached_gem - raise Bundler::GemNotFound, "Could not find #{spec.file_name} for installation" + if spec.default_gem? + cached_built_in_gem(spec) + else + cached_path(spec) end - cached_gem end def cached_path(spec) + global_cache_path = download_cache_path(spec) + @caches << global_cache_path if global_cache_path + possibilities = @caches.map {|p| "#{p}/#{spec.file_name}" } possibilities.find {|p| File.exist?(p) } end @@ -459,19 +459,26 @@ module Bundler spec.fetch_platform - download_path = requires_sudo? ? Bundler.tmp(spec.full_name) : rubygems_dir - gem_path = "#{rubygems_dir}/cache/#{spec.full_name}.gem" + cache_path = download_cache_path(spec) || default_cache_path_for(rubygems_dir) + gem_path = "#{cache_path}/#{spec.file_name}" + + if requires_sudo? + download_path = Bundler.tmp(spec.full_name) + download_cache_path = default_cache_path_for(download_path) + else + download_cache_path = cache_path + end - SharedHelpers.filesystem_access("#{download_path}/cache") do |p| + SharedHelpers.filesystem_access(download_cache_path) do |p| FileUtils.mkdir_p(p) end - download_gem(spec, download_path) + download_gem(spec, download_cache_path) if requires_sudo? - SharedHelpers.filesystem_access("#{rubygems_dir}/cache") do |p| + SharedHelpers.filesystem_access(cache_path) do |p| Bundler.mkdir_p(p) end - Bundler.sudo "mv #{download_path}/cache/#{spec.full_name}.gem #{gem_path}" + Bundler.sudo "mv #{download_cache_path}/#{spec.file_name} #{gem_path}" end gem_path @@ -479,16 +486,8 @@ module Bundler Bundler.rm_rf(download_path) if requires_sudo? end - def builtin_gem?(spec) - # Ruby 2.1, where all included gems have this summary - return true if spec.summary =~ /is bundled with Ruby/ - - # Ruby 2.0, where gemspecs are stored in specifications/default/ - spec.loaded_from && spec.loaded_from.include?("specifications/default/") - end - def installed?(spec) - installed_specs[spec].any? + installed_specs[spec].any? && !spec.deleted_gem? end def requires_sudo? @@ -499,6 +498,10 @@ module Bundler Bundler.rubygems.gem_dir end + def default_cache_path_for(dir) + "#{dir}/cache" + end + def cache_path Bundler.app_cache end @@ -511,52 +514,13 @@ module Bundler # @param [Specification] spec # the spec we want to download or retrieve from the cache. # - # @param [String] download_path + # @param [String] download_cache_path # the local directory the .gem will end up in. # - def download_gem(spec, download_path) - local_path = File.join(download_path, "cache/#{spec.full_name}.gem") - - if (cache_path = download_cache_path(spec)) && cache_path.file? - SharedHelpers.filesystem_access(local_path) do - FileUtils.cp(cache_path, local_path) - end - else - uri = spec.remote.uri - Bundler.ui.confirm("Fetching #{version_message(spec)}") - rubygems_local_path = Bundler.rubygems.download_gem(spec, uri, download_path) - - # older rubygems return varying file:// variants depending on version - rubygems_local_path = rubygems_local_path.gsub(/\Afile:/, "") unless Bundler.rubygems.provides?(">= 3.2.0.rc.2") - rubygems_local_path = rubygems_local_path.gsub(%r{\A//}, "") if Bundler.rubygems.provides?("< 3.1.0") - - if rubygems_local_path != local_path - SharedHelpers.filesystem_access(local_path) do - FileUtils.mv(rubygems_local_path, local_path) - end - end - cache_globally(spec, local_path) - end - end - - # Checks if the requested spec exists in the global cache. If it does - # not, we create the relevant global cache subdirectory if it does not - # exist and copy the spec from the local cache to the global cache. - # - # @param [Specification] spec - # the spec we want to copy to the global cache. - # - # @param [String] local_cache_path - # the local directory from which we want to copy the .gem. - # - def cache_globally(spec, local_cache_path) - return unless cache_path = download_cache_path(spec) - return if cache_path.exist? - - SharedHelpers.filesystem_access(cache_path.dirname, &:mkpath) - SharedHelpers.filesystem_access(cache_path) do - FileUtils.cp(local_cache_path, cache_path) - end + def download_gem(spec, download_cache_path) + uri = spec.remote.uri + Bundler.ui.confirm("Fetching #{version_message(spec)}") + Bundler.rubygems.download_gem(spec, uri, download_cache_path) end # Returns the global cache path of the calling Rubygems::Source object. @@ -575,7 +539,7 @@ module Bundler return unless remote = spec.remote return unless cache_slug = remote.cache_slug - Bundler.user_cache.join("gems", cache_slug, spec.file_name) + Bundler.user_cache.join("gems", cache_slug) end def extension_cache_slug(spec) diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb index fbcd26c765..984c1c3dcb 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb @@ -1,14 +1,18 @@ -require_relative 'connection_pool/version' -require_relative 'connection_pool/timed_stack' +require "timeout" +require_relative "connection_pool/version" +class Bundler::ConnectionPool + class Error < ::RuntimeError; end + class PoolShuttingDownError < ::Bundler::ConnectionPool::Error; end + class TimeoutError < ::Timeout::Error; end +end -# Generic connection pool class for e.g. sharing a limited number of network connections -# among many threads. Note: Connections are lazily created. +# Generic connection pool class for sharing a limited number of objects or network connections +# among many threads. Note: pool elements are lazily created. # # Example usage with block (faster): # # @pool = Bundler::ConnectionPool.new { Redis.new } -# # @pool.with do |redis| # redis.lpop('my-list') if redis.llen('my-list') > 0 # end @@ -34,29 +38,23 @@ require_relative 'connection_pool/timed_stack' class Bundler::ConnectionPool DEFAULTS = {size: 5, timeout: 5} - class Error < RuntimeError - end - def self.wrap(options, &block) Wrapper.new(options, &block) end def initialize(options = {}, &block) - raise ArgumentError, 'Connection pool requires a block' unless block + raise ArgumentError, "Connection pool requires a block" unless block options = DEFAULTS.merge(options) - @size = options.fetch(:size) + @size = Integer(options.fetch(:size)) @timeout = options.fetch(:timeout) @available = TimedStack.new(@size, &block) - @key = :"current-#{@available.object_id}" - @key_count = :"current-#{@available.object_id}-count" + @key = :"pool-#{@available.object_id}" + @key_count = :"pool-#{@available.object_id}-count" end -if Thread.respond_to?(:handle_interrupt) - - # MRI def with(options = {}) Thread.handle_interrupt(Exception => :never) do conn = checkout(options) @@ -69,28 +67,15 @@ if Thread.respond_to?(:handle_interrupt) end end end - -else - - # jruby 1.7.x - def with(options = {}) - conn = checkout(options) - begin - yield conn - ensure - checkin - end - end - -end + alias then with def checkout(options = {}) if ::Thread.current[@key] - ::Thread.current[@key_count]+= 1 + ::Thread.current[@key_count] += 1 ::Thread.current[@key] else - ::Thread.current[@key_count]= 1 - ::Thread.current[@key]= @available.pop(options[:timeout] || @timeout) + ::Thread.current[@key_count] = 1 + ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout) end end @@ -98,64 +83,44 @@ end if ::Thread.current[@key] if ::Thread.current[@key_count] == 1 @available.push(::Thread.current[@key]) - ::Thread.current[@key]= nil + ::Thread.current[@key] = nil + ::Thread.current[@key_count] = nil else - ::Thread.current[@key_count]-= 1 + ::Thread.current[@key_count] -= 1 end else - raise Bundler::ConnectionPool::Error, 'no connections are checked out' + raise Bundler::ConnectionPool::Error, "no connections are checked out" end nil end + ## + # Shuts down the Bundler::ConnectionPool by passing each connection to +block+ and + # then removing it from the pool. Attempting to checkout a connection after + # shutdown will raise +Bundler::ConnectionPool::PoolShuttingDownError+. + def shutdown(&block) @available.shutdown(&block) end - # Size of this connection pool - def size - @size + ## + # Reloads the Bundler::ConnectionPool by passing each connection to +block+ and then + # removing it the pool. Subsequent checkouts will create new connections as + # needed. + + def reload(&block) + @available.shutdown(reload: true, &block) end + # Size of this connection pool + attr_reader :size + # Number of pool entries available for checkout at this instant. def available @available.length end - - private - - class Wrapper < ::BasicObject - METHODS = [:with, :pool_shutdown] - - def initialize(options = {}, &block) - @pool = options.fetch(:pool) { ::Bundler::ConnectionPool.new(options, &block) } - end - - def with(&block) - @pool.with(&block) - end - - def pool_shutdown(&block) - @pool.shutdown(&block) - end - - def pool_size - @pool.size - end - - def pool_available - @pool.available - end - - def respond_to?(id, *args) - METHODS.include?(id) || with { |c| c.respond_to?(id, *args) } - end - - def method_missing(name, *args, &block) - with do |connection| - connection.send(name, *args, &block) - end - end - end end + +require_relative "connection_pool/timed_stack" +require_relative "connection_pool/wrapper" diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/monotonic_time.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/monotonic_time.rb deleted file mode 100644 index 5a9c4a27bb..0000000000 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/monotonic_time.rb +++ /dev/null @@ -1,66 +0,0 @@ -# Global monotonic clock from Concurrent Ruby 1.0. -# Copyright (c) Jerry D'Antonio -- released under the MIT license. -# Slightly modified; used with permission. -# https://github.com/ruby-concurrency/concurrent-ruby - -require 'thread' - -class Bundler::ConnectionPool - - class_definition = Class.new do - - if defined?(Process::CLOCK_MONOTONIC) - - # @!visibility private - def get_time - Process.clock_gettime(Process::CLOCK_MONOTONIC) - end - - elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' - - # @!visibility private - def get_time - java.lang.System.nanoTime() / 1_000_000_000.0 - end - - else - - # @!visibility private - def initialize - @mutex = Mutex.new - @last_time = Time.now.to_f - end - - # @!visibility private - def get_time - @mutex.synchronize do - now = Time.now.to_f - if @last_time < now - @last_time = now - else # clock has moved back in time - @last_time += 0.000_001 - end - end - end - end - end - - ## - # Clock that cannot be set and represents monotonic time since - # some unspecified starting point. - # - # @!visibility private - GLOBAL_MONOTONIC_CLOCK = class_definition.new - private_constant :GLOBAL_MONOTONIC_CLOCK - - class << self - ## - # Returns the current time a tracked by the application monotonic clock. - # - # @return [Float] The current monotonic time when `since` not given else - # the elapsed monotonic time between `since` and the current time - def monotonic_time - GLOBAL_MONOTONIC_CLOCK.get_time - end - end -end diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb index f3fe1e04ad..a7b1cf06a8 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb @@ -1,13 +1,3 @@ -require 'thread' -require 'timeout' -require_relative 'monotonic_time' - -## -# Raised when you attempt to retrieve a connection from a pool that has been -# shut down. - -class Bundler::ConnectionPool::PoolShuttingDownError < RuntimeError; end - ## # The TimedStack manages a pool of homogeneous connections (or any resource # you wish to manage). Connections are created lazily up to a given maximum @@ -25,7 +15,7 @@ class Bundler::ConnectionPool::PoolShuttingDownError < RuntimeError; end # # conn = ts.pop # ts.pop timeout: 5 -# #=> raises Timeout::Error after 5 seconds +# #=> raises Bundler::ConnectionPool::TimeoutError after 5 seconds class Bundler::ConnectionPool::TimedStack attr_reader :max @@ -39,8 +29,8 @@ class Bundler::ConnectionPool::TimedStack @created = 0 @que = [] @max = size - @mutex = Mutex.new - @resource = ConditionVariable.new + @mutex = Thread::Mutex.new + @resource = Thread::ConditionVariable.new @shutdown_block = nil end @@ -59,12 +49,12 @@ class Bundler::ConnectionPool::TimedStack @resource.broadcast end end - alias_method :<<, :push + alias << push ## # Retrieves a connection from the stack. If a connection is available it is # immediately returned. If no connection is available within the given - # timeout a Timeout::Error is raised. + # timeout a Bundler::ConnectionPool::TimeoutError is raised. # # +:timeout+ is the only checked entry in +options+ and is preferred over # the +timeout+ argument (which will be removed in a future release). Other @@ -74,7 +64,7 @@ class Bundler::ConnectionPool::TimedStack options, timeout = timeout, 0.5 if Hash === timeout timeout = options.fetch :timeout, timeout - deadline = Bundler::ConnectionPool.monotonic_time + timeout + deadline = current_time + timeout @mutex.synchronize do loop do raise Bundler::ConnectionPool::PoolShuttingDownError if @shutdown_block @@ -83,18 +73,20 @@ class Bundler::ConnectionPool::TimedStack connection = try_create(options) return connection if connection - to_wait = deadline - Bundler::ConnectionPool.monotonic_time - raise Timeout::Error, "Waited #{timeout} sec" if to_wait <= 0 + to_wait = deadline - current_time + raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec" if to_wait <= 0 @resource.wait(@mutex, to_wait) end end end ## - # Shuts down the TimedStack which prevents connections from being checked - # out. The +block+ is called once for each connection on the stack. + # Shuts down the TimedStack by passing each connection to +block+ and then + # removing it from the pool. Attempting to checkout a connection after + # shutdown will raise +Bundler::ConnectionPool::PoolShuttingDownError+ unless + # +:reload+ is +true+. - def shutdown(&block) + def shutdown(reload: false, &block) raise ArgumentError, "shutdown must receive a block" unless block_given? @mutex.synchronize do @@ -102,6 +94,7 @@ class Bundler::ConnectionPool::TimedStack @resource.broadcast shutdown_connections + @shutdown_block = nil if reload end end @@ -121,6 +114,10 @@ class Bundler::ConnectionPool::TimedStack private + def current_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + ## # This is an extension point for TimedStack and is called with a mutex. # @@ -149,6 +146,7 @@ class Bundler::ConnectionPool::TimedStack conn = fetch_connection(options) @shutdown_block.call(conn) end + @created = 0 end ## diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb index b149c0e242..56ebf69902 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb @@ -1,3 +1,3 @@ class Bundler::ConnectionPool - VERSION = "2.2.2" + VERSION = "2.3.0" end diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb new file mode 100644 index 0000000000..880170c06b --- /dev/null +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb @@ -0,0 +1,57 @@ +class Bundler::ConnectionPool + class Wrapper < ::BasicObject + METHODS = [:with, :pool_shutdown, :wrapped_pool] + + def initialize(options = {}, &block) + @pool = options.fetch(:pool) { ::Bundler::ConnectionPool.new(options, &block) } + end + + def wrapped_pool + @pool + end + + def with(&block) + @pool.with(&block) + end + + def pool_shutdown(&block) + @pool.shutdown(&block) + end + + def pool_size + @pool.size + end + + def pool_available + @pool.available + end + + def respond_to?(id, *args) + METHODS.include?(id) || with { |c| c.respond_to?(id, *args) } + end + + # rubocop:disable Style/MethodMissingSuper + # rubocop:disable Style/MissingRespondToMissing + if ::RUBY_VERSION >= "3.0.0" + def method_missing(name, *args, **kwargs, &block) + with do |connection| + connection.send(name, *args, **kwargs, &block) + end + end + elsif ::RUBY_VERSION >= "2.7.0" + ruby2_keywords def method_missing(name, *args, &block) + with do |connection| + connection.send(name, *args, &block) + end + end + else + def method_missing(name, *args, &block) + with do |connection| + connection.send(name, *args, &block) + end + end + end + # rubocop:enable Style/MethodMissingSuper + # rubocop:enable Style/MissingRespondToMissing + end +end diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 23a568c8ba..5ee93a2266 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "2.2.29".freeze + VERSION = "2.2.30".freeze def self.bundler_major_version @bundler_major_version ||= VERSION.split(".").first.to_i diff --git a/lib/bundler/worker.rb b/lib/bundler/worker.rb index ffa095b228..5e4ee21c51 100644 --- a/lib/bundler/worker.rb +++ b/lib/bundler/worker.rb @@ -21,8 +21,8 @@ module Bundler # @param func [Proc] job to run in inside the worker pool def initialize(size, name, func) @name = name - @request_queue = Queue.new - @response_queue = Queue.new + @request_queue = Thread::Queue.new + @response_queue = Thread::Queue.new @func = func @size = size @threads = nil diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 3c48f2a8ac..dc6746769a 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -8,7 +8,7 @@ require 'rbconfig' module Gem - VERSION = "3.2.29".freeze + VERSION = "3.2.30".freeze end # Must be first since it unloads the prelude from 1.9.2 @@ -178,7 +178,7 @@ module Gem @configuration = nil @gemdeps = nil @loaded_specs = {} - LOADED_SPECS_MUTEX = Mutex.new + LOADED_SPECS_MUTEX = Thread::Mutex.new @path_to_default_spec_map = {} @platforms = [] @ruby = nil diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb index bdfeb0ba6e..867cb07cca 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -43,6 +43,11 @@ class Gem::Commands::CertCommand < Gem::Command options[:key] = open_private_key(key_file) end + add_option('-A', '--key-algorithm ALGORITHM', + 'Select which key algorithm to use for --build') do |algorithm, options| + options[:key_algorithm] = algorithm + end + add_option('-s', '--sign CERT', 'Signs CERT with the key from -K', 'and the certificate from -C') do |cert_file, options| @@ -89,14 +94,14 @@ class Gem::Commands::CertCommand < Gem::Command def open_private_key(key_file) check_openssl passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE'] - key = OpenSSL::PKey::RSA.new File.read(key_file), passphrase + key = OpenSSL::PKey.read File.read(key_file), passphrase raise OptionParser::InvalidArgument, "#{key_file}: private key not found" unless key.private? key rescue Errno::ENOENT raise OptionParser::InvalidArgument, "#{key_file}: does not exist" - rescue OpenSSL::PKey::RSAError - raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA key" + rescue OpenSSL::PKey::PKeyError, ArgumentError + raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA, DSA, or EC key" end def execute @@ -170,7 +175,8 @@ class Gem::Commands::CertCommand < Gem::Command raise Gem::CommandLineError, "Passphrase and passphrase confirmation don't match" unless passphrase == passphrase_confirmation - key = Gem::Security.create_key + algorithm = options[:key_algorithm] || Gem::Security::DEFAULT_KEY_ALGORITHM + key = Gem::Security.create_key(algorithm) key_path = Gem::Security.write key, "gem-private_key.pem", 0600, passphrase return key, key_path @@ -255,13 +261,14 @@ For further reading on signing gems see `ri Gem::Security`. key_file = File.join Gem.default_key_path key = File.read key_file passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE'] - options[:key] = OpenSSL::PKey::RSA.new key, passphrase + options[:key] = OpenSSL::PKey.read key, passphrase + rescue Errno::ENOENT alert_error \ "--private-key not specified and ~/.gem/gem-private_key.pem does not exist" terminate_interaction 1 - rescue OpenSSL::PKey::RSAError + rescue OpenSSL::PKey::PKeyError alert_error \ "--private-key not specified and ~/.gem/gem-private_key.pem is not valid" diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 7ef0da4f36..92c9b93fed 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -618,7 +618,7 @@ abort "#{deprecation_message}" end def regenerate_plugins - require "rubygems/commands/pristine_command" + require_relative "pristine_command" say "Regenerating plugins" args = %w[--all --only-plugins --silent] diff --git a/lib/rubygems/core_ext/tcpsocket_init.rb b/lib/rubygems/core_ext/tcpsocket_init.rb index 3d9740c579..2a79b63bd6 100644 --- a/lib/rubygems/core_ext/tcpsocket_init.rb +++ b/lib/rubygems/core_ext/tcpsocket_init.rb @@ -11,10 +11,10 @@ module CoreExtensions IPV4_DELAY_SECONDS = 0.1 def initialize(host, serv, *rest) - mutex = Mutex.new + mutex = Thread::Mutex.new addrs = [] threads = [] - cond_var = ConditionVariable.new + cond_var = Thread::ConditionVariable.new Addrinfo.foreach(host, serv, nil, :STREAM) do |addr| Thread.report_on_exception = false if defined? Thread.report_on_exception = () diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 26ebfaa846..cf5ed7b502 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -68,7 +68,7 @@ class Gem::Installer @path_warning = false - @install_lock = Mutex.new + @install_lock = Thread::Mutex.new class << self ## diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index c4b29e4176..94705914af 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -71,6 +71,13 @@ class Gem::Package end end + class SymlinkError < Error + def initialize(name, destination, destination_dir) + super "installing symlink '%s' pointing to parent path %s of %s is not allowed" % + [name, destination, destination_dir] + end + end + class NonSeekableIO < Error; end class TooLongFileName < Error; end @@ -400,13 +407,21 @@ EOM # extracted. def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: - directories = [] if dir_mode + directories = [] open_tar_gz io do |tar| tar.each do |entry| next unless File.fnmatch pattern, entry.full_name, File::FNM_DOTMATCH destination = install_location entry.full_name, destination_dir + if entry.symlink? + link_target = entry.header.linkname + real_destination = link_target.start_with?("/") ? link_target : File.expand_path(link_target, File.dirname(destination)) + + raise Gem::Package::SymlinkError.new(entry.full_name, real_destination, destination_dir) unless + normalize_path(real_destination).start_with? normalize_path(destination_dir + '/') + end + FileUtils.rm_rf destination mkdir_options = {} @@ -417,9 +432,11 @@ EOM else File.dirname destination end - directories << mkdir if directories - mkdir_p_safe mkdir, mkdir_options, destination_dir, entry.full_name + unless directories.include?(mkdir) + FileUtils.mkdir_p mkdir, **mkdir_options + directories << mkdir + end File.open destination, 'wb' do |out| out.write entry.read @@ -432,8 +449,7 @@ EOM end end - if directories - directories.uniq! + if dir_mode File.chmod(dir_mode, *directories) end end @@ -466,21 +482,11 @@ EOM raise Gem::Package::PathError.new(filename, destination_dir) if filename.start_with? '/' - destination_dir = File.expand_path(File.realpath(destination_dir)) - destination = File.expand_path(File.join(destination_dir, filename)) + destination_dir = File.realpath(destination_dir) + destination = File.expand_path(filename, destination_dir) raise Gem::Package::PathError.new(destination, destination_dir) unless - destination.start_with? destination_dir + '/' - - begin - real_destination = File.expand_path(File.realpath(destination)) - rescue - # it's fine if the destination doesn't exist, because rm -rf'ing it can't cause any damage - nil - else - raise Gem::Package::PathError.new(real_destination, destination_dir) unless - real_destination.start_with? destination_dir + '/' - end + normalize_path(destination).start_with? normalize_path(destination_dir + '/') destination.tap(&Gem::UNTAINT) destination @@ -494,22 +500,6 @@ EOM end end - def mkdir_p_safe(mkdir, mkdir_options, destination_dir, file_name) - destination_dir = File.realpath(File.expand_path(destination_dir)) - parts = mkdir.split(File::SEPARATOR) - parts.reduce do |path, basename| - path = File.realpath(path) unless path == "" - path = File.expand_path(path + File::SEPARATOR + basename) - lstat = File.lstat path rescue nil - if !lstat || !lstat.directory? - unless normalize_path(path).start_with? normalize_path(destination_dir) and (FileUtils.mkdir path, **mkdir_options rescue false) - raise Gem::Package::PathError.new(file_name, destination_dir) - end - end - path - end - end - ## # Loads a Gem::Specification from the TarEntry +entry+ diff --git a/lib/rubygems/query_utils.rb b/lib/rubygems/query_utils.rb index ea0f260ab4..0acd5bf9c8 100644 --- a/lib/rubygems/query_utils.rb +++ b/lib/rubygems/query_utils.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require 'rubygems/local_remote_options' -require 'rubygems/spec_fetcher' -require 'rubygems/version_option' -require 'rubygems/text' +require_relative 'local_remote_options' +require_relative 'spec_fetcher' +require_relative 'version_option' +require_relative 'text' module Gem::QueryUtils diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 22ad89b970..de6f88f39a 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -72,7 +72,7 @@ class Gem::RemoteFetcher # fetching the gem. def initialize(proxy=nil, dns=nil, headers={}) - require 'rubygems/core_ext/tcpsocket_init' if Gem.configuration.ipv4_fallback_enabled + require_relative 'core_ext/tcpsocket_init' if Gem.configuration.ipv4_fallback_enabled require 'net/http' require 'stringio' require 'uri' @@ -81,7 +81,7 @@ class Gem::RemoteFetcher @proxy = proxy @pools = {} - @pool_lock = Mutex.new + @pool_lock = Thread::Mutex.new @cert_files = Gem::Request.get_cert_files @headers = headers diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb index 72e25e20ab..d6100c914b 100644 --- a/lib/rubygems/request.rb +++ b/lib/rubygems/request.rb @@ -96,8 +96,10 @@ class Gem::Request return unless cert case error_number when OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED then + require 'time' "Certificate #{cert.subject} expired at #{cert.not_after.iso8601}" when OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID then + require 'time' "Certificate #{cert.subject} not valid until #{cert.not_before.iso8601}" when OpenSSL::X509::V_ERR_CERT_REJECTED then "Certificate #{cert.subject} is rejected" diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb index 7f3988952c..a4c2929b38 100644 --- a/lib/rubygems/request/connection_pools.rb +++ b/lib/rubygems/request/connection_pools.rb @@ -11,7 +11,7 @@ class Gem::Request::ConnectionPools # :nodoc: @proxy_uri = proxy_uri @cert_files = cert_files @pools = {} - @pool_mutex = Mutex.new + @pool_mutex = Thread::Mutex.new end def pool_for(uri) diff --git a/lib/rubygems/request/http_pool.rb b/lib/rubygems/request/http_pool.rb index 9985bbafa6..f028516db8 100644 --- a/lib/rubygems/request/http_pool.rb +++ b/lib/rubygems/request/http_pool.rb @@ -12,7 +12,7 @@ class Gem::Request::HTTPPool # :nodoc: @http_args = http_args @cert_files = cert_files @proxy_uri = proxy_uri - @queue = SizedQueue.new 1 + @queue = Thread::SizedQueue.new 1 @queue << nil end diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index cff960708c..9286c54221 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -151,7 +151,7 @@ class Gem::RequestSet @prerelease = options[:prerelease] requests = [] - download_queue = Queue.new + download_queue = Thread::Queue.new # Create a thread-safe list of gems to download sorted_requests.each do |req| diff --git a/lib/rubygems/s3_uri_signer.rb b/lib/rubygems/s3_uri_signer.rb index bba9afc9ff..4d1deee997 100644 --- a/lib/rubygems/s3_uri_signer.rb +++ b/lib/rubygems/s3_uri_signer.rb @@ -1,5 +1,4 @@ -require 'digest' -require 'rubygems/openssl' +require_relative 'openssl' ## # S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems @@ -87,7 +86,7 @@ class Gem::S3URISigner "AWS4-HMAC-SHA256", date_time, credential_info, - Digest::SHA256.hexdigest(canonical_request), + OpenSSL::Digest::SHA256.hexdigest(canonical_request), ].join("\n") end @@ -140,8 +139,8 @@ class Gem::S3URISigner def ec2_metadata_credentials_json require 'net/http' - require 'rubygems/request' - require 'rubygems/request/connection_pools' + require_relative 'request' + require_relative 'request/connection_pools' require 'json' iam_info = ec2_metadata_request(EC2_IAM_INFO) diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb index 28f705549c..8240a1a059 100644 --- a/lib/rubygems/security.rb +++ b/lib/rubygems/security.rb @@ -152,6 +152,7 @@ require_relative 'openssl' # certificate for EMAIL_ADDR # -C, --certificate CERT Signing certificate for --sign # -K, --private-key KEY Key for --sign or --build +# -A, --key-algorithm ALGORITHM Select key algorithm for --build from RSA, DSA, or EC. Defaults to RSA. # -s, --sign CERT Signs CERT with the key from -K # and the certificate from -C # -d, --days NUMBER_OF_DAYS Days before the certificate expires @@ -317,7 +318,6 @@ require_relative 'openssl' # * Honor extension restrictions # * Might be better to store the certificate chain as a PKCS#7 or PKCS#12 # file, instead of an array embedded in the metadata. -# * Flexible signature and key algorithms, not hard-coded to RSA and SHA1. # # == Original author # @@ -337,17 +337,19 @@ module Gem::Security DIGEST_NAME = 'SHA256' # :nodoc: ## - # Algorithm for creating the key pair used to sign gems + # Length of keys created by RSA and DSA keys - KEY_ALGORITHM = - if defined?(OpenSSL::PKey::RSA) - OpenSSL::PKey::RSA - end + RSA_DSA_KEY_LENGTH = 3072 ## - # Length of keys created by KEY_ALGORITHM + # Default algorithm to use when building a key pair - KEY_LENGTH = 3072 + DEFAULT_KEY_ALGORITHM = 'RSA' + + ## + # Named curve used for Elliptic Curve + + EC_NAME = 'secp384r1' ## # Cipher used to encrypt the key pair used to sign gems. @@ -400,7 +402,7 @@ module Gem::Security serial = 1) cert = OpenSSL::X509::Certificate.new - cert.public_key = key.public_key + cert.public_key = get_public_key(key) cert.version = 2 cert.serial = serial @@ -419,6 +421,24 @@ module Gem::Security end ## + # Gets the right public key from a PKey instance + + def self.get_public_key(key) + return key.public_key unless key.is_a?(OpenSSL::PKey::EC) + + ec_key = OpenSSL::PKey::EC.new(key.group.curve_name) + ec_key.public_key = key.public_key + ec_key + end + + ## + # In Ruby 2.3 EC doesn't implement the private_key? but not the private? method + + if defined?(OpenSSL::PKey::EC) && Gem::Version.new(String.new(RUBY_VERSION)) < Gem::Version.new("2.4.0") + OpenSSL::PKey::EC.send(:alias_method, :private?, :private_key?) + end + + ## # Creates a self-signed certificate with an issuer and subject from +email+, # a subject alternative name of +email+ and the given +extensions+ for the # +key+. @@ -459,11 +479,25 @@ module Gem::Security end ## - # Creates a new key pair of the specified +length+ and +algorithm+. The - # default is a 3072 bit RSA key. - - def self.create_key(length = KEY_LENGTH, algorithm = KEY_ALGORITHM) - algorithm.new length + # Creates a new key pair of the specified +algorithm+. RSA, DSA, and EC + # are supported. + + def self.create_key(algorithm) + if defined?(OpenSSL::PKey) + case algorithm.downcase + when 'dsa' + OpenSSL::PKey::DSA.new(RSA_DSA_KEY_LENGTH) + when 'rsa' + OpenSSL::PKey::RSA.new(RSA_DSA_KEY_LENGTH) + when 'ec' + domain_key = OpenSSL::PKey::EC.new(EC_NAME) + domain_key.generate_key + domain_key + else + raise Gem::Security::Exception, + "#{algorithm} algorithm not found. RSA, DSA, and EC algorithms are supported." + end + end end ## @@ -492,7 +526,7 @@ module Gem::Security raise Gem::Security::Exception, "incorrect signing key for re-signing " + "#{expired_certificate.subject}" unless - expired_certificate.public_key.to_pem == private_key.public_key.to_pem + expired_certificate.public_key.to_pem == get_public_key(private_key).to_pem unless expired_certificate.subject.to_s == expired_certificate.issuer.to_s diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb index 9683e55b32..3c3cb647ee 100644 --- a/lib/rubygems/security/policy.rb +++ b/lib/rubygems/security/policy.rb @@ -115,9 +115,11 @@ class Gem::Security::Policy raise Gem::Security::Exception, 'missing key or signature' end + public_key = Gem::Security.get_public_key(key) + raise Gem::Security::Exception, "certificate #{signer.subject} does not match the signing key" unless - signer.public_key.to_pem == key.public_key.to_pem + signer.public_key.to_pem == public_key.to_pem true end @@ -164,9 +166,9 @@ class Gem::Security::Policy end save_cert = OpenSSL::X509::Certificate.new File.read path - save_dgst = digester.digest save_cert.public_key.to_s + save_dgst = digester.digest save_cert.public_key.to_pem - pkey_str = root.public_key.to_s + pkey_str = root.public_key.to_pem cert_dgst = digester.digest pkey_str raise Gem::Security::Exception, diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb index c5c2c4f220..968cf88973 100644 --- a/lib/rubygems/security/signer.rb +++ b/lib/rubygems/security/signer.rb @@ -83,8 +83,8 @@ class Gem::Security::Signer @digest_name = Gem::Security::DIGEST_NAME @digest_algorithm = Gem::Security.create_digest(@digest_name) - if @key && !@key.is_a?(OpenSSL::PKey::RSA) - @key = OpenSSL::PKey::RSA.new(File.read(@key), @passphrase) + if @key && !@key.is_a?(OpenSSL::PKey::PKey) + @key = OpenSSL::PKey.read(File.read(@key), @passphrase) end if @cert_chain @@ -177,8 +177,7 @@ class Gem::Security::Signer disk_cert = File.read(disk_cert_path) rescue nil disk_key_path = File.join(Gem.default_key_path) - disk_key = - OpenSSL::PKey::RSA.new(File.read(disk_key_path), @passphrase) rescue nil + disk_key = OpenSSL::PKey.read(File.read(disk_key_path), @passphrase) rescue nil return unless disk_key diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb index 816385d139..b5bd6b80e6 100644 --- a/lib/rubygems/source.rb +++ b/lib/rubygems/source.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "rubygems/text" +require_relative "text" ## # A Source knows how to list and fetch gems from a RubyGems marshal index. # diff --git a/lib/rubygems/source/git.rb b/lib/rubygems/source/git.rb index 9876adc24e..cda5aa8073 100644 --- a/lib/rubygems/source/git.rb +++ b/lib/rubygems/source/git.rb @@ -225,7 +225,7 @@ class Gem::Source::Git < Gem::Source # A hash for the git gem based on the git repository URI. def uri_hash # :nodoc: - require 'digest' # required here to avoid deadlocking in Gem.activate_bin_path (because digest is a gem on 2.5+) + require_relative '../openssl' normalized = if @repository =~ %r{^\w+://(\w+@)?} @@ -235,6 +235,6 @@ class Gem::Source::Git < Gem::Source @repository end - Digest::SHA1.hexdigest normalized + OpenSSL::Digest::SHA1.hexdigest normalized end end diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index aaa3b8ce7b..37fa9d0a8e 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -105,7 +105,7 @@ class Gem::Specification < Gem::BasicSpecification # rubocop:disable Style/MutableConstant LOAD_CACHE = {} # :nodoc: # rubocop:enable Style/MutableConstant - LOAD_CACHE_MUTEX = Mutex.new + LOAD_CACHE_MUTEX = Thread::Mutex.new private_constant :LOAD_CACHE if defined? private_constant diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb index 61d50a9ff8..0ab44fbf6c 100644 --- a/lib/rubygems/user_interaction.rb +++ b/lib/rubygems/user_interaction.rb @@ -543,7 +543,7 @@ class Gem::StreamUI # A progress reporter that behaves nicely with threaded downloading. class ThreadedDownloadReporter - MUTEX = Mutex.new + MUTEX = Thread::Mutex.new ## # The current file name being displayed diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb index 2a55305172..4363c5adce 100644 --- a/lib/rubygems/util.rb +++ b/lib/rubygems/util.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rubygems/deprecate' +require_relative 'deprecate' ## # This module contains various utility methods as module methods. diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb index 86d3359087..d1cbc8171a 100644 --- a/spec/bundler/bundler/definition_spec.rb +++ b/spec/bundler/bundler/definition_spec.rb @@ -126,8 +126,7 @@ RSpec.describe Bundler::Definition do only_java (1.1-java) PLATFORMS - java - #{lockfile_platforms} + #{lockfile_platforms_for(["java"] + local_platforms)} DEPENDENCIES only_java diff --git a/spec/bundler/bundler/digest_spec.rb b/spec/bundler/bundler/digest_spec.rb new file mode 100644 index 0000000000..d6bb043fd0 --- /dev/null +++ b/spec/bundler/bundler/digest_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "digest" +require "bundler/digest" + +RSpec.describe Bundler::Digest do + context "SHA1" do + subject { Bundler::Digest } + let(:stdlib) { ::Digest::SHA1 } + + it "is compatible with stdlib" do + ["foo", "skfjsdlkfjsdf", "3924m", "ldskfj"].each do |payload| + expect(subject.sha1(payload)).to be == stdlib.hexdigest(payload) + end + end + end +end diff --git a/spec/bundler/bundler/gem_helper_spec.rb b/spec/bundler/bundler/gem_helper_spec.rb index 43c9329fdb..355cb71823 100644 --- a/spec/bundler/bundler/gem_helper_spec.rb +++ b/spec/bundler/bundler/gem_helper_spec.rb @@ -219,7 +219,7 @@ RSpec.describe Bundler::GemHelper do FileUtils.touch app_gem_path app_gem_path end - expect { subject.install_gem }.to raise_error(/Couldn't install gem/) + expect { subject.install_gem }.to raise_error(/Running `#{gem_bin} install #{app_gem_path}` failed/) end end end diff --git a/spec/bundler/bundler/rubygems_integration_spec.rb b/spec/bundler/bundler/rubygems_integration_spec.rb index 11fa2f4e0d..94abf70ddd 100644 --- a/spec/bundler/bundler/rubygems_integration_spec.rb +++ b/spec/bundler/bundler/rubygems_integration_spec.rb @@ -43,12 +43,10 @@ RSpec.describe Bundler::RubygemsIntegration do describe "#download_gem" do let(:bundler_retry) { double(Bundler::Retry) } - let(:retry) { double("Bundler::Retry") } - let(:uri) { Bundler::URI.parse("https://foo.bar") } - let(:path) { Gem.path.first } + let(:uri) { Bundler::URI.parse("https://foo.bar") } + let(:cache_dir) { "#{Gem.path.first}/cache" } let(:spec) do - spec = Bundler::RemoteSpecification.new("Foo", Gem::Version.new("2.5.2"), - Gem::Platform::RUBY, nil) + spec = Gem::Specification.new("Foo", Gem::Version.new("2.5.2")) spec.remote = Bundler::Source::Rubygems::Remote.new(uri.to_s) spec end @@ -60,9 +58,9 @@ RSpec.describe Bundler::RubygemsIntegration do expect(Bundler::Retry).to receive(:new).with("download gem from #{uri}/"). and_return(bundler_retry) expect(bundler_retry).to receive(:attempts).and_yield - expect(fetcher).to receive(:download).with(spec, uri, path) + expect(fetcher).to receive(:cache_update_path) - Bundler.rubygems.download_gem(spec, uri, path) + Bundler.rubygems.download_gem(spec, uri, cache_dir) end end diff --git a/spec/bundler/cache/gems_spec.rb b/spec/bundler/cache/gems_spec.rb index 72e372fb41..a8382a5d8c 100644 --- a/spec/bundler/cache/gems_spec.rb +++ b/spec/bundler/cache/gems_spec.rb @@ -89,35 +89,33 @@ RSpec.describe "bundle cache" do it_behaves_like "when there are only gemsources" end - describe "when there is a built-in gem" do + describe "when there is a built-in gem", :ruby_repo do + let(:default_json_version) { ruby "gem 'json'; require 'json'; puts JSON::VERSION" } + before :each do build_repo2 do - build_gem "builtin_gem", "1.0.2" - end - - build_gem "builtin_gem", "1.0.2", :to_system => true do |s| - s.summary = "This builtin_gem is bundled with Ruby" + build_gem "json", default_json_version end - FileUtils.rm("#{system_gem_path}/cache/builtin_gem-1.0.2.gem") + build_gem "json", default_json_version, :to_system => true, :default => true end it "uses builtin gems when installing to system gems" do bundle "config set path.system true" - install_gemfile %(source "#{file_uri_for(gem_repo1)}"; gem 'builtin_gem', '1.0.2') - expect(the_bundle).to include_gems("builtin_gem 1.0.2") + install_gemfile %(source "#{file_uri_for(gem_repo1)}"; gem 'json', '#{default_json_version}'), :verbose => true + expect(out).to include("Using json #{default_json_version}") end it "caches remote and builtin gems" do install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" - gem 'builtin_gem', '1.0.2' + gem 'json', '#{default_json_version}' gem 'rack', '1.0.0' G bundle :cache expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist - expect(bundled_app("vendor/cache/builtin_gem-1.0.2.gem")).to exist + expect(bundled_app("vendor/cache/json-#{default_json_version}.gem")).to exist end it "doesn't make remote request after caching the gem" do @@ -139,12 +137,12 @@ RSpec.describe "bundle cache" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem 'builtin_gem', '1.0.2' + gem 'json', '#{default_json_version}' G bundle :cache, :raise_on_error => false expect(exitstatus).to_not eq(0) - expect(err).to include("builtin_gem-1.0.2 is built in to Ruby, and can't be cached") + expect(err).to include("json-#{default_json_version} is built in to Ruby, and can't be cached") end end diff --git a/spec/bundler/commands/binstubs_spec.rb b/spec/bundler/commands/binstubs_spec.rb index c8eef55266..1cd0e16d95 100644 --- a/spec/bundler/commands/binstubs_spec.rb +++ b/spec/bundler/commands/binstubs_spec.rb @@ -116,7 +116,7 @@ RSpec.describe "bundle binstubs <gem>" do s.executables = "print_loaded_gems" s.bindir = "exe" s.write "exe/print_loaded_gems", <<-R - specs = Gem.loaded_specs.values.reject {|s| Bundler.rubygems.spec_default_gem?(s) } + specs = Gem.loaded_specs.values.reject {|s| s.default_gem? } puts specs.map(&:full_name).sort.inspect R end diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index 429fb17d82..9cce998416 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -236,7 +236,7 @@ RSpec.describe "bundle clean" do bundle "config set path vendor/bundle" bundle "install" - update_git "foo", :path => lib_path("foo-bar") + update_git "foo-bar", :path => lib_path("foo-bar") revision2 = revision_for(lib_path("foo-bar")) bundle "update", :all => true diff --git a/spec/bundler/commands/info_spec.rb b/spec/bundler/commands/info_spec.rb index 906349cacf..518f93511a 100644 --- a/spec/bundler/commands/info_spec.rb +++ b/spec/bundler/commands/info_spec.rb @@ -60,7 +60,15 @@ RSpec.describe "bundle info" do bundle "info rails --path" - expect(err).to match(/has been deleted/i) + expect(err).to match(/The gem rails has been deleted/i) + expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s) + + bundle "info rail --path" + expect(err).to match(/The gem rails has been deleted/i) + expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s) + + bundle "info rails" + expect(err).to match(/The gem rails has been deleted/i) expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s) end diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 35c45b68b7..2c7f0360e6 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -94,6 +94,21 @@ RSpec.describe "bundle install with gem sources" do expect(the_bundle).to include_gems("rack 1.0.0") end + it "auto-heals missing gems" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem 'rack' + G + + FileUtils.rm_rf(default_bundle_path("gems/rack-1.0.0")) + + bundle "install --verbose" + + expect(out).to include("Installing rack 1.0.0") + expect(default_bundle_path("gems/rack-1.0.0")).to exist + expect(the_bundle).to include_gems("rack 1.0.0") + end + it "fetches gems when multiple versions are specified" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -663,6 +678,77 @@ RSpec.describe "bundle install with gem sources" do end end + describe "when bundle gems path does not have write access", :permissions do + let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } + + before do + FileUtils.mkdir_p(gems_path) + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem 'rack' + G + end + + it "should display a proper message to explain the problem" do + FileUtils.chmod("-x", gems_path) + bundle "config set --local path vendor" + + begin + bundle :install, :raise_on_error => false + ensure + FileUtils.chmod("+x", gems_path) + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") + + expect(err).to include( + "There was an error while trying to create `#{gems_path.join("rack-1.0.0")}`. " \ + "It is likely that you need to grant executable permissions for all parent directories and write permissions for `#{gems_path}`." + ) + end + end + + describe "when the path of a specific gem is not writable", :permissions do + let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } + let(:foo_path) { gems_path.join("foo-1.0.0") } + + before do + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.write "CHANGELOG.md", "foo" + end + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'foo' + G + end + + it "should display a proper message to explain the problem" do + bundle "config set --local path vendor" + bundle :install + expect(out).to include("Bundle complete!") + expect(err).to be_empty + + FileUtils.chmod("-x", foo_path) + + begin + bundle "install --redownload", :raise_on_error => false + ensure + FileUtils.chmod("+x", foo_path) + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") + + expect(err).to include( + "There was an error while trying to delete `#{foo_path}`. " \ + "It is likely that you need to grant executable permissions for all parent directories " \ + "and write permissions for `#{gems_path}`, and the same thing for all subdirectories inside #{foo_path}." + ) + end + end + describe "when bundle cache path does not have write access", :permissions do let(:cache_path) { bundled_app("vendor/#{Bundler.ruby_scope}/cache") } @@ -760,6 +846,101 @@ RSpec.describe "bundle install with gem sources" do end end + context "with missing platform specific gems in lockfile" do + before do + build_repo4 do + build_gem "racc", "1.5.2" + + build_gem "nokogiri", "1.12.4" do |s| + s.platform = "x86_64-darwin" + s.add_runtime_dependency "racc", "~> 1.4" + end + + build_gem "nokogiri", "1.12.4" do |s| + s.platform = "x86_64-linux" + s.add_runtime_dependency "racc", "~> 1.4" + end + + build_gem "crass", "1.0.6" + + build_gem "loofah", "2.12.0" do |s| + s.add_runtime_dependency "crass", "~> 1.0.2" + s.add_runtime_dependency "nokogiri", ">= 1.5.9" + end + end + + gemfile <<-G + source "https://gem.repo4" + + ruby "#{RUBY_VERSION}" + + gem "loofah", "~> 2.12.0" + G + + lockfile <<-L + GEM + remote: https://gem.repo4/ + specs: + crass (1.0.6) + loofah (2.12.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + nokogiri (1.12.4-x86_64-darwin) + racc (~> 1.4) + racc (1.5.2) + + PLATFORMS + x86_64-darwin-20 + x86_64-linux + + DEPENDENCIES + loofah (~> 2.12.0) + + RUBY VERSION + #{Bundler::RubyVersion.system} + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile" do + bundle "config set --local path vendor/bundle" + + simulate_platform "x86_64-linux" do + bundle "install", :artifice => "compact_index" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + crass (1.0.6) + loofah (2.12.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + nokogiri (1.12.4-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.12.4-x86_64-linux) + racc (~> 1.4) + racc (1.5.2) + + PLATFORMS + x86_64-darwin-20 + x86_64-linux + + DEPENDENCIES + loofah (~> 2.12.0) + + RUBY VERSION + #{Bundler::RubyVersion.system} + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + context "with --local flag" do before do system_gems "rack-1.0.0", :path => default_bundle_path diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb index 5ea9eee878..df2650989f 100644 --- a/spec/bundler/install/gemfile/git_spec.rb +++ b/spec/bundler/install/gemfile/git_spec.rb @@ -232,7 +232,7 @@ RSpec.describe "bundle install with git sources" do # want to ensure we don't fallback to HEAD update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s| - s.write("lib/foo.rb", "raise 'FAIL'") + s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'") end install_gemfile <<-G @@ -268,7 +268,7 @@ RSpec.describe "bundle install with git sources" do # want to ensure we don't fallback to HEAD update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s| - s.write("lib/foo.rb", "raise 'FAIL'") + s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'") end install_gemfile <<-G diff --git a/spec/bundler/install/gemspecs_spec.rb b/spec/bundler/install/gemspecs_spec.rb index 0c4518fe2b..3684d8749d 100644 --- a/spec/bundler/install/gemspecs_spec.rb +++ b/spec/bundler/install/gemspecs_spec.rb @@ -34,6 +34,8 @@ RSpec.describe "bundle install" do gem 'rack' G + system_gems "rack-1.0.0", :path => default_bundle_path + FileUtils.mkdir_p "#{default_bundle_path}/specifications" File.open("#{default_bundle_path}/specifications/rack-1.0.0.gemspec", "w+") do |f| spec = Gem::Specification.new do |s| @@ -44,7 +46,7 @@ RSpec.describe "bundle install" do f.write spec.to_ruby end bundle :install, :artifice => "endpoint_marshal_fail" # force gemspec load - expect(the_bundle).to include_gems "activesupport 2.3.2" + expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.2" end it "does not hang when gemspec has incompatible encoding" do diff --git a/spec/bundler/install/global_cache_spec.rb b/spec/bundler/install/global_cache_spec.rb index 9bc243e7cf..afa0ff76c1 100644 --- a/spec/bundler/install/global_cache_spec.rb +++ b/spec/bundler/install/global_cache_spec.rb @@ -37,6 +37,18 @@ RSpec.describe "global gem caching" do expect(the_bundle).to include_gems "rack 1.0.0" end + it "shows a proper error message if a cached gem is corrupted" do + source_global_cache.mkpath + FileUtils.touch(source_global_cache("rack-1.0.0.gem")) + + install_gemfile <<-G, :artifice => "compact_index_no_gem", :raise_on_error => false + source "#{source}" + gem "rack" + G + + expect(err).to include("Gem::Package::FormatError: package metadata is missing in #{source_global_cache("rack-1.0.0.gem")}") + end + describe "when the same gem from different sources is installed" do it "should use the appropriate one from the global cache" do install_gemfile <<-G, :artifice => "compact_index" @@ -113,7 +125,7 @@ RSpec.describe "global gem caching" do expect(source2_global_cache("rack-0.9.1.gem")).to exist bundle :install, :artifice => "compact_index_no_gem", :raise_on_error => false expect(err).to include("Internal Server Error 500") - expect(err).not_to include("please copy and paste the report template above into a new issue") + expect(err).not_to include("ERROR REPORT TEMPLATE") # rack 1.0.0 is not installed and rack 0.9.1 is not expect(the_bundle).not_to include_gems "rack 1.0.0" @@ -128,7 +140,7 @@ RSpec.describe "global gem caching" do expect(source2_global_cache("rack-0.9.1.gem")).to exist bundle :install, :artifice => "compact_index_no_gem", :raise_on_error => false expect(err).to include("Internal Server Error 500") - expect(err).not_to include("please copy and paste the report template above into a new issue") + expect(err).not_to include("ERROR REPORT TEMPLATE") # rack 0.9.1 is not installed and rack 1.0.0 is not expect(the_bundle).not_to include_gems "rack 0.9.1" diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb index 0e2a3a3cd8..4e9e0f5fc3 100644 --- a/spec/bundler/lock/lockfile_spec.rb +++ b/spec/bundler/lock/lockfile_spec.rb @@ -984,8 +984,7 @@ RSpec.describe "the lockfile format" do rack (1.0.0) PLATFORMS - java - #{lockfile_platforms} + #{lockfile_platforms_for(["java"] + local_platforms)} DEPENDENCIES rack diff --git a/spec/bundler/runtime/platform_spec.rb b/spec/bundler/runtime/platform_spec.rb index d81bccbdf8..a89659d6ce 100644 --- a/spec/bundler/runtime/platform_spec.rb +++ b/spec/bundler/runtime/platform_spec.rb @@ -86,8 +86,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do racc (1.5.2) PLATFORMS - ruby - #{Bundler.local_platform} + #{lockfile_platforms_for(["ruby"] + local_platforms)} DEPENDENCIES nokogiri (~> 1.11) diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb index 42bbacea0e..367ef9c711 100644 --- a/spec/bundler/runtime/setup_spec.rb +++ b/spec/bundler/runtime/setup_spec.rb @@ -1228,6 +1228,41 @@ end end describe "with gemified standard libraries" do + it "does not load Digest", :ruby_repo do + skip "Only for Ruby 3.0+" unless RUBY_VERSION >= "3.0" + + build_git "bar", :gemspec => false do |s| + s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0') + s.write "bar.gemspec", <<-G + require_relative 'lib/bar/version' + + Gem::Specification.new do |s| + s.name = 'bar' + s.version = BAR_VERSION + s.summary = 'Bar' + s.files = Dir["lib/**/*.rb"] + s.author = 'no one' + + s.add_runtime_dependency 'digest' + end + G + end + + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "bar", :git => "#{lib_path("bar-1.0")}" + G + + bundle :install + + ruby <<-RUBY + require '#{entrypoint}/setup' + puts defined?(::Digest) ? "Digest defined" : "Digest undefined" + require 'digest' + RUBY + expect(out).to eq("Digest undefined") + end + it "does not load Psych" do gemfile "source \"#{file_uri_for(gem_repo1)}\"" ruby <<-RUBY diff --git a/spec/bundler/support/artifice/compact_index_rate_limited.rb b/spec/bundler/support/artifice/compact_index_rate_limited.rb index ba17476045..570105e2a0 100644 --- a/spec/bundler/support/artifice/compact_index_rate_limited.rb +++ b/spec/bundler/support/artifice/compact_index_rate_limited.rb @@ -7,7 +7,7 @@ Artifice.deactivate class CompactIndexRateLimited < CompactIndexAPI class RequestCounter def self.queue - @queue ||= Queue.new + @queue ||= Thread::Queue.new end def self.size diff --git a/spec/bundler/support/artifice/endpoint.rb b/spec/bundler/support/artifice/endpoint.rb index 37ca378ef9..4a820e5a3f 100644 --- a/spec/bundler/support/artifice/endpoint.rb +++ b/spec/bundler/support/artifice/endpoint.rb @@ -8,7 +8,7 @@ require "artifice" require "sinatra/base" ALL_REQUESTS = [] # rubocop:disable Style/MutableConstant -ALL_REQUESTS_MUTEX = Mutex.new +ALL_REQUESTS_MUTEX = Thread::Mutex.new at_exit do if expected = ENV["BUNDLER_SPEC_ALL_REQUESTS"] diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index 80e3d47b0b..90e9cbb242 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -553,17 +553,8 @@ module Spec update_gemspec = options[:gemspec] || false source = options[:source] || "git@#{libpath}" - @context.git "checkout master", libpath - if branch = options[:branch] - raise "You can't specify `master` as the branch" if branch == "master" - escaped_branch = Shellwords.shellescape(branch) - - if @context.git("branch -l #{escaped_branch}", libpath).empty? - @context.git("branch #{escaped_branch}", libpath) - end - - @context.git("checkout #{escaped_branch}", libpath) + @context.git("checkout -b #{Shellwords.shellescape(branch)}", libpath) elsif tag = options[:tag] @context.git("tag #{Shellwords.shellescape(tag)}", libpath) elsif options[:remote] @@ -577,8 +568,7 @@ module Spec _default_files[path] += "\n#{Builders.constantize(name)}_PREV_REF = '#{current_ref}'" end super(options.merge(:path => libpath, :gemspec => update_gemspec, :source => source)) - @context.git("add *", libpath) - @context.git("commit -m BUMP", libpath, :raise_on_error => false) + @context.git("commit -am BUMP", libpath) end end diff --git a/spec/bundler/support/platforms.rb b/spec/bundler/support/platforms.rb index 0cb7f7cd29..07973fd727 100644 --- a/spec/bundler/support/platforms.rb +++ b/spec/bundler/support/platforms.rb @@ -90,7 +90,11 @@ module Spec end def lockfile_platforms - local_platforms.map(&:to_s).sort.join("\n ") + lockfile_platforms_for(local_platforms) + end + + def lockfile_platforms_for(platforms) + platforms.map(&:to_s).sort.join("\n ") end def local_platforms diff --git a/spec/bundler/update/git_spec.rb b/spec/bundler/update/git_spec.rb index 26ad1b45d3..f35d5857c6 100644 --- a/spec/bundler/update/git_spec.rb +++ b/spec/bundler/update/git_spec.rb @@ -13,7 +13,7 @@ RSpec.describe "bundle update" do end G - update_git "foo", :branch => "omg" do |s| + update_git "foo" do |s| s.write "lib/foo.rb", "FOO = '1.1'" end @@ -48,7 +48,7 @@ RSpec.describe "bundle update" do end G - update_git "foo", :branch => "omg", :path => lib_path("foo") do |s| + update_git "foo", :path => lib_path("foo") do |s| s.write "lib/foo.rb", "FOO = '1.1'" end diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index ce05c9cf30..c074f4bb15 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -827,16 +827,6 @@ class Gem::TestCase < Test::Unit::TestCase Gem::Specification.unresolved_deps.values.map(&:to_s).sort end - def save_loaded_features - old_loaded_features = $LOADED_FEATURES.dup - yield - ensure - prefix = File.dirname(__FILE__) + "/" - new_features = ($LOADED_FEATURES - old_loaded_features) - old_loaded_features.concat(new_features.select {|f| f.rindex(prefix, 0) }) - $LOADED_FEATURES.replace old_loaded_features - end - def new_default_spec(name, version, deps = nil, *files) spec = util_spec name, version, deps @@ -1531,14 +1521,14 @@ Also, a list: end ## - # Loads an RSA private key named +key_name+ with +passphrase+ in <tt>test/rubygems/</tt> + # Loads a private key named +key_name+ with +passphrase+ in <tt>test/rubygems/</tt> def self.load_key(key_name, passphrase = nil) key_file = key_path key_name key = File.read key_file - OpenSSL::PKey::RSA.new key, passphrase + OpenSSL::PKey.read key, passphrase end ## diff --git a/test/rubygems/private_ec_key.pem b/test/rubygems/private_ec_key.pem new file mode 100644 index 0000000000..5d855d0dfc --- /dev/null +++ b/test/rubygems/private_ec_key.pem @@ -0,0 +1,9 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,4107F98A374CB8EC18F1AA4EA4B6A0DB + +BRklFxJGcz7gqQYxek8TZkt8qbPhB0FSR6nyw3SYuio/2tlT9ohs74mlK3EbG9Lt +Y4OquJbksBFmoB7fIoM4vnuIZ0Eoz2ooxn9tjhBtqJ3mVscYXwZmA3UDUWDMlviQ +Fu37OpikQv4TFA1jlmUK0LM8xmUCfUeLl0kHD17lFsz2gkO2kwg8mn/YUMOIaDOu +EnnmxbAwnZBpemQkQfpTt2mYL9gu3CcMt5gokBuGDxY= +-----END EC PRIVATE KEY----- diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb index da154dac75..832701a173 100644 --- a/test/rubygems/test_gem.rb +++ b/test/rubygems/test_gem.rb @@ -25,72 +25,66 @@ class TestGem < Gem::TestCase end def test_self_finish_resolve - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" - b2 = util_spec "b", "2", "c" => ">= 2" - c1 = util_spec "c", "1" - c2 = util_spec "c", "2" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" + b2 = util_spec "b", "2", "c" => ">= 2" + c1 = util_spec "c", "1" + c2 = util_spec "c", "2" - install_specs c1, c2, b1, b2, a1 + install_specs c1, c2, b1, b2, a1 - a1.activate + a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + assert_equal %w[a-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - Gem.finish_resolve + Gem.finish_resolve - assert_equal %w[a-1 b-2 c-2], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-1 b-2 c-2], loaded_spec_names + assert_equal [], unresolved_names end def test_self_finish_resolve_wtf - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0", "d" => "> 0" # this - b1 = util_spec "b", "1", { "c" => ">= 1" }, "lib/b.rb" # this - b2 = util_spec "b", "2", { "c" => ">= 2" }, "lib/b.rb" - c1 = util_spec "c", "1" # this - c2 = util_spec "c", "2" - d1 = util_spec "d", "1", { "c" => "< 2" }, "lib/d.rb" - d2 = util_spec "d", "2", { "c" => "< 2" }, "lib/d.rb" # this + a1 = util_spec "a", "1", "b" => "> 0", "d" => "> 0" # this + b1 = util_spec "b", "1", { "c" => ">= 1" }, "lib/b.rb" # this + b2 = util_spec "b", "2", { "c" => ">= 2" }, "lib/b.rb" + c1 = util_spec "c", "1" # this + c2 = util_spec "c", "2" + d1 = util_spec "d", "1", { "c" => "< 2" }, "lib/d.rb" + d2 = util_spec "d", "2", { "c" => "< 2" }, "lib/d.rb" # this - install_specs c1, c2, b1, b2, d1, d2, a1 + install_specs c1, c2, b1, b2, d1, d2, a1 - a1.activate + a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal ["b (> 0)", "d (> 0)"], unresolved_names + assert_equal %w[a-1], loaded_spec_names + assert_equal ["b (> 0)", "d (> 0)"], unresolved_names - Gem.finish_resolve + Gem.finish_resolve - assert_equal %w[a-1 b-1 c-1 d-2], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-1 b-1 c-1 d-2], loaded_spec_names + assert_equal [], unresolved_names end def test_self_finish_resolve_respects_loaded_specs - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" - b2 = util_spec "b", "2", "c" => ">= 2" - c1 = util_spec "c", "1" - c2 = util_spec "c", "2" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" + b2 = util_spec "b", "2", "c" => ">= 2" + c1 = util_spec "c", "1" + c2 = util_spec "c", "2" - install_specs c1, c2, b1, b2, a1 + install_specs c1, c2, b1, b2, a1 - a1.activate - c1.activate + a1.activate + c1.activate - assert_equal %w[a-1 c-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + assert_equal %w[a-1 c-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - Gem.finish_resolve + Gem.finish_resolve - assert_equal %w[a-1 b-1 c-1], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-1 b-1 c-1], loaded_spec_names + assert_equal [], unresolved_names end def test_self_install @@ -210,25 +204,21 @@ class TestGem < Gem::TestCase end def test_require_missing - save_loaded_features do - assert_raise ::LoadError do - require "test_require_missing" - end + assert_raise ::LoadError do + require "test_require_missing" end end def test_require_does_not_glob - save_loaded_features do - a1 = util_spec "a", "1", nil, "lib/a1.rb" + a1 = util_spec "a", "1", nil, "lib/a1.rb" - install_specs a1 - - assert_raise ::LoadError do - require "a*" - end + install_specs a1 - assert_equal [], loaded_spec_names + assert_raise ::LoadError do + require "a*" end + + assert_equal [], loaded_spec_names end def test_self_bin_path_active @@ -1483,24 +1473,22 @@ class TestGem < Gem::TestCase end def test_self_needs_picks_up_unresolved_deps - save_loaded_features do - a = util_spec "a", "1" - b = util_spec "b", "1", "c" => nil - c = util_spec "c", "2" - d = util_spec "d", "1", {'e' => '= 1'}, "lib/d#{$$}.rb" - e = util_spec "e", "1" - - install_specs a, c, b, e, d + a = util_spec "a", "1" + b = util_spec "b", "1", "c" => nil + c = util_spec "c", "2" + d = util_spec "d", "1", {'e' => '= 1'}, "lib/d#{$$}.rb" + e = util_spec "e", "1" - Gem.needs do |r| - r.gem "a" - r.gem "b", "= 1" + install_specs a, c, b, e, d - require "d#{$$}" - end + Gem.needs do |r| + r.gem "a" + r.gem "b", "= 1" - assert_equal %w[a-1 b-1 c-2 d-1 e-1], loaded_spec_names + require "d#{$$}" end + + assert_equal %w[a-1 b-1 c-2 d-1 e-1], loaded_spec_names end def test_self_gunzip diff --git a/test/rubygems/test_gem_commands_cert_command.rb b/test/rubygems/test_gem_commands_cert_command.rb index f722678a19..077be11d55 100644 --- a/test/rubygems/test_gem_commands_cert_command.rb +++ b/test/rubygems/test_gem_commands_cert_command.rb @@ -14,9 +14,10 @@ class TestGemCommandsCertCommand < Gem::TestCase ALTERNATE_CERT = load_cert 'alternate' EXPIRED_PUBLIC_CERT = load_cert 'expired' - ALTERNATE_KEY_FILE = key_path 'alternate' - PRIVATE_KEY_FILE = key_path 'private' - PUBLIC_KEY_FILE = key_path 'public' + ALTERNATE_KEY_FILE = key_path 'alternate' + PRIVATE_KEY_FILE = key_path 'private' + PRIVATE_EC_KEY_FILE = key_path 'private_ec' + PUBLIC_KEY_FILE = key_path 'public' ALTERNATE_CERT_FILE = cert_path 'alternate' CHILD_CERT_FILE = cert_path 'child' @@ -142,6 +143,42 @@ Added '/CN=alternate/DC=example' assert_path_exist File.join(@tempdir, 'gem-public_cert.pem') end + def test_execute_build_key_algorithm_ec_key + passphrase = 'Foo bar' + + @cmd.handle_options %W[--build nobody@example.com --key-algorithm ec] + + @build_ui = Gem::MockGemUi.new "#{passphrase}\n#{passphrase}" + + use_ui @build_ui do + @cmd.execute + end + + output = @build_ui.output.squeeze("\n").split "\n" + + assert_equal "Passphrase for your Private Key: ", + output.shift + assert_equal "Please repeat the passphrase for your Private Key: ", + output.shift + assert_equal "Certificate: #{File.join @tempdir, 'gem-public_cert.pem'}", + output.shift + assert_equal "Private Key: #{File.join @tempdir, 'gem-private_key.pem'}", + output.shift + + assert_equal "Don't forget to move the key file to somewhere private!", + output.shift + + assert_empty output + assert_empty @build_ui.error + + assert_path_exist File.join(@tempdir, 'gem-private_key.pem') + + cert_path = File.join(@tempdir, 'gem-public_cert.pem') + assert_path_exist cert_path + cert = OpenSSL::X509::Certificate.new(File.read(cert_path)) + assert cert.public_key.is_a? OpenSSL::PKey::EC + end + def test_execute_build_bad_email_address passphrase = 'Foo bar' email = "nobody@" @@ -279,6 +316,28 @@ Added '/CN=alternate/DC=example' assert_path_exist File.join(@tempdir, 'gem-public_cert.pem') end + def test_execute_build_ec_key + @cmd.handle_options %W[ + --build nobody@example.com + --private-key #{PRIVATE_EC_KEY_FILE} + ] + + use_ui @ui do + @cmd.execute + end + + output = @ui.output.split "\n" + + assert_equal "Certificate: #{File.join @tempdir, 'gem-public_cert.pem'}", + output.shift + + assert_empty output + assert_empty @ui.error + + assert_path_exist File.join(@tempdir, 'gem-public_cert.pem') + assert_path_not_exist File.join(@tempdir, 'gem-private_key.pem') + end + def test_execute_certificate use_ui @ui do @cmd.handle_options %W[--certificate #{PUBLIC_CERT_FILE}] @@ -742,7 +801,7 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis @cmd.handle_options %W[--private-key #{bad}] end - assert_equal "invalid argument: --private-key #{bad}: invalid RSA key", + assert_equal "invalid argument: --private-key #{bad}: invalid RSA, DSA, or EC key", e.message e = assert_raise OptionParser::InvalidArgument do diff --git a/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb index 3fa2c1911c..48dcbee9f1 100644 --- a/test/rubygems/test_gem_package.rb +++ b/test/rubygems/test_gem_package.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require_relative 'package/tar_test_case' -require 'digest' +require 'rubygems/openssl' class TestGemPackage < Gem::Package::TarTestCase def setup @@ -84,17 +84,17 @@ class TestGemPackage < Gem::Package::TarTestCase io.write spec.to_yaml end - metadata_sha256 = Digest::SHA256.hexdigest s.string - metadata_sha512 = Digest::SHA512.hexdigest s.string + metadata_sha256 = OpenSSL::Digest::SHA256.hexdigest s.string + metadata_sha512 = OpenSSL::Digest::SHA512.hexdigest s.string expected = { 'SHA512' => { 'metadata.gz' => metadata_sha512, - 'data.tar.gz' => Digest::SHA512.hexdigest(tar), + 'data.tar.gz' => OpenSSL::Digest::SHA512.hexdigest(tar), }, 'SHA256' => { 'metadata.gz' => metadata_sha256, - 'data.tar.gz' => Digest::SHA256.hexdigest(tar), + 'data.tar.gz' => OpenSSL::Digest::SHA256.hexdigest(tar), }, } @@ -574,18 +574,19 @@ class TestGemPackage < Gem::Package::TarTestCase destination_subdir = File.join @destination, 'subdir' FileUtils.mkdir_p destination_subdir - e = assert_raise(Gem::Package::PathError, Errno::EACCES) do + expected_exceptions = win_platform? ? [Gem::Package::SymlinkError, Errno::EACCES] : [Gem::Package::SymlinkError] + + e = assert_raise(*expected_exceptions) do package.extract_tar_gz tgz_io, destination_subdir end - if Gem::Package::PathError === e - assert_equal("installing into parent path lib/link/outside.txt of " + - "#{destination_subdir} is not allowed", e.message) - elsif win_platform? - pend "symlink - must be admin with no UAC on Windows" - else - raise e - end + pend "symlink - must be admin with no UAC on Windows" if Errno::EACCES === e + + assert_equal("installing symlink 'lib/link' pointing to parent path #{@destination} of " + + "#{destination_subdir} is not allowed", e.message) + + assert_path_not_exist File.join(@destination, "outside.txt") + assert_path_not_exist File.join(destination_subdir, "lib/link") end def test_extract_symlink_parent_doesnt_delete_user_dir @@ -608,20 +609,20 @@ class TestGemPackage < Gem::Package::TarTestCase tar.add_symlink 'link/dir', '.', 16877 end - e = assert_raise(Gem::Package::PathError, Errno::EACCES) do + expected_exceptions = win_platform? ? [Gem::Package::SymlinkError, Errno::EACCES] : [Gem::Package::SymlinkError] + + e = assert_raise(*expected_exceptions) do package.extract_tar_gz tgz_io, destination_subdir end - assert_path_exist destination_user_subdir + pend "symlink - must be admin with no UAC on Windows" if Errno::EACCES === e - if Gem::Package::PathError === e - assert_equal("installing into parent path #{destination_user_subdir} of " + - "#{destination_subdir} is not allowed", e.message) - elsif win_platform? - pend "symlink - must be admin with no UAC on Windows" - else - raise e - end + assert_equal("installing symlink 'link' pointing to parent path #{destination_user_dir} of " + + "#{destination_subdir} is not allowed", e.message) + + assert_path_exist destination_user_subdir + assert_path_not_exist File.join(destination_subdir, "link/dir") + assert_path_not_exist File.join(destination_subdir, "link") end def test_extract_tar_gz_directory @@ -856,7 +857,7 @@ class TestGemPackage < Gem::Package::TarTestCase io.write metadata_gz end - digest = Digest::SHA1.new + digest = OpenSSL::Digest::SHA1.new digest << metadata_gz checksums = { @@ -1015,7 +1016,7 @@ class TestGemPackage < Gem::Package::TarTestCase bogus_data = Gem::Util.gzip 'hello' fake_signer = Class.new do def digest_name; 'SHA512'; end - def digest_algorithm; Digest(:SHA512).new; end + def digest_algorithm; OpenSSL::Digest(:SHA512).new; end def key; 'key'; end def sign(*); 'fake_sig'; end end diff --git a/test/rubygems/test_gem_security.rb b/test/rubygems/test_gem_security.rb index 2eabbea3bf..d04bd4a8bd 100644 --- a/test/rubygems/test_gem_security.rb +++ b/test/rubygems/test_gem_security.rb @@ -12,6 +12,7 @@ end class TestGemSecurity < Gem::TestCase CHILD_KEY = load_key 'child' + EC_KEY = load_key 'private_ec', 'Foo bar' ALTERNATE_CERT = load_cert 'child' CHILD_CERT = load_cert 'child' @@ -103,11 +104,38 @@ class TestGemSecurity < Gem::TestCase end def test_class_create_key - key = @SEC.create_key 1024 + key = @SEC.create_key 'rsa' assert_kind_of OpenSSL::PKey::RSA, key end + def test_class_create_key_downcases + key = @SEC.create_key 'DSA' + + assert_kind_of OpenSSL::PKey::DSA, key + end + + def test_class_create_key_raises_unknown_algorithm + e = assert_raise Gem::Security::Exception do + @SEC.create_key 'NOT_RSA' + end + + assert_equal "NOT_RSA algorithm not found. RSA, DSA, and EC algorithms are supported.", + e.message + end + + def test_class_get_public_key_rsa + pkey_pem = PRIVATE_KEY.public_key.to_pem + + assert_equal pkey_pem, @SEC.get_public_key(PRIVATE_KEY).to_pem + end + + def test_class_get_public_key_ec + pkey = @SEC.get_public_key(EC_KEY) + + assert_respond_to pkey, :to_pem + end + def test_class_email_to_name assert_equal '/CN=nobody/DC=example', @SEC.email_to_name('nobody@example').to_s @@ -259,7 +287,7 @@ class TestGemSecurity < Gem::TestCase end def test_class_write - key = @SEC.create_key 1024 + key = @SEC.create_key 'rsa' path = File.join @tempdir, 'test-private_key.pem' @@ -273,7 +301,7 @@ class TestGemSecurity < Gem::TestCase end def test_class_write_encrypted - key = @SEC.create_key 1024 + key = @SEC.create_key 'rsa' path = File.join @tempdir, 'test-private_encrypted_key.pem' @@ -289,7 +317,7 @@ class TestGemSecurity < Gem::TestCase end def test_class_write_encrypted_cipher - key = @SEC.create_key 1024 + key = @SEC.create_key 'rsa' path = File.join @tempdir, 'test-private_encrypted__with_non_default_cipher_key.pem' diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index 336fcf000e..582813c01d 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -127,265 +127,241 @@ end end def test_self_activate_ambiguous_direct - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec("b", "1", { "c" => ">= 1" }, "lib/d#{$$}.rb") - b2 = util_spec("b", "2", { "c" => ">= 2" }, "lib/d#{$$}.rb") - c1 = util_spec "c", "1" - c2 = util_spec "c", "2" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec("b", "1", { "c" => ">= 1" }, "lib/d#{$$}.rb") + b2 = util_spec("b", "2", { "c" => ">= 2" }, "lib/d#{$$}.rb") + c1 = util_spec "c", "1" + c2 = util_spec "c", "2" - Gem::Specification.reset - install_specs c1, c2, b1, b2, a1 + Gem::Specification.reset + install_specs c1, c2, b1, b2, a1 - a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + a1.activate + assert_equal %w[a-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-1 b-2 c-2], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-1 b-2 c-2], loaded_spec_names + assert_equal [], unresolved_names end def test_find_in_unresolved_tree_is_not_exponentiental - save_loaded_features do - num_of_pkg = 7 - num_of_version_per_pkg = 3 - packages = (0..num_of_pkg).map do |pkgi| - (0..num_of_version_per_pkg).map do |pkg_version| - deps = Hash[((pkgi + 1)..num_of_pkg).map do |deppkgi| - ["pkg#{deppkgi}", ">= 0"] - end] - util_spec "pkg#{pkgi}", pkg_version.to_s, deps - end + num_of_pkg = 7 + num_of_version_per_pkg = 3 + packages = (0..num_of_pkg).map do |pkgi| + (0..num_of_version_per_pkg).map do |pkg_version| + deps = Hash[((pkgi + 1)..num_of_pkg).map do |deppkgi| + ["pkg#{deppkgi}", ">= 0"] + end] + util_spec "pkg#{pkgi}", pkg_version.to_s, deps end - base = util_spec "pkg_base", "1", {"pkg0" => ">= 0"} + end + base = util_spec "pkg_base", "1", {"pkg0" => ">= 0"} - Gem::Specification.reset - install_specs(*packages.flatten.reverse) - install_specs base - base.activate + Gem::Specification.reset + install_specs(*packages.flatten.reverse) + install_specs base + base.activate - tms = Benchmark.measure do - assert_raise(LoadError) { require 'no_such_file_foo' } - end - assert_operator tms.total, :<=, 10 + tms = Benchmark.measure do + assert_raise(LoadError) { require 'no_such_file_foo' } end + assert_operator tms.total, :<=, 10 end def test_self_activate_ambiguous_indirect - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" - b2 = util_spec "b", "2", "c" => ">= 2" - c1 = util_spec "c", "1", nil, "lib/d#{$$}.rb" - c2 = util_spec "c", "2", nil, "lib/d#{$$}.rb" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" + b2 = util_spec "b", "2", "c" => ">= 2" + c1 = util_spec "c", "1", nil, "lib/d#{$$}.rb" + c2 = util_spec "c", "2", nil, "lib/d#{$$}.rb" - install_specs c1, c2, b1, b2, a1 + install_specs c1, c2, b1, b2, a1 - a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + a1.activate + assert_equal %w[a-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-1 b-2 c-2], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-1 b-2 c-2], loaded_spec_names + assert_equal [], unresolved_names end def test_self_activate_ambiguous_indirect_conflict - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - a2 = util_spec "a", "2", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" - b2 = util_spec "b", "2", "c" => ">= 2" - c1 = util_spec "c", "1", nil, "lib/d#{$$}.rb" - c2 = util_spec("c", "2", { "a" => "1" }, "lib/d#{$$}.rb") # conflicts with a-2 + a1 = util_spec "a", "1", "b" => "> 0" + a2 = util_spec "a", "2", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" + b2 = util_spec "b", "2", "c" => ">= 2" + c1 = util_spec "c", "1", nil, "lib/d#{$$}.rb" + c2 = util_spec("c", "2", { "a" => "1" }, "lib/d#{$$}.rb") # conflicts with a-2 - install_specs c1, b1, a1, a2, c2, b2 + install_specs c1, b1, a1, a2, c2, b2 - a2.activate - assert_equal %w[a-2], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + a2.activate + assert_equal %w[a-2], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-2 b-1 c-1], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-2 b-1 c-1], loaded_spec_names + assert_equal [], unresolved_names end def test_self_activate_ambiguous_unrelated - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" - b2 = util_spec "b", "2", "c" => ">= 2" - c1 = util_spec "c", "1" - c2 = util_spec "c", "2" - d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" + b2 = util_spec "b", "2", "c" => ">= 2" + c1 = util_spec "c", "1" + c2 = util_spec "c", "2" + d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" - install_specs d1, c1, c2, b1, b2, a1 + install_specs d1, c1, c2, b1, b2, a1 - a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + a1.activate + assert_equal %w[a-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-1 d-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names - end + assert_equal %w[a-1 d-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names end def test_require_should_prefer_latest_gem_level1 - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 0" # unresolved - b2 = util_spec "b", "2", "c" => ">= 0" - c1 = util_spec "c", "1", nil, "lib/c#{$$}.rb" # 1st level - c2 = util_spec "c", "2", nil, "lib/c#{$$}.rb" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 0" # unresolved + b2 = util_spec "b", "2", "c" => ">= 0" + c1 = util_spec "c", "1", nil, "lib/c#{$$}.rb" # 1st level + c2 = util_spec "c", "2", nil, "lib/c#{$$}.rb" - install_specs c1, c2, b1, b2, a1 + install_specs c1, c2, b1, b2, a1 - a1.activate + a1.activate - require "c#{$$}" + require "c#{$$}" - assert_equal %w[a-1 b-2 c-2], loaded_spec_names - end + assert_equal %w[a-1 b-2 c-2], loaded_spec_names end def test_require_should_prefer_latest_gem_level2 - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 0" # unresolved - b2 = util_spec "b", "2", "c" => ">= 0" - c1 = util_spec "c", "1", "d" => ">= 0" # 1st level - c2 = util_spec "c", "2", "d" => ">= 0" - d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level - d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 0" # unresolved + b2 = util_spec "b", "2", "c" => ">= 0" + c1 = util_spec "c", "1", "d" => ">= 0" # 1st level + c2 = util_spec "c", "2", "d" => ">= 0" + d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level + d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" - install_specs d1, d2, c1, c2, b1, b2, a1 + install_specs d1, d2, c1, c2, b1, b2, a1 - a1.activate + a1.activate - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-1 b-2 c-2 d-2], loaded_spec_names - end + assert_equal %w[a-1 b-2 c-2 d-2], loaded_spec_names end def test_require_finds_in_2nd_level_indirect - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 0" # unresolved - b2 = util_spec "b", "2", "c" => ">= 0" - c1 = util_spec "c", "1", "d" => "<= 2" # 1st level - c2 = util_spec "c", "2", "d" => "<= 2" - d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level - d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" - d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 0" # unresolved + b2 = util_spec "b", "2", "c" => ">= 0" + c1 = util_spec "c", "1", "d" => "<= 2" # 1st level + c2 = util_spec "c", "2", "d" => "<= 2" + d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level + d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" + d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" - install_specs d1, d2, d3, c1, c2, b1, b2, a1 + install_specs d1, d2, d3, c1, c2, b1, b2, a1 - a1.activate + a1.activate - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-1 b-2 c-2 d-2], loaded_spec_names - end + assert_equal %w[a-1 b-2 c-2 d-2], loaded_spec_names end def test_require_should_prefer_reachable_gems - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 0" # unresolved - b2 = util_spec "b", "2", "c" => ">= 0" - c1 = util_spec "c", "1", "d" => "<= 2" # 1st level - c2 = util_spec "c", "2", "d" => "<= 2" - d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level - d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" - d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" - e = util_spec "anti_d", "1", nil, "lib/d#{$$}.rb" - - install_specs d1, d2, d3, e, c1, c2, b1, b2, a1 + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 0" # unresolved + b2 = util_spec "b", "2", "c" => ">= 0" + c1 = util_spec "c", "1", "d" => "<= 2" # 1st level + c2 = util_spec "c", "2", "d" => "<= 2" + d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level + d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" + d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" + e = util_spec "anti_d", "1", nil, "lib/d#{$$}.rb" + + install_specs d1, d2, d3, e, c1, c2, b1, b2, a1 - a1.activate + a1.activate - require "d#{$$}" + require "d#{$$}" - assert_equal %w[a-1 b-2 c-2 d-2], loaded_spec_names - end + assert_equal %w[a-1 b-2 c-2 d-2], loaded_spec_names end def test_require_should_not_conflict - save_loaded_features do - base = util_spec "0", "1", "A" => ">= 1" - a1 = util_spec "A", "1", {"c" => ">= 2", "b" => "> 0"}, "lib/a.rb" - a2 = util_spec "A", "2", {"c" => ">= 2", "b" => "> 0"}, "lib/a.rb" - b1 = util_spec "b", "1", {"c" => "= 1"}, "lib/d#{$$}.rb" - b2 = util_spec "b", "2", {"c" => "= 2"}, "lib/d#{$$}.rb" - c1 = util_spec "c", "1", {}, "lib/c.rb" - c2 = util_spec "c", "2", {}, "lib/c.rb" - c3 = util_spec "c", "3", {}, "lib/c.rb" - - install_specs c1, c2, c3, b1, b2, a1, a2, base - - base.activate - assert_equal %w[0-1], loaded_spec_names - assert_equal ["A (>= 1)"], unresolved_names + base = util_spec "0", "1", "A" => ">= 1" + a1 = util_spec "A", "1", {"c" => ">= 2", "b" => "> 0"}, "lib/a.rb" + a2 = util_spec "A", "2", {"c" => ">= 2", "b" => "> 0"}, "lib/a.rb" + b1 = util_spec "b", "1", {"c" => "= 1"}, "lib/d#{$$}.rb" + b2 = util_spec "b", "2", {"c" => "= 2"}, "lib/d#{$$}.rb" + c1 = util_spec "c", "1", {}, "lib/c.rb" + c2 = util_spec "c", "2", {}, "lib/c.rb" + c3 = util_spec "c", "3", {}, "lib/c.rb" - require "d#{$$}" + install_specs c1, c2, c3, b1, b2, a1, a2, base - assert_equal %w[0-1 A-2 b-2 c-2], loaded_spec_names - assert_equal [], unresolved_names - end + base.activate + assert_equal %w[0-1], loaded_spec_names + assert_equal ["A (>= 1)"], unresolved_names + + require "d#{$$}" + + assert_equal %w[0-1 A-2 b-2 c-2], loaded_spec_names + assert_equal [], unresolved_names end def test_inner_clonflict_in_indirect_gems - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" # unresolved - b2 = util_spec "b", "2", "c" => ">= 1", "d" => "< 3" - c1 = util_spec "c", "1", "d" => "<= 2" # 1st level - c2 = util_spec "c", "2", "d" => "<= 2" - c3 = util_spec "c", "3", "d" => "<= 3" - d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level - d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" - d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" - - install_specs d1, d2, d3, c1, c2, c3, b1, b2, a1 + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" # unresolved + b2 = util_spec "b", "2", "c" => ">= 1", "d" => "< 3" + c1 = util_spec "c", "1", "d" => "<= 2" # 1st level + c2 = util_spec "c", "2", "d" => "<= 2" + c3 = util_spec "c", "3", "d" => "<= 3" + d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level + d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" + d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" + + install_specs d1, d2, d3, c1, c2, c3, b1, b2, a1 - a1.activate + a1.activate - require "d#{$$}" + require "d#{$$}" - assert_includes [%w[a-1 b-2 c-3 d-2],%w[a-1 b-2 d-2]], loaded_spec_names - end + assert_includes [%w[a-1 b-2 c-3 d-2],%w[a-1 b-2 d-2]], loaded_spec_names end def test_inner_clonflict_in_indirect_gems_reversed - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - b1 = util_spec "b", "1", "xc" => ">= 1" # unresolved - b2 = util_spec "b", "2", "xc" => ">= 1", "d" => "< 3" - c1 = util_spec "xc", "1", "d" => "<= 3" # 1st level - c2 = util_spec "xc", "2", "d" => "<= 2" - c3 = util_spec "xc", "3", "d" => "<= 3" - d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level - d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" - d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" + a1 = util_spec "a", "1", "b" => "> 0" + b1 = util_spec "b", "1", "xc" => ">= 1" # unresolved + b2 = util_spec "b", "2", "xc" => ">= 1", "d" => "< 3" + c1 = util_spec "xc", "1", "d" => "<= 3" # 1st level + c2 = util_spec "xc", "2", "d" => "<= 2" + c3 = util_spec "xc", "3", "d" => "<= 3" + d1 = util_spec "d", "1", nil, "lib/d#{$$}.rb" # 2nd level + d2 = util_spec "d", "2", nil, "lib/d#{$$}.rb" + d3 = util_spec "d", "3", nil, "lib/d#{$$}.rb" + + install_specs d1, d2, d3, c1, c2, c3, b1, b2, a1 - install_specs d1, d2, d3, c1, c2, c3, b1, b2, a1 + a1.activate - a1.activate + require "d#{$$}" - require "d#{$$}" - - assert_includes [%w[a-1 b-2 d-2 xc-3], %w[a-1 b-2 d-2]], loaded_spec_names - end + assert_includes [%w[a-1 b-2 d-2 xc-3], %w[a-1 b-2 d-2]], loaded_spec_names end ## @@ -509,41 +485,37 @@ end install_specs b1, b2, a1 a1.activate - save_loaded_features do - require "b/c" - end + require "b/c" assert_equal %w[a-1 b-1], loaded_spec_names end def test_self_activate_via_require_wtf - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0", "d" => "> 0" # this - b1 = util_spec "b", "1", { "c" => ">= 1" }, "lib/b#{$$}.rb" - b2 = util_spec "b", "2", { "c" => ">= 2" }, "lib/b#{$$}.rb" # this - c1 = util_spec "c", "1" - c2 = util_spec "c", "2" # this - d1 = util_spec "d", "1", { "c" => "< 2" }, "lib/d#{$$}.rb" - d2 = util_spec "d", "2", { "c" => "< 2" }, "lib/d#{$$}.rb" # this + a1 = util_spec "a", "1", "b" => "> 0", "d" => "> 0" # this + b1 = util_spec "b", "1", { "c" => ">= 1" }, "lib/b#{$$}.rb" + b2 = util_spec "b", "2", { "c" => ">= 2" }, "lib/b#{$$}.rb" # this + c1 = util_spec "c", "1" + c2 = util_spec "c", "2" # this + d1 = util_spec "d", "1", { "c" => "< 2" }, "lib/d#{$$}.rb" + d2 = util_spec "d", "2", { "c" => "< 2" }, "lib/d#{$$}.rb" # this - install_specs c1, c2, b1, b2, d1, d2, a1 + install_specs c1, c2, b1, b2, d1, d2, a1 - a1.activate + a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal ["b (> 0)", "d (> 0)"], unresolved_names + assert_equal %w[a-1], loaded_spec_names + assert_equal ["b (> 0)", "d (> 0)"], unresolved_names - require "b#{$$}" + require "b#{$$}" - e = assert_raise Gem::LoadError do - require "d#{$$}" - end + e = assert_raise Gem::LoadError do + require "d#{$$}" + end - assert_equal "unable to find a version of 'd' to activate", e.message + assert_equal "unable to find a version of 'd' to activate", e.message - assert_equal %w[a-1 b-2 c-2], loaded_spec_names - assert_equal ["d (> 0)"], unresolved_names - end + assert_equal %w[a-1 b-2 c-2], loaded_spec_names + assert_equal ["d (> 0)"], unresolved_names end def test_self_activate_deep_unambiguous @@ -2146,43 +2118,39 @@ dependencies: [] end def test_require_already_activated - save_loaded_features do - a1 = util_spec "a", "1", nil, "lib/d#{$$}.rb" + a1 = util_spec "a", "1", nil, "lib/d#{$$}.rb" - install_specs a1 # , a2, b1, b2, c1, c2 + install_specs a1 # , a2, b1, b2, c1, c2 - a1.activate - assert_equal %w[a-1], loaded_spec_names - assert_equal [], unresolved_names + a1.activate + assert_equal %w[a-1], loaded_spec_names + assert_equal [], unresolved_names - assert require "d#{$$}" + assert require "d#{$$}" - assert_equal %w[a-1], loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w[a-1], loaded_spec_names + assert_equal [], unresolved_names end def test_require_already_activated_indirect_conflict - save_loaded_features do - a1 = util_spec "a", "1", "b" => "> 0" - a2 = util_spec "a", "2", "b" => "> 0" - b1 = util_spec "b", "1", "c" => ">= 1" - b2 = util_spec "b", "2", "c" => ">= 2" - c1 = util_spec "c", "1", nil, "lib/d#{$$}.rb" - c2 = util_spec("c", "2", { "a" => "1" }, "lib/d#{$$}.rb") # conflicts with a-2 + a1 = util_spec "a", "1", "b" => "> 0" + a2 = util_spec "a", "2", "b" => "> 0" + b1 = util_spec "b", "1", "c" => ">= 1" + b2 = util_spec "b", "2", "c" => ">= 2" + c1 = util_spec "c", "1", nil, "lib/d#{$$}.rb" + c2 = util_spec("c", "2", { "a" => "1" }, "lib/d#{$$}.rb") # conflicts with a-2 - install_specs c1, b1, a1, a2, c2, b2 + install_specs c1, b1, a1, a2, c2, b2 - a1.activate - c1.activate - assert_equal %w[a-1 c-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + a1.activate + c1.activate + assert_equal %w[a-1 c-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - assert require "d#{$$}" + assert require "d#{$$}" - assert_equal %w[a-1 c-1], loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names - end + assert_equal %w[a-1 c-1], loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names end def test_requirements diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index 2389416d86..ab57ddae47 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -58,4 +58,4 @@ DEPENDENCIES test-unit BUNDLED WITH - 2.2.29 + 2.2.30 diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index 533838d870..dc572fd644 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -40,4 +40,4 @@ DEPENDENCIES webrick (= 1.7.0) BUNDLED WITH - 2.2.29 + 2.2.30 |