diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 5dc711ffdeec45..9b905db1f9aee6 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -496,7 +496,15 @@ def most_specific_locked_platform private :sources def nothing_changed? - !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@missing_lockfile_dep && !@unlocking_bundler && !@invalid_lockfile_dep + !@source_changes && + !@dependency_changes && + !@new_platform && + !@path_changes && + !@local_changes && + !@missing_lockfile_dep && + !@unlocking_bundler && + !@locked_spec_with_missing_deps && + !@locked_spec_with_invalid_deps end def no_resolve_needed? @@ -653,7 +661,8 @@ def change_reason [@local_changes, "the gemspecs for git local gems changed"], [@missing_lockfile_dep, "your lock file is missing \"#{@missing_lockfile_dep}\""], [@unlocking_bundler, "an update to the version of Bundler itself was requested"], - [@invalid_lockfile_dep, "your lock file has an invalid dependency \"#{@invalid_lockfile_dep}\""], + [@locked_spec_with_missing_deps, "your lock file includes \"#{@locked_spec_with_missing_deps}\" but not some of its dependencies"], + [@locked_spec_with_invalid_deps, "your lockfile does not satisfy dependencies of \"#{@locked_spec_with_invalid_deps}\""], ].select(&:first).map(&:last).join(", ") end @@ -708,26 +717,25 @@ def converge_locals end def check_lockfile - @invalid_lockfile_dep = nil @missing_lockfile_dep = nil - locked_names = @locked_specs.map(&:name) + @locked_spec_with_invalid_deps = nil + @locked_spec_with_missing_deps = nil + missing = [] invalid = [] @locked_specs.each do |s| - s.dependencies.each do |dep| - next if dep.name == "bundler" + validation = @locked_specs.validate_deps(s) - missing << s unless locked_names.include?(dep.name) - invalid << s if @locked_specs.none? {|spec| dep.matches_spec?(spec) } - end + missing << s if validation == :missing + invalid << s if validation == :invalid end if missing.any? @locked_specs.delete(missing) - @missing_lockfile_dep = missing.first.name + @locked_spec_with_missing_deps = missing.first.name elsif !@dependency_changes @missing_lockfile_dep = current_dependencies.find do |d| @locked_specs[d.name].empty? && d.name != "bundler" @@ -737,7 +745,7 @@ def check_lockfile if invalid.any? @locked_specs.delete(invalid) - @invalid_lockfile_dep = invalid.first.name + @locked_spec_with_invalid_deps = invalid.first.name end end diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb index f2d10167325e58..38aea2a4aa83eb 100644 --- a/lib/bundler/lazy_specification.rb +++ b/lib/bundler/lazy_specification.rb @@ -10,6 +10,8 @@ class LazySpecification attr_reader :name, :version, :platform attr_accessor :source, :remote, :force_ruby_platform, :dependencies, :required_ruby_version, :required_rubygems_version + alias_method :runtime_dependencies, :dependencies + def self.from_spec(s) lazy_spec = new(s.name, s.version, s.platform, s.source) lazy_spec.dependencies = s.dependencies diff --git a/lib/bundler/remote_specification.rb b/lib/bundler/remote_specification.rb index f626a3218e0503..9d237f3fa04834 100644 --- a/lib/bundler/remote_specification.rb +++ b/lib/bundler/remote_specification.rb @@ -88,6 +88,10 @@ def dependencies end end + def runtime_dependencies + dependencies.select(&:runtime?) + end + def git_version return unless loaded_from && source.is_a?(Bundler::Source::Git) " #{source.revision[0..6]}" diff --git a/lib/bundler/self_manager.rb b/lib/bundler/self_manager.rb index 1925a266d977db..5accda4bcbaccc 100644 --- a/lib/bundler/self_manager.rb +++ b/lib/bundler/self_manager.rb @@ -121,7 +121,7 @@ def remote_specs source = Bundler::Source::Rubygems.new("remotes" => "https://rubygems.org") source.remote! source.add_dependency_names("bundler") - source.specs + source.specs.select(&:matches_current_metadata?) end end diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index a0ef3c2cb5871a..ceaac2cec523d0 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -37,7 +37,7 @@ def for(dependencies, check = false, platforms = [nil]) specs_for_dep.first.dependencies.each do |d| next if d.type == :development - incomplete = true if d.name != "bundler" && lookup[d.name].empty? + incomplete = true if d.name != "bundler" && lookup[d.name].nil? deps << [d, dep[1]] end else @@ -45,7 +45,7 @@ def for(dependencies, check = false, platforms = [nil]) end if incomplete && check - @incomplete_specs += lookup[name].any? ? lookup[name] : [LazySpecification.new(name, nil, nil)] + @incomplete_specs += lookup[name] || [LazySpecification.new(name, nil, nil)] end end @@ -64,7 +64,9 @@ def complete_platforms!(platforms) valid_platform = lookup.all? do |_, specs| spec = specs.first matching_specs = spec.source.specs.search([spec.name, spec.version]) - platform_spec = GemHelpers.select_best_platform_match(matching_specs, platform).find(&:matches_current_metadata?) + platform_spec = GemHelpers.select_best_platform_match(matching_specs, platform).find do |s| + s.matches_current_metadata? && valid_dependencies?(s) + end if platform_spec new_specs << LazySpecification.from_spec(platform_spec) @@ -90,9 +92,20 @@ def complete_platforms!(platforms) platforms end + def validate_deps(s) + s.runtime_dependencies.each do |dep| + next if dep.name == "bundler" + + return :missing unless names.include?(dep.name) + return :invalid if none? {|spec| dep.matches_spec?(spec) } + end + + :valid + end + def [](key) key = key.name if key.respond_to?(:name) - lookup[key].reverse + lookup[key]&.reverse || [] end def []=(key, value) @@ -167,7 +180,7 @@ def delete_by_name(name) end def what_required(spec) - unless req = find {|s| s.dependencies.any? {|d| d.type == :runtime && d.name == spec.name } } + unless req = find {|s| s.runtime_dependencies.any? {|d| d.name == spec.name } } return [spec] end what_required(req) << spec @@ -193,8 +206,16 @@ def each(&b) sorted.each(&b) end + def names + lookup.keys + end + private + def valid_dependencies?(s) + validate_deps(s) == :valid + end + def sorted rake = @specs.find {|s| s.name == "rake" } begin @@ -213,8 +234,9 @@ def extract_circular_gems(error) def lookup @lookup ||= begin - lookup = Hash.new {|h, k| h[k] = [] } + lookup = {} @specs.each do |s| + lookup[s.name] ||= [] lookup[s.name] << s end lookup @@ -228,6 +250,8 @@ def tsort_each_node def specs_for_dependency(dep, platform) specs_for_name = lookup[dep.name] + return [] unless specs_for_name + matching_specs = if dep.force_ruby_platform GemHelpers.force_ruby_platform(specs_for_name) else @@ -240,7 +264,11 @@ def specs_for_dependency(dep, platform) def tsort_each_child(s) s.dependencies.sort_by(&:name).each do |d| next if d.type == :development - lookup[d.name].each {|s2| yield s2 } + + specs_for_name = lookup[d.name] + next unless specs_for_name + + specs_for_name.each {|s2| yield s2 } end end end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/color.rb b/lib/bundler/vendor/thor/lib/thor/shell/color.rb index 6a9176331c34f1..5d708fadcad2dd 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/color.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/color.rb @@ -1,5 +1,4 @@ require_relative "basic" -require_relative "lcs_diff" class Bundler::Thor module Shell @@ -7,8 +6,6 @@ module Shell # Bundler::Thor::Shell::Basic to see all available methods. # class Color < Basic - include LCSDiff - # Embed in a String to clear all previous ANSI sequences. CLEAR = "\e[0m" # The start of an ANSI bold sequence. diff --git a/lib/bundler/vendor/thor/lib/thor/shell/html.rb b/lib/bundler/vendor/thor/lib/thor/shell/html.rb index 6091485acbf726..0277b882b7ad99 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/html.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/html.rb @@ -1,5 +1,4 @@ require_relative "basic" -require_relative "lcs_diff" class Bundler::Thor module Shell @@ -7,8 +6,6 @@ module Shell # Bundler::Thor::Shell::Basic to see all available methods. # class HTML < Basic - include LCSDiff - # The start of an HTML bold sequence. BOLD = "font-weight: bold" diff --git a/lib/bundler/vendor/thor/lib/thor/shell/lcs_diff.rb b/lib/bundler/vendor/thor/lib/thor/shell/lcs_diff.rb deleted file mode 100644 index 81268a9f028bd6..00000000000000 --- a/lib/bundler/vendor/thor/lib/thor/shell/lcs_diff.rb +++ /dev/null @@ -1,49 +0,0 @@ -module LCSDiff -protected - - # Overwrite show_diff to show diff with colors if Diff::LCS is - # available. - def show_diff(destination, content) #:nodoc: - if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil? - actual = File.binread(destination).to_s.split("\n") - content = content.to_s.split("\n") - - Diff::LCS.sdiff(actual, content).each do |diff| - output_diff_line(diff) - end - else - super - end - end - -private - - def output_diff_line(diff) #:nodoc: - case diff.action - when "-" - say "- #{diff.old_element.chomp}", :red, true - when "+" - say "+ #{diff.new_element.chomp}", :green, true - when "!" - say "- #{diff.old_element.chomp}", :red, true - say "+ #{diff.new_element.chomp}", :green, true - else - say " #{diff.old_element.chomp}", nil, true - end - end - - # Check if Diff::LCS is loaded. If it is, use it to create pretty output - # for diff. - def diff_lcs_loaded? #:nodoc: - return true if defined?(Diff::LCS) - return @diff_lcs_loaded unless @diff_lcs_loaded.nil? - - @diff_lcs_loaded = begin - require "diff/lcs" - true - rescue LoadError - false - end - end - -end diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 46a984283d6aed..a98d7fab30d7f7 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "2.5.1".freeze + VERSION = "2.5.2".freeze def self.bundler_major_version @bundler_major_version ||= VERSION.split(".").first.to_i diff --git a/lib/rubygems.rb b/lib/rubygems.rb index a5091b07b80465..fd5683f1be0cff 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "3.5.1" + VERSION = "3.5.2" end # Must be first since it unloads the prelude from 1.9.2 @@ -942,6 +942,13 @@ def self.suffixes end].compact.uniq end + ## + # Suffixes for dynamic library require-able paths. + + def self.dynamic_library_suffixes + @dynamic_library_suffixes ||= suffixes - [".rb"] + end + ## # Prints the amount of time the supplied block takes to run using the debug # UI output. diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb index cb6931b9747044..0380fceecefd62 100644 --- a/lib/rubygems/basic_specification.rb +++ b/lib/rubygems/basic_specification.rb @@ -84,7 +84,13 @@ def contains_requirable_file?(file) return false end - have_file? file, Gem.suffixes + is_soext = file.end_with?(".so", ".o") + + if is_soext + have_file? file.delete_suffix(File.extname(file)), Gem.dynamic_library_suffixes + else + have_file? file, Gem.suffixes + end end def default_gem? diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index f3c1cb2895d516..387e40ffd753bb 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -268,7 +268,7 @@ def add_files(tar) # :nodoc: tar.add_file_simple file, stat.mode, stat.size do |dst_io| File.open file, "rb" do |src_io| - dst_io.write src_io.read 16_384 until src_io.eof? + copy_stream(src_io, dst_io) end end end @@ -453,7 +453,7 @@ def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: end if entry.file? - File.open(destination, "wb") {|out| out.write entry.read } + File.open(destination, "wb") {|out| copy_stream(entry, out) } FileUtils.chmod file_mode(entry.header.mode), destination end @@ -714,6 +714,16 @@ def verify_gz(entry) # :nodoc: rescue Zlib::GzipFile::Error => e raise Gem::Package::FormatError.new(e.message, entry.full_name) end + + if RUBY_ENGINE == "truffleruby" + def copy_stream(src, dst) # :nodoc: + dst.write src.read + end + else + def copy_stream(src, dst) # :nodoc: + IO.copy_stream(src, dst) + end + end end require_relative "package/digest_io" diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb index dec0c35c633132..8702e223d67104 100644 --- a/lib/rubygems/request.rb +++ b/lib/rubygems/request.rb @@ -30,7 +30,7 @@ def initialize(uri, request_class, last_modified, pool) @uri = uri @request_class = request_class @last_modified = last_modified - @requests = Hash.new 0 + @requests = Hash.new(0).compare_by_identity @user_agent = user_agent @connection_pool = pool @@ -196,7 +196,7 @@ def perform_request(request) # :nodoc: bad_response = false begin - @requests[connection.object_id] += 1 + @requests[connection] += 1 verbose "#{request.method} #{Gem::Uri.redact(@uri)}" @@ -247,7 +247,7 @@ def perform_request(request) # :nodoc: rescue EOFError, Gem::Timeout::Error, Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE - requests = @requests[connection.object_id] + requests = @requests[connection] verbose "connection reset after #{requests} requests, retrying" raise Gem::RemoteFetcher::FetchError.new("too many connection resets", @uri) if retried @@ -267,7 +267,7 @@ def perform_request(request) # :nodoc: # Resets HTTP connection +connection+. def reset(connection) - @requests.delete connection.object_id + @requests.delete connection connection.finish connection.start diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb index 2e3c1584b13058..a19cf789ee9bb6 100644 --- a/spec/bundler/bundler/definition_spec.rb +++ b/spec/bundler/bundler/definition_spec.rb @@ -121,10 +121,7 @@ gem "foo", :path => "#{lib_path("foo")}" G - bundle :check, env: { "DEBUG" => "1" } - - expect(out).to match(/using resolution from the lockfile/) - expect(lockfile).to eq <<~G + expected_lockfile = <<~G PATH remote: #{lib_path("foo")} specs: @@ -145,6 +142,13 @@ BUNDLED WITH #{Bundler::VERSION} G + + expect(lockfile).to eq(expected_lockfile) + + bundle :check, env: { "DEBUG" => "1" } + + expect(out).to match(/using resolution from the lockfile/) + expect(lockfile).to eq(expected_lockfile) end it "for a locked gem for another platform" do diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index 013cadd5413d71..59e7c3867c7e87 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -1371,26 +1371,28 @@ expect(the_bundle).to include_gem "rack 1.0" end - it "updates the bundler version in the lockfile even if the latest version is not installed", :ruby_repo, :realworld do + it "updates the bundler version in the lockfile even if the latest version is not installed", :ruby_repo do pristine_system_gems "bundler-2.3.9" build_repo4 do build_gem "rack", "1.0" + + build_bundler "999.0.0" end - install_gemfile <<-G, env: { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } + install_gemfile <<-G source "#{file_uri_for(gem_repo4)}" gem "rack" G lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9") - bundle :update, bundler: true, artifice: "vcr", verbose: true, env: { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } + bundle :update, bundler: true, artifice: "compact_index", verbose: true, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } # Only updates properly on modern RubyGems. if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev") - expect(out).to include("Updating bundler to 2.3.10") - expect(out).to include("Using bundler 2.3.10") + expect(out).to include("Updating bundler to 999.0.0") + expect(out).to include("Using bundler 999.0.0") expect(out).not_to include("Installing Bundler 2.3.9 and restarting using that version.") expect(lockfile).to eq <<~L @@ -1406,16 +1408,63 @@ rack BUNDLED WITH - 2.3.10 + 999.0.0 L - expect(the_bundle).to include_gems "bundler 2.3.10" + expect(the_bundle).to include_gems "bundler 999.0.0" + expect(the_bundle).to include_gems "rack 1.0" + else + # Old RubyGems versions do not trampoline but they still change BUNDLED + # WITH to the latest bundler version. This means the below check fails + # because it tries to use bundler 999.0.0 which did not get installed. + # Workaround the bug by forcing the version we know is installed. + expect(the_bundle).to include_gems "rack 1.0", env: { "BUNDLER_VERSION" => "2.3.9" } + end + end + + it "does not update the bundler version in the lockfile if the latest version is not compatible with current ruby", :ruby_repo do + pristine_system_gems "bundler-2.3.9" + + build_repo4 do + build_gem "rack", "1.0" + + build_bundler "2.3.9" + build_bundler "999.0.0" do |s| + s.required_ruby_version = "> #{Gem.ruby_version}" + end end + install_gemfile <<-G, env: { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } + source "#{file_uri_for(gem_repo4)}" + gem "rack" + G + lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9") + + bundle :update, bundler: true, artifice: "compact_index", verbose: true, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s, "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } + + expect(out).to include("Using bundler 2.3.9") + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + BUNDLED WITH + 2.3.9 + L + + expect(the_bundle).to include_gems "bundler 2.3.9" expect(the_bundle).to include_gems "rack 1.0" end - it "errors if the explicit target version does not exist", :realworld do + it "errors if the explicit target version does not exist" do pristine_system_gems "bundler-2.3.9" build_repo4 do @@ -1428,7 +1477,7 @@ G lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9") - bundle :update, bundler: "999.999.999", artifice: "vcr", raise_on_error: false + bundle :update, bundler: "999.999.999", artifice: "compact_index", raise_on_error: false # Only gives a meaningful error message on modern RubyGems. diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb index 8b37570793908f..834465041123a4 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -1220,6 +1220,48 @@ end end + it "does not add ruby platform gem if it brings extra dependencies not resolved originally" do + build_repo4 do + build_gem "nokogiri", "1.15.5" do |s| + s.add_dependency "mini_portile2", "~> 2.8.2" + end + + build_gem "nokogiri", "1.15.5" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + G + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "nokogiri", "1.15.5", "x86_64-linux" + end + + simulate_platform "x86_64-linux" do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.15.5-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + private def setup_multiplatform_gem diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb index 8fe2f37054493a..5e996f5aaca02a 100644 --- a/spec/bundler/lock/lockfile_spec.rb +++ b/spec/bundler/lock/lockfile_spec.rb @@ -1603,7 +1603,7 @@ L bundle "install --verbose" - expect(out).to include("re-resolving dependencies because your lock file is missing \"minitest-bisect\"") + expect(out).to include("re-resolving dependencies because your lock file includes \"minitest-bisect\" but not some of its dependencies") expect(lockfile).to eq <<~L GEM diff --git a/spec/bundler/realworld/edgecases_spec.rb b/spec/bundler/realworld/edgecases_spec.rb index c9af686dbe4123..dc70271b6e6b98 100644 --- a/spec/bundler/realworld/edgecases_spec.rb +++ b/spec/bundler/realworld/edgecases_spec.rb @@ -219,140 +219,6 @@ def rubygems_version(name, requirement) expect(err).to include("resque-scheduler 2.2.0 includes a gemspec with `require_paths` set to an array of arrays. Newer versions of this gem might've already fixed this").once end - it "doesn't hang on big gemfile" do - skip "Only for ruby 2.7" unless RUBY_VERSION.start_with?("2.7") - - gemfile <<~G - # frozen_string_literal: true - - source "https://rubygems.org" - - ruby "~> 2.7.7" - - gem "rails" - gem "pg", ">= 0.18", "< 2.0" - gem "goldiloader" - gem "awesome_nested_set" - gem "circuitbox" - gem "passenger" - gem "globalid" - gem "rack-cors" - gem "rails-pg-extras" - gem "linear_regression_trend" - gem "rack-protection" - gem "pundit" - gem "remote_ip_proxy_scrubber" - gem "bcrypt" - gem "searchkick" - gem "excon" - gem "faraday_middleware-aws-sigv4" - gem "typhoeus" - gem "sidekiq" - gem "sidekiq-undertaker" - gem "sidekiq-cron" - gem "storext" - gem "appsignal" - gem "fcm" - gem "business_time" - gem "tzinfo" - gem "holidays" - gem "bigdecimal" - gem "progress_bar" - gem "redis" - gem "hiredis" - gem "state_machines" - gem "state_machines-audit_trail" - gem "state_machines-activerecord" - gem "interactor" - gem "ar_transaction_changes" - gem "redis-rails" - gem "seed_migration" - gem "lograge" - gem "graphiql-rails", group: :development - gem "graphql" - gem "pusher" - gem "rbnacl" - gem "jwt" - gem "json-schema" - gem "discard" - gem "money" - gem "strip_attributes" - gem "validates_email_format_of" - gem "audited" - gem "concurrent-ruby" - gem "with_advisory_lock" - - group :test do - gem "rspec-sidekiq" - gem "simplecov", require: false - end - - group :development, :test do - gem "byebug", platform: :mri - gem "guard" - gem "guard-bundler" - gem "guard-rspec" - gem "rb-fsevent" - gem "rspec_junit_formatter" - gem "rspec-collection_matchers" - gem "rspec-rails" - gem "rspec-retry" - gem "state_machines-rspec" - gem "dotenv-rails" - gem "database_cleaner-active_record" - gem "database_cleaner-redis" - gem "timecop" - end - - gem "factory_bot_rails" - gem "faker" - - group :development do - gem "listen" - gem "sql_queries_count" - gem "rubocop" - gem "rubocop-performance" - gem "rubocop-rspec" - gem "rubocop-rails" - gem "brakeman" - gem "bundler-audit" - gem "solargraph" - gem "annotate" - end - G - - if Bundler.feature_flag.bundler_3_mode? - # Conflicts on bundler version, so we count attempts differently - bundle :lock, env: { "DEBUG_RESOLVER" => "1" }, raise_on_error: false - expect(out.split("\n").grep(/backtracking to/).count).to eq(8) - else - bundle :lock, env: { "DEBUG_RESOLVER" => "1" } - expect(out).to include("Solution found after 7 attempts") - end - end - - it "doesn't hang on tricky gemfile" do - skip "Only for ruby 2.7" unless RUBY_VERSION.start_with?("2.7") - - gemfile <<~G - source 'https://rubygems.org' - - group :development do - gem "puppet-module-posix-default-r2.7", '~> 0.3' - gem "puppet-module-posix-dev-r2.7", '~> 0.3' - gem "beaker-rspec" - gem "beaker-puppet" - gem "beaker-docker" - gem "beaker-puppet_install_helper" - gem "beaker-module_install_helper" - end - G - - bundle :lock, env: { "DEBUG_RESOLVER" => "1" } - - expect(out).to include("Solution found after 6 attempts") - end - it "doesn't hang on nix gemfile" do skip "Only for ruby 3.0" unless RUBY_VERSION.start_with?("3.0") diff --git a/spec/bundler/realworld/slow_perf_spec.rb b/spec/bundler/realworld/slow_perf_spec.rb index d5ca51975aa904..be557d39027a56 100644 --- a/spec/bundler/realworld/slow_perf_spec.rb +++ b/spec/bundler/realworld/slow_perf_spec.rb @@ -30,4 +30,107 @@ expect { bundle "lock" }.to take_less_than(30) # seconds end + + it "resolves big gemfile quickly" do + gemfile <<~G + # frozen_string_literal: true + + source "https://rubygems.org" + + gem "rails" + gem "pg", ">= 0.18", "< 2.0" + gem "goldiloader" + gem "awesome_nested_set" + gem "circuitbox" + gem "passenger" + gem "globalid" + gem "rack-cors" + gem "rails-pg-extras" + gem "linear_regression_trend" + gem "rack-protection" + gem "pundit" + gem "remote_ip_proxy_scrubber" + gem "bcrypt" + gem "searchkick" + gem "excon" + gem "faraday_middleware-aws-sigv4" + gem "typhoeus" + gem "sidekiq" + gem "sidekiq-undertaker" + gem "sidekiq-cron" + gem "storext" + gem "appsignal" + gem "fcm" + gem "business_time" + gem "tzinfo" + gem "holidays" + gem "bigdecimal" + gem "progress_bar" + gem "redis" + gem "hiredis" + gem "state_machines" + gem "state_machines-audit_trail" + gem "state_machines-activerecord" + gem "interactor" + gem "ar_transaction_changes" + gem "redis-rails" + gem "seed_migration" + gem "lograge" + gem "graphiql-rails", group: :development + gem "graphql" + gem "pusher" + gem "rbnacl" + gem "jwt" + gem "json-schema" + gem "discard" + gem "money" + gem "strip_attributes" + gem "validates_email_format_of" + gem "audited" + gem "concurrent-ruby" + gem "with_advisory_lock" + + group :test do + gem "rspec-sidekiq" + gem "simplecov", require: false + end + + group :development, :test do + gem "byebug", platform: :mri + gem "guard" + gem "guard-bundler" + gem "guard-rspec" + gem "rb-fsevent" + gem "rspec_junit_formatter" + gem "rspec-collection_matchers" + gem "rspec-rails" + gem "rspec-retry" + gem "state_machines-rspec" + gem "dotenv-rails" + gem "database_cleaner-active_record" + gem "database_cleaner-redis" + gem "timecop" + end + + gem "factory_bot_rails" + gem "faker" + + group :development do + gem "listen" + gem "sql_queries_count" + gem "rubocop" + gem "rubocop-performance" + gem "rubocop-rspec" + gem "rubocop-rails" + gem "brakeman" + gem "bundler-audit" + gem "solargraph" + gem "annotate" + end + G + + expect do + bundle "lock", env: { "DEBUG_RESOLVER" => "1" }, raise_on_error: !Bundler.feature_flag.bundler_3_mode? + end.to take_less_than(30) # seconds + end end diff --git a/spec/bundler/support/artifice/helpers/compact_index.rb b/spec/bundler/support/artifice/helpers/compact_index.rb index 8e7acb41a90442..cf8bb34c5afeae 100644 --- a/spec/bundler/support/artifice/helpers/compact_index.rb +++ b/spec/bundler/support/artifice/helpers/compact_index.rb @@ -77,7 +77,7 @@ def gems(gem_repo = default_gem_repo) specs.group_by(&:name).map do |name, versions| gem_versions = versions.map do |spec| - deps = spec.dependencies.select {|d| d.type == :runtime }.map do |d| + deps = spec.runtime_dependencies.map do |d| reqs = d.requirement.requirements.map {|r| r.join(" ") }.join(", ") CompactIndex::Dependency.new(d.name, reqs) end diff --git a/spec/bundler/support/artifice/helpers/endpoint.rb b/spec/bundler/support/artifice/helpers/endpoint.rb index 6115e91535f5ff..be52df69365ff3 100644 --- a/spec/bundler/support/artifice/helpers/endpoint.rb +++ b/spec/bundler/support/artifice/helpers/endpoint.rb @@ -72,7 +72,7 @@ def dependencies_for(gem_names, gem_repo = default_gem_repo) name: spec.name, number: spec.version.version, platform: spec.platform.to_s, - dependencies: spec.dependencies.select {|dep| dep.type == :runtime }.map do |dep| + dependencies: spec.runtime_dependencies.map do |dep| [dep.name, dep.requirement.requirements.map {|a| a.join(" ") }.join(", ")] end, } diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index 1763e091fe2c81..68e3bd7c7b60fa 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -297,6 +297,10 @@ def build_lib(name, *args, &blk) build_with(LibBuilder, name, args, &blk) end + def build_bundler(*args, &blk) + build_with(BundlerBuilder, "bundler", args, &blk) + end + def build_gem(name, *args, &blk) build_with(GemBuilder, name, args, &blk) end @@ -402,6 +406,49 @@ def required_ruby_version=(*reqs) alias_method :dep, :runtime end + class BundlerBuilder + attr_writer :required_ruby_version + + def initialize(context, name, version) + raise "can only build bundler" unless name == "bundler" + + @context = context + @version = version || Bundler::VERSION + end + + def _build(options = {}) + full_name = "bundler-#{@version}" + build_path = @context.tmp + full_name + bundler_path = build_path + "#{full_name}.gem" + + Dir.mkdir build_path + + @context.shipped_files.each do |shipped_file| + target_shipped_file = shipped_file + target_shipped_file = shipped_file.sub(/\Alibexec/, "exe") if @context.ruby_core? + target_shipped_file = build_path + target_shipped_file + target_shipped_dir = File.dirname(target_shipped_file) + FileUtils.mkdir_p target_shipped_dir unless File.directory?(target_shipped_dir) + FileUtils.cp shipped_file, target_shipped_file, preserve: true + end + + @context.replace_version_file(@version, dir: build_path) + @context.replace_required_ruby_version(@required_ruby_version, dir: build_path) if @required_ruby_version + + Spec::BuildMetadata.write_build_metadata(dir: build_path) + + @context.gem_command "build #{@context.relative_gemspec}", dir: build_path + + if block_given? + yield(bundler_path) + else + FileUtils.mv bundler_path, options[:path] + end + ensure + build_path.rmtree + end + end + class LibBuilder def initialize(context, name, version) @context = context diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 5e5a6a6e3b6d21..64ed0553b84d59 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -326,34 +326,8 @@ def install_gem(path, install_dir, default = false) gem_command "install #{args} '#{path}'" end - def with_built_bundler(version = nil) - version ||= Bundler::VERSION - full_name = "bundler-#{version}" - build_path = tmp + full_name - bundler_path = build_path + "#{full_name}.gem" - - Dir.mkdir build_path - - begin - shipped_files.each do |shipped_file| - target_shipped_file = shipped_file - target_shipped_file = shipped_file.sub(/\Alibexec/, "exe") if ruby_core? - target_shipped_file = build_path + target_shipped_file - target_shipped_dir = File.dirname(target_shipped_file) - FileUtils.mkdir_p target_shipped_dir unless File.directory?(target_shipped_dir) - FileUtils.cp shipped_file, target_shipped_file, preserve: true - end - - replace_version_file(version, dir: build_path) - - Spec::BuildMetadata.write_build_metadata(dir: build_path) - - gem_command "build #{relative_gemspec}", dir: build_path - - yield(bundler_path) - ensure - build_path.rmtree - end + def with_built_bundler(version = nil, &block) + Builders::BundlerBuilder.new(self, "bundler", version)._build(&block) end def with_gem_path_as(path) diff --git a/spec/bundler/support/matchers.rb b/spec/bundler/support/matchers.rb index ea7c784683517e..9d604bec25500c 100644 --- a/spec/bundler/support/matchers.rb +++ b/spec/bundler/support/matchers.rb @@ -103,7 +103,21 @@ def self.define_compound_matcher(matcher, preconditions, &declarations) actual.call - (Time.now - start_time).to_f < seconds + actual_time = (Time.now - start_time).to_f + + acceptable = actual_time < seconds + + @errors = ["took #{actual_time} seconds"] unless acceptable + + acceptable + end + + failure_message do + super() + " but:\n" + @errors.map {|e| indent(e) }.join("\n") + end + + failure_message_when_negated do + super() + " but:\n" + @errors.map {|e| indent(e) }.join("\n") end supports_block_expectations diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index 3dbc553c0148e1..7352d5a35316ef 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -250,6 +250,13 @@ def replace_version_file(version, dir: source_root) File.open(version_file, "w") {|f| f << contents } end + def replace_required_ruby_version(version, dir:) + gemspec_file = File.expand_path("bundler.gemspec", dir) + contents = File.read(gemspec_file) + contents.sub!(/(^\s+s\.required_ruby_version\s*=\s*)"[^"]+"/, %(\\1"#{version}")) + File.open(gemspec_file, "w") {|f| f << contents } + end + def ruby_core? # avoid to warnings @ruby_core ||= nil diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock index e1d83f092d207d..0937f71973f5f8 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock @@ -152,18 +152,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.83" +version = "0.9.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e5b8d560b60790a3e60e56e73a8c7be88ac14e6af39fc82b5eca72c71753840" +checksum = "3def04a96a36ef8681a2b2e26c01683b93a8630175c845fa06cab76c5a8c7ce0" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.83" +version = "0.9.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d2bfd00002007d7e9ad93d0397437933040caf452d260c26dbef5fd95ae1a6" +checksum = "c017d134afd764dd43c2faa91aa50b698a3bb4ff30e83113da483c789e74be8c" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml index 7596823e08caa4..68834952827e21 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.83" +rb-sys = "0.9.84" diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index 5601f286a95b40..5f0546b93da9e9 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -1561,6 +1561,17 @@ def test_contains_requirable_file_eh_extension_java_platform assert_empty err end + def test_contains_requirable_file_extension_soext + ext_spec + dlext = RbConfig::CONFIG["DLEXT"] + @ext.files += ["lib/ext.#{dlext}"] + + FileUtils.mkdir_p @ext.extension_dir + FileUtils.touch File.join(@ext.extension_dir, "ext.#{dlext}") + FileUtils.touch File.join(@ext.extension_dir, "gem.build_complete") + assert @ext.contains_requirable_file? "ext.so" + end + def test_date assert_date @a1.date end