Merge RubyGems-3.6.7 and Bundler-2.6.7

This commit is contained in:
Hiroshi SHIBATA 2025-04-09 09:19:31 +09:00 committed by Takashi Kokubun
parent db2bf9f078
commit e580145171
57 changed files with 721 additions and 289 deletions

View file

@ -190,7 +190,7 @@ module Bundler
def replace(spec, checksum) def replace(spec, checksum)
return unless checksum return unless checksum
lock_name = spec.name_tuple.lock_name lock_name = spec.lock_name
@store_mutex.synchronize do @store_mutex.synchronize do
existing = fetch_checksum(lock_name, checksum.algo) existing = fetch_checksum(lock_name, checksum.algo)
if !existing || existing.same_source?(checksum) if !existing || existing.same_source?(checksum)
@ -201,10 +201,12 @@ module Bundler
end end
end end
def register(spec, checksum) def missing?(spec)
return unless checksum @store[spec.lock_name].nil?
end
register_checksum(spec.name_tuple.lock_name, checksum) def register(spec, checksum)
register_checksum(spec.lock_name, checksum)
end end
def merge!(other) def merge!(other)
@ -216,9 +218,9 @@ module Bundler
end end
def to_lock(spec) def to_lock(spec)
lock_name = spec.name_tuple.lock_name lock_name = spec.lock_name
checksums = @store[lock_name] checksums = @store[lock_name]
if checksums if checksums&.any?
"#{lock_name} #{checksums.values.map(&:to_lock).sort.join(",")}" "#{lock_name} #{checksums.values.map(&:to_lock).sort.join(",")}"
else else
lock_name lock_name
@ -229,11 +231,15 @@ module Bundler
def register_checksum(lock_name, checksum) def register_checksum(lock_name, checksum)
@store_mutex.synchronize do @store_mutex.synchronize do
existing = fetch_checksum(lock_name, checksum.algo) if checksum
if existing existing = fetch_checksum(lock_name, checksum.algo)
merge_checksum(lock_name, checksum, existing) if existing
merge_checksum(lock_name, checksum, existing)
else
store_checksum(lock_name, checksum)
end
else else
store_checksum(lock_name, checksum) init_checksum(lock_name)
end end
end end
end end
@ -243,7 +249,11 @@ module Bundler
end end
def store_checksum(lock_name, checksum) def store_checksum(lock_name, checksum)
(@store[lock_name] ||= {})[checksum.algo] = checksum init_checksum(lock_name)[checksum.algo] = checksum
end
def init_checksum(lock_name)
@store[lock_name] ||= {}
end end
def fetch_checksum(lock_name, algo) def fetch_checksum(lock_name, algo)

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative "gem_parser" require "rubygems/resolver/api_set/gem_parser"
module Bundler module Bundler
class CompactIndexClient class CompactIndexClient

View file

@ -1,32 +0,0 @@
# frozen_string_literal: true
module Bundler
class CompactIndexClient
if defined?(Gem::Resolver::APISet::GemParser)
GemParser = Gem::Resolver::APISet::GemParser
else
class GemParser
EMPTY_ARRAY = [].freeze
private_constant :EMPTY_ARRAY
def parse(line)
version_and_platform, rest = line.split(" ", 2)
version, platform = version_and_platform.split("-", 2)
dependencies, requirements = rest.split("|", 2).map! {|s| s.split(",") } if rest
dependencies = dependencies ? dependencies.map! {|d| parse_dependency(d) } : EMPTY_ARRAY
requirements = requirements ? requirements.map! {|d| parse_dependency(d) } : EMPTY_ARRAY
[version, platform, dependencies, requirements]
end
private
def parse_dependency(string)
dependency = string.split(":")
dependency[-1] = dependency[-1].split("&") if dependency.size > 1
dependency[0] = -dependency[0]
dependency
end
end
end
end
end

View file

@ -64,7 +64,7 @@ module Bundler
end end
def gem_parser def gem_parser
@gem_parser ||= GemParser.new @gem_parser ||= Gem::Resolver::APISet::GemParser.new
end end
# This is mostly the same as `split(" ", 3)` but it avoids allocating extra objects. # This is mostly the same as `split(" ", 3)` but it avoids allocating extra objects.

View file

@ -95,6 +95,7 @@ module Bundler
@locked_ruby_version = nil @locked_ruby_version = nil
@new_platforms = [] @new_platforms = []
@removed_platforms = [] @removed_platforms = []
@originally_invalid_platforms = []
if lockfile_exists? if lockfile_exists?
@lockfile_contents = Bundler.read_file(lockfile) @lockfile_contents = Bundler.read_file(lockfile)
@ -147,9 +148,8 @@ module Bundler
@current_platform_missing = add_current_platform unless Bundler.frozen_bundle? @current_platform_missing = add_current_platform unless Bundler.frozen_bundle?
converge_path_sources_to_gemspec_sources
@path_changes = converge_paths
@source_changes = converge_sources @source_changes = converge_sources
@path_changes = converge_paths
if conservative if conservative
@gems_to_unlock = @explicit_unlocks.any? ? @explicit_unlocks : @dependencies.map(&:name) @gems_to_unlock = @explicit_unlocks.any? ? @explicit_unlocks : @dependencies.map(&:name)
@ -539,12 +539,13 @@ module Bundler
reason = resolve_needed? ? change_reason : "some dependencies were deleted from your gemfile" reason = resolve_needed? ? change_reason : "some dependencies were deleted from your gemfile"
msg = String.new msg = String.new("#{reason.capitalize.strip}, but ")
msg << "#{reason.capitalize.strip}, but the lockfile can't be updated because #{update_refused_reason}" msg << "the lockfile " unless msg.start_with?("Your lockfile")
msg << "can't be updated because #{update_refused_reason}"
msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any? msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any?
msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any? msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any?
msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any? msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any?
msg << "\n\nRun `bundle install` elsewhere and add the updated #{SharedHelpers.relative_gemfile_path} to version control.\n" unless unlocking? msg << "\n\nRun `bundle install` elsewhere and add the updated #{SharedHelpers.relative_lockfile_path} to version control.\n" unless unlocking?
msg msg
end end
@ -563,6 +564,7 @@ module Bundler
@local_changes || @local_changes ||
@missing_lockfile_dep || @missing_lockfile_dep ||
@unlocking_bundler || @unlocking_bundler ||
@locked_spec_with_missing_checksums ||
@locked_spec_with_missing_deps || @locked_spec_with_missing_deps ||
@locked_spec_with_invalid_deps @locked_spec_with_invalid_deps
end end
@ -759,7 +761,11 @@ module Bundler
end end
end end
@platforms = result.add_extra_platforms!(platforms) if should_add_extra_platforms? if should_add_extra_platforms?
result.add_extra_platforms!(platforms)
elsif @originally_invalid_platforms.any?
result.add_originally_invalid_platforms!(platforms, @originally_invalid_platforms)
end
SpecSet.new(result.for(dependencies, @platforms | [Gem::Platform::RUBY])) SpecSet.new(result.for(dependencies, @platforms | [Gem::Platform::RUBY]))
end end
@ -809,13 +815,14 @@ module Bundler
[ [
[@source_changes, "the list of sources changed"], [@source_changes, "the list of sources changed"],
[@dependency_changes, "the dependencies in your gemfile changed"], [@dependency_changes, "the dependencies in your gemfile changed"],
[@current_platform_missing, "your lockfile does not include the current platform"], [@current_platform_missing, "your lockfile is missing the current platform"],
[@new_platforms.any?, "you are adding a new platform to your lockfile"], [@new_platforms.any?, "you are adding a new platform to your lockfile"],
[@path_changes, "the gemspecs for path gems changed"], [@path_changes, "the gemspecs for path gems changed"],
[@local_changes, "the gemspecs for git local gems changed"], [@local_changes, "the gemspecs for git local gems changed"],
[@missing_lockfile_dep, "your lock file is missing \"#{@missing_lockfile_dep}\""], [@missing_lockfile_dep, "your lockfile is missing \"#{@missing_lockfile_dep}\""],
[@unlocking_bundler, "an update to the version of Bundler itself was requested"], [@unlocking_bundler, "an update to the version of Bundler itself was requested"],
[@locked_spec_with_missing_deps, "your lock file includes \"#{@locked_spec_with_missing_deps}\" but not some of its dependencies"], [@locked_spec_with_missing_checksums, "your lockfile is missing a CHECKSUMS entry for \"#{@locked_spec_with_missing_checksums}\""],
[@locked_spec_with_missing_deps, "your lockfile 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}\""], [@locked_spec_with_invalid_deps, "your lockfile does not satisfy dependencies of \"#{@locked_spec_with_invalid_deps}\""],
].select(&:first).map(&:last).join(", ") ].select(&:first).map(&:last).join(", ")
end end
@ -832,8 +839,8 @@ module Bundler
!locked || dependencies_for_source_changed?(source, locked) || specs_for_source_changed?(source) !locked || dependencies_for_source_changed?(source, locked) || specs_for_source_changed?(source)
end end
def dependencies_for_source_changed?(source, locked_source = source) def dependencies_for_source_changed?(source, locked_source)
deps_for_source = @dependencies.select {|s| s.source == source } deps_for_source = @dependencies.select {|dep| dep.source == source }
locked_deps_for_source = locked_dependencies.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 deps_for_source.uniq.sort != locked_deps_for_source.sort
@ -841,7 +848,7 @@ module Bundler
def specs_for_source_changed?(source) def specs_for_source_changed?(source)
locked_index = Index.new locked_index = Index.new
locked_index.use(@locked_specs.select {|s| source.can_lock?(s) }) locked_index.use(@locked_specs.select {|s| s.replace_source_with!(source) })
!locked_index.subset?(source.specs) !locked_index.subset?(source.specs)
rescue PathError, GitError => e rescue PathError, GitError => e
@ -873,21 +880,27 @@ module Bundler
def check_lockfile def check_lockfile
@locked_spec_with_invalid_deps = nil @locked_spec_with_invalid_deps = nil
@locked_spec_with_missing_deps = nil @locked_spec_with_missing_deps = nil
@locked_spec_with_missing_checksums = nil
missing = [] missing_deps = []
missing_checksums = []
invalid = [] invalid = []
@locked_specs.each do |s| @locked_specs.each do |s|
missing_checksums << s if @locked_checksums && s.source.checksum_store.missing?(s)
validation = @locked_specs.validate_deps(s) validation = @locked_specs.validate_deps(s)
missing << s if validation == :missing missing_deps << s if validation == :missing
invalid << s if validation == :invalid invalid << s if validation == :invalid
end end
if missing.any? @locked_spec_with_missing_checksums = missing_checksums.first.name if missing_checksums.any?
@locked_specs.delete(missing)
@locked_spec_with_missing_deps = missing.first.name if missing_deps.any?
@locked_specs.delete(missing_deps)
@locked_spec_with_missing_deps = missing_deps.first.name
end end
if invalid.any? if invalid.any?
@ -903,24 +916,6 @@ module Bundler
end end
end end
def converge_path_source_to_gemspec_source(source)
return source unless source.instance_of?(Source::Path)
gemspec_source = sources.path_sources.find {|s| s.is_a?(Source::Gemspec) && s.as_path_source == source }
gemspec_source || source
end
def converge_path_sources_to_gemspec_sources
@locked_sources.map! do |source|
converge_path_source_to_gemspec_source(source)
end
@locked_specs.each do |spec|
spec.source &&= converge_path_source_to_gemspec_source(spec.source)
end
@locked_deps.each do |_, dep|
dep.source &&= converge_path_source_to_gemspec_source(dep.source)
end
end
def converge_sources def converge_sources
# Replace the sources from the Gemfile with the sources from the Gemfile.lock, # Replace the sources from the Gemfile with the sources from the Gemfile.lock,
# if they exist in the Gemfile.lock and are `==`. If you can't find an equivalent # if they exist in the Gemfile.lock and are `==`. If you can't find an equivalent
@ -963,11 +958,17 @@ module Bundler
unless name == "bundler" unless name == "bundler"
locked_specs = @originally_locked_specs[name] locked_specs = @originally_locked_specs[name]
if locked_specs.any? && !dep.matches_spec?(locked_specs.first) if locked_specs.empty?
@gems_to_unlock << name @missing_lockfile_dep = name if dep_changed == false
dep_changed = true else
elsif locked_specs.empty? && dep_changed == false if locked_specs.map(&:source).uniq.size > 1
@missing_lockfile_dep = name @locked_specs.delete(locked_specs.select {|s| s.source != dep.source })
end
unless dep.matches_spec?(locked_specs.first)
@gems_to_unlock << name
dep_changed = true
end
end end
end end
@ -1141,16 +1142,21 @@ module Bundler
def remove_invalid_platforms! def remove_invalid_platforms!
return if Bundler.frozen_bundle? return if Bundler.frozen_bundle?
platforms.reverse_each do |platform| @originally_invalid_platforms = platforms.select do |platform|
next if local_platform == platform || next if local_platform == platform ||
@new_platforms.include?(platform) || @new_platforms.include?(platform)
@path_changes ||
@dependency_changes ||
@locked_spec_with_invalid_deps ||
!spec_set_incomplete_for_platform?(@originally_locked_specs, platform)
remove_platform(platform) # We should probably avoid removing non-ruby platforms, since that means
# lockfile will no longer install on those platforms, so a error to give
# heads up to the user may be better. However, we have tests expecting
# non ruby platform autoremoval to work, so leaving that in place for
# now.
next if @dependency_changes && platform != Gem::Platform::RUBY
spec_set_incomplete_for_platform?(@originally_locked_specs, platform)
end end
@platforms -= @originally_invalid_platforms
end end
def spec_set_incomplete_for_platform?(spec_set, platform) def spec_set_incomplete_for_platform?(spec_set, platform)

View file

@ -77,7 +77,7 @@ module Bundler
@gemspecs << spec @gemspecs << spec
path path, "glob" => glob, "name" => spec.name do path path, "glob" => glob, "name" => spec.name, "gemspec" => spec do
add_dependency spec.name add_dependency spec.name
end end
@ -141,8 +141,7 @@ module Bundler
def path(path, options = {}, &blk) def path(path, options = {}, &blk)
source_options = normalize_hash(options).merge( source_options = normalize_hash(options).merge(
"path" => Pathname.new(path), "path" => Pathname.new(path),
"root_path" => gemfile_root, "root_path" => gemfile_root
"gemspec" => gemspecs.find {|g| g.name == options["name"] }
) )
source_options["global"] = true unless block_given? source_options["global"] = true unless block_given?

View file

@ -175,6 +175,14 @@ module Bundler
@force_ruby_platform = true @force_ruby_platform = true
end end
def replace_source_with!(gemfile_source)
return unless gemfile_source.can_lock?(self)
@source = gemfile_source
true
end
private private
def use_exact_resolved_specifications? def use_exact_resolved_specifications?
@ -196,7 +204,7 @@ module Bundler
# If in frozen mode, we fallback to a non-installable candidate because by # If in frozen mode, we fallback to a non-installable candidate because by
# doing this we avoid re-resolving and potentially end up changing the # doing this we avoid re-resolving and potentially end up changing the
# lock file, which is not allowed. In that case, we will give a proper error # lockfile, which is not allowed. In that case, we will give a proper error
# about the mismatch higher up the stack, right before trying to install the # about the mismatch higher up the stack, right before trying to install the
# bad gem. # bad gem.
def choose_compatible(candidates, fallback_to_non_installable: Bundler.frozen_bundle?) def choose_compatible(candidates, fallback_to_non_installable: Bundler.frozen_bundle?)

View file

@ -239,7 +239,6 @@ module Bundler
spaces = $1 spaces = $1
return unless spaces.size == 2 return unless spaces.size == 2
checksums = $6 checksums = $6
return unless checksums
name = $2 name = $2
version = $3 version = $3
platform = $4 platform = $4
@ -249,10 +248,14 @@ module Bundler
full_name = Gem::NameTuple.new(name, version, platform).full_name full_name = Gem::NameTuple.new(name, version, platform).full_name
return unless spec = @specs[full_name] return unless spec = @specs[full_name]
checksums.split(",") do |lock_checksum| if checksums
column = line.index(lock_checksum) + 1 checksums.split(",") do |lock_checksum|
checksum = Checksum.from_lock(lock_checksum, "#{@lockfile_path}:#{@pos.line}:#{column}") column = line.index(lock_checksum) + 1
spec.source.checksum_store.register(spec, checksum) checksum = Checksum.from_lock(lock_checksum, "#{@lockfile_path}:#{@pos.line}:#{column}")
spec.source.checksum_store.register(spec, checksum)
end
else
spec.source.checksum_store.register(spec, nil)
end end
end end

View file

@ -195,7 +195,7 @@ module Bundler
@sources[name] @sources[name]
end end
# @param [Hash] The options that are present in the lock file # @param [Hash] The options that are present in the lockfile
# @return [API::Source] the instance of the class that handles the source # @return [API::Source] the instance of the class that handles the source
# type passed in locked_opts # type passed in locked_opts
def from_lock(locked_opts) def from_lock(locked_opts)

View file

@ -67,7 +67,7 @@ module Bundler
# to check out same version of gem later. # to check out same version of gem later.
# #
# There options are passed when the source plugin is created from the # There options are passed when the source plugin is created from the
# lock file. # lockfile.
# #
# @return [Hash] # @return [Hash]
def options_to_lock def options_to_lock

View file

@ -8,6 +8,14 @@ module Bundler
SharedHelpers.in_bundle? ? Bundler.root : Plugin.root SharedHelpers.in_bundle? ? Bundler.root : Plugin.root
end end
def eql?(other)
return unless other.class == self.class
expanded_original_path == other.expanded_original_path &&
version == other.version
end
alias_method :==, :eql?
def generate_bin(spec, disable_extensions = false) def generate_bin(spec, disable_extensions = false)
# Need to find a way without code duplication # Need to find a way without code duplication
# For now, we can ignore this # For now, we can ignore this

View file

@ -12,6 +12,7 @@ module Bundler
require_relative "resolver/candidate" require_relative "resolver/candidate"
require_relative "resolver/incompatibility" require_relative "resolver/incompatibility"
require_relative "resolver/root" require_relative "resolver/root"
require_relative "resolver/strategy"
include GemHelpers include GemHelpers
@ -78,7 +79,7 @@ module Bundler
end end
def solve_versions(root:, logger:) def solve_versions(root:, logger:)
solver = PubGrub::VersionSolver.new(source: self, root: root, logger: logger) solver = PubGrub::VersionSolver.new(source: self, root: root, strategy: Strategy.new(self), logger: logger)
result = solver.solve result = solver.solve
resolved_specs = result.flat_map {|package, version| version.to_specs(package, @most_specific_locked_platform) } resolved_specs = result.flat_map {|package, version| version.to_specs(package, @most_specific_locked_platform) }
SpecSet.new(resolved_specs).specs_with_additional_variants_from(@base.locked_specs) SpecSet.new(resolved_specs).specs_with_additional_variants_from(@base.locked_specs)
@ -167,15 +168,7 @@ module Bundler
end end
def versions_for(package, range=VersionRange.any) def versions_for(package, range=VersionRange.any)
versions = select_sorted_versions(package, range) range.select_versions(@sorted_versions[package])
# Conditional avoids (among other things) calling
# sort_versions_by_preferred with the root package
if versions.size > 1
sort_versions_by_preferred(package, versions)
else
versions
end
end end
def no_versions_incompatibility_for(package, unsatisfied_term) def no_versions_incompatibility_for(package, unsatisfied_term)
@ -284,8 +277,8 @@ module Bundler
ruby_group = Resolver::SpecGroup.new(ruby_specs) ruby_group = Resolver::SpecGroup.new(ruby_specs)
unless ruby_group.empty? unless ruby_group.empty?
platform_specs.each do |specs| platform_specs.each do |s|
ruby_group.merge(Resolver::SpecGroup.new(specs)) ruby_group.merge(Resolver::SpecGroup.new(s))
end end
groups << Resolver::Candidate.new(version, group: ruby_group, priority: -1) groups << Resolver::Candidate.new(version, group: ruby_group, priority: -1)
@ -355,6 +348,10 @@ module Bundler
raise GemNotFound, message raise GemNotFound, message
end end
def sort_versions_by_preferred(package, versions)
@gem_version_promoter.sort_versions(package, versions)
end
private private
def filtered_versions_for(package) def filtered_versions_for(package)
@ -414,10 +411,6 @@ module Bundler
requirement.satisfied_by?(spec.version) || spec.source.is_a?(Source::Gemspec) requirement.satisfied_by?(spec.version) || spec.source.is_a?(Source::Gemspec)
end end
def sort_versions_by_preferred(package, versions)
@gem_version_promoter.sort_versions(package, versions)
end
def repository_for(package) def repository_for(package)
source_for(package.name) source_for(package.name)
end end
@ -433,7 +426,7 @@ module Bundler
next [dep_package, dep_constraint] if name == "bundler" next [dep_package, dep_constraint] if name == "bundler"
dep_range = dep_constraint.range dep_range = dep_constraint.range
versions = select_sorted_versions(dep_package, dep_range) versions = versions_for(dep_package, dep_range)
if versions.empty? if versions.empty?
if dep_package.ignores_prereleases? || dep_package.prefer_local? if dep_package.ignores_prereleases? || dep_package.prefer_local?
@all_versions.delete(dep_package) @all_versions.delete(dep_package)
@ -441,7 +434,7 @@ module Bundler
end end
dep_package.consider_prereleases! if dep_package.ignores_prereleases? dep_package.consider_prereleases! if dep_package.ignores_prereleases?
dep_package.consider_remote_versions! if dep_package.prefer_local? dep_package.consider_remote_versions! if dep_package.prefer_local?
versions = select_sorted_versions(dep_package, dep_range) versions = versions_for(dep_package, dep_range)
end end
if versions.empty? && select_all_versions(dep_package, dep_range).any? if versions.empty? && select_all_versions(dep_package, dep_range).any?
@ -456,10 +449,6 @@ module Bundler
end.to_h end.to_h
end end
def select_sorted_versions(package, range)
range.select_versions(@sorted_versions[package])
end
def select_all_versions(package, range) def select_all_versions(package, range)
range.select_versions(@all_versions[package]) range.select_versions(@all_versions[package])
end end

View file

@ -17,7 +17,7 @@ module Bundler
# Some candidates may also keep some information explicitly about the # Some candidates may also keep some information explicitly about the
# package they refer to. These candidates are referred to as "canonical" and # package they refer to. These candidates are referred to as "canonical" and
# are used when materializing resolution results back into RubyGems # are used when materializing resolution results back into RubyGems
# specifications that can be installed, written to lock files, and so on. # specifications that can be installed, written to lockfiles, and so on.
# #
class Candidate class Candidate
include Comparable include Comparable

View file

@ -0,0 +1,40 @@
# frozen_string_literal: true
module Bundler
class Resolver
class Strategy
def initialize(source)
@source = source
end
def next_package_and_version(unsatisfied)
package, range = next_term_to_try_from(unsatisfied)
[package, most_preferred_version_of(package, range).first]
end
private
def next_term_to_try_from(unsatisfied)
unsatisfied.min_by do |package, range|
matching_versions = @source.versions_for(package, range)
higher_versions = @source.versions_for(package, range.upper_invert)
[matching_versions.count <= 1 ? 0 : 1, higher_versions.count]
end
end
def most_preferred_version_of(package, range)
versions = @source.versions_for(package, range)
# Conditional avoids (among other things) calling
# sort_versions_by_preferred with the root package
if versions.size > 1
@source.sort_versions_by_preferred(package, versions)
else
versions
end
end
end
end
end

View file

@ -262,6 +262,10 @@ module Gem
!default_gem? && !File.directory?(full_gem_path) !default_gem? && !File.directory?(full_gem_path)
end end
def lock_name
@lock_name ||= name_tuple.lock_name
end
unless VALIDATES_FOR_RESOLUTION unless VALIDATES_FOR_RESOLUTION
def validate_for_resolution def validate_for_resolution
SpecificationPolicy.new(self).validate_for_resolution SpecificationPolicy.new(self).validate_for_resolution
@ -443,6 +447,17 @@ module Gem
end end
end end
unless Gem.rubygems_version >= Gem::Version.new("3.6.7")
module UnfreezeCompactIndexParsedResponse
def parse(line)
version, platform, dependencies, requirements = super
[version, platform, dependencies.frozen? ? dependencies.dup : dependencies, requirements.frozen? ? requirements.dup : requirements]
end
end
Resolver::APISet::GemParser.prepend(UnfreezeCompactIndexParsedResponse)
end
if Gem.rubygems_version < Gem::Version.new("3.6.0") if Gem.rubygems_version < Gem::Version.new("3.6.0")
class Package; end class Package; end
require "rubygems/package/tar_reader" require "rubygems/package/tar_reader"

View file

@ -130,11 +130,14 @@ module Bundler
specs_to_cache.each do |spec| specs_to_cache.each do |spec|
next if spec.name == "bundler" next if spec.name == "bundler"
next if spec.source.is_a?(Source::Gemspec)
if spec.source.respond_to?(:migrate_cache) source = spec.source
spec.source.migrate_cache(custom_path, local: local) next if source.is_a?(Source::Gemspec)
elsif spec.source.respond_to?(:cache)
spec.source.cache(spec, custom_path) if source.respond_to?(:migrate_cache)
source.migrate_cache(custom_path, local: local)
elsif source.respond_to?(:cache)
source.cache(spec, custom_path)
end end
end end

View file

@ -4,15 +4,12 @@ module Bundler
class Source class Source
class Gemspec < Path class Gemspec < Path
attr_reader :gemspec attr_reader :gemspec
attr_writer :checksum_store
def initialize(options) def initialize(options)
super super
@gemspec = options["gemspec"] @gemspec = options["gemspec"]
end end
def as_path_source
Path.new(options)
end
end end
end end
end end

View file

@ -185,7 +185,8 @@ module Bundler
_, err, status = capture(command, nil) _, err, status = capture(command, nil)
return extra_ref if status.success? return extra_ref if status.success?
if err.include?("Could not find remote branch") if err.include?("Could not find remote branch") || # git up to 2.49
err.include?("Remote branch #{branch_option} not found") # git 2.49 or higher
raise MissingGitRevisionError.new(command_with_no_credentials, nil, explicit_ref, credential_filtered_uri) raise MissingGitRevisionError.new(command_with_no_credentials, nil, explicit_ref, credential_filtered_uri)
else else
idx = command.index("--depth") idx = command.index("--depth")
@ -262,7 +263,7 @@ module Bundler
end end
def not_pinned? def not_pinned?
branch || tag || ref.nil? branch_option || ref.nil?
end end
def pinned_to_full_sha? def pinned_to_full_sha?
@ -426,7 +427,7 @@ module Bundler
# anyways. # anyways.
return args if @revision return args if @revision
args += ["--branch", branch || tag] if branch || tag args += ["--branch", branch_option] if branch_option
args args
end end
@ -442,6 +443,10 @@ module Bundler
extra_args extra_args
end end
def branch_option
branch || tag
end
def full_clone? def full_clone?
depth.nil? depth.nil?
end end

View file

@ -60,8 +60,8 @@ module Bundler
end end
def eql?(other) def eql?(other)
return unless other.class == self.class [Gemspec, Path].include?(other.class) &&
expanded_original_path == other.expanded_original_path && expanded_original_path == other.expanded_original_path &&
version == other.version version == other.version
end end

View file

@ -173,39 +173,57 @@ module Bundler
def map_sources(replacement_sources) def map_sources(replacement_sources)
rubygems = @rubygems_sources.map do |source| rubygems = @rubygems_sources.map do |source|
replace_rubygems_source(replacement_sources, source) || source replace_rubygems_source(replacement_sources, source)
end end
git, plugin = [@git_sources, @plugin_sources].map do |sources| git, plugin = [@git_sources, @plugin_sources].map do |sources|
sources.map do |source| sources.map do |source|
replacement_sources.find {|s| s == source } || source replace_source(replacement_sources, source)
end end
end end
path = @path_sources.map do |source| path = @path_sources.map do |source|
replacement_sources.find {|s| s == (source.is_a?(Source::Gemspec) ? source.as_path_source : source) } || source replace_path_source(replacement_sources, source)
end end
[rubygems, path, git, plugin] [rubygems, path, git, plugin]
end end
def global_replacement_source(replacement_sources) def global_replacement_source(replacement_sources)
replacement_source = replace_rubygems_source(replacement_sources, global_rubygems_source) replace_rubygems_source(replacement_sources, global_rubygems_source, &:local!)
return global_rubygems_source unless replacement_source
replacement_source.local!
replacement_source
end end
def replace_rubygems_source(replacement_sources, gemfile_source) def replace_rubygems_source(replacement_sources, gemfile_source)
replacement_source = replacement_sources.find {|s| s == gemfile_source } replace_source(replacement_sources, gemfile_source) do |replacement_source|
return unless replacement_source # locked sources never include credentials so always prefer remotes from the gemfile
replacement_source.remotes = gemfile_source.remotes
yield replacement_source if block_given?
replacement_source
end
end
def replace_source(replacement_sources, gemfile_source)
replacement_source = replacement_sources.find {|s| s == gemfile_source }
return gemfile_source unless replacement_source
replacement_source = yield(replacement_source) if block_given?
# locked sources never include credentials so always prefer remotes from the gemfile
replacement_source.remotes = gemfile_source.remotes
replacement_source replacement_source
end end
def replace_path_source(replacement_sources, gemfile_source)
replace_source(replacement_sources, gemfile_source) do |replacement_source|
if gemfile_source.is_a?(Source::Gemspec)
gemfile_source.checksum_store = replacement_source.checksum_store
gemfile_source
else
replacement_source
end
end
end
def different_sources?(lock_sources, replacement_sources) def different_sources?(lock_sources, replacement_sources)
!equivalent_sources?(lock_sources, replacement_sources) !equivalent_sources?(lock_sources, replacement_sources)
end end

View file

@ -47,8 +47,17 @@ module Bundler
end.uniq end.uniq
end end
def add_originally_invalid_platforms!(platforms, originally_invalid_platforms)
originally_invalid_platforms.each do |originally_invalid_platform|
platforms << originally_invalid_platform if complete_platform(originally_invalid_platform)
end
end
def add_extra_platforms!(platforms) def add_extra_platforms!(platforms)
return platforms.concat([Gem::Platform::RUBY]).uniq if @specs.empty? if @specs.empty?
platforms.concat([Gem::Platform::RUBY]).uniq
return
end
new_platforms = all_platforms.select do |platform| new_platforms = all_platforms.select do |platform|
next if platforms.include?(platform) next if platforms.include?(platform)
@ -56,14 +65,12 @@ module Bundler
complete_platform(platform) complete_platform(platform)
end end
return platforms if new_platforms.empty? return if new_platforms.empty?
platforms.concat(new_platforms) platforms.concat(new_platforms)
less_specific_platform = new_platforms.find {|platform| platform != Gem::Platform::RUBY && Bundler.local_platform === platform && platform === Bundler.local_platform } less_specific_platform = new_platforms.find {|platform| platform != Gem::Platform::RUBY && Bundler.local_platform === platform && platform === Bundler.local_platform }
platforms.delete(Bundler.local_platform) if less_specific_platform platforms.delete(Bundler.local_platform) if less_specific_platform
platforms
end end
def validate_deps(s) def validate_deps(s)

View file

@ -160,6 +160,12 @@ class Bundler::ConnectionPool
@available.shutdown(reload: true, &block) @available.shutdown(reload: true, &block)
end end
## Reaps idle connections that have been idle for over +idle_seconds+.
# +idle_seconds+ defaults to 60.
def reap(idle_seconds = 60, &block)
@available.reap(idle_seconds, &block)
end
# Size of this connection pool # Size of this connection pool
attr_reader :size attr_reader :size
# Automatically drop all connections after fork # Automatically drop all connections after fork
@ -169,6 +175,11 @@ class Bundler::ConnectionPool
def available def available
@available.length @available.length
end end
# Number of pool entries created and idle in the pool.
def idle
@available.idle
end
end end
require_relative "connection_pool/timed_stack" require_relative "connection_pool/timed_stack"

View file

@ -41,6 +41,7 @@ class Bundler::ConnectionPool::TimedStack
def push(obj, options = {}) def push(obj, options = {})
@mutex.synchronize do @mutex.synchronize do
if @shutdown_block if @shutdown_block
@created -= 1 unless @created == 0
@shutdown_block.call(obj) @shutdown_block.call(obj)
else else
store_connection obj, options store_connection obj, options
@ -98,6 +99,26 @@ class Bundler::ConnectionPool::TimedStack
end end
end end
##
# Reaps connections that were checked in more than +idle_seconds+ ago.
def reap(idle_seconds, &block)
raise ArgumentError, "reap must receive a block" unless block
raise ArgumentError, "idle_seconds must be a number" unless idle_seconds.is_a?(Numeric)
raise Bundler::ConnectionPool::PoolShuttingDownError if @shutdown_block
idle.times do
conn =
@mutex.synchronize do
raise Bundler::ConnectionPool::PoolShuttingDownError if @shutdown_block
reserve_idle_connection(idle_seconds)
end
break unless conn
block.call(conn)
end
end
## ##
# Returns +true+ if there are no available connections. # Returns +true+ if there are no available connections.
@ -112,6 +133,12 @@ class Bundler::ConnectionPool::TimedStack
@max - @created + @que.length @max - @created + @que.length
end end
##
# The number of connections created and available on the stack.
def idle
@que.length
end
private private
def current_time def current_time
@ -133,7 +160,7 @@ class Bundler::ConnectionPool::TimedStack
# This method must return a connection from the stack. # This method must return a connection from the stack.
def fetch_connection(options = nil) def fetch_connection(options = nil)
@que.pop @que.pop&.first
end end
## ##
@ -144,9 +171,32 @@ class Bundler::ConnectionPool::TimedStack
def shutdown_connections(options = nil) def shutdown_connections(options = nil)
while connection_stored?(options) while connection_stored?(options)
conn = fetch_connection(options) conn = fetch_connection(options)
@created -= 1 unless @created == 0
@shutdown_block.call(conn) @shutdown_block.call(conn)
end end
@created = 0 end
##
# This is an extension point for TimedStack and is called with a mutex.
#
# This method returns the oldest idle connection if it has been idle for more than idle_seconds.
# This requires that the stack is kept in order of checked in time (oldest first).
def reserve_idle_connection(idle_seconds)
return unless idle_connections?(idle_seconds)
@created -= 1 unless @created == 0
@que.shift.first
end
##
# This is an extension point for TimedStack and is called with a mutex.
#
# Returns true if the first connection in the stack has been idle for more than idle_seconds
def idle_connections?(idle_seconds)
connection_stored? && (current_time - @que.first.last > idle_seconds)
end end
## ##
@ -155,7 +205,7 @@ class Bundler::ConnectionPool::TimedStack
# This method must return +obj+ to the stack. # This method must return +obj+ to the stack.
def store_connection(obj, options = nil) def store_connection(obj, options = nil)
@que.push obj @que.push [obj, current_time]
end end
## ##

View file

@ -1,3 +1,3 @@
class Bundler::ConnectionPool class Bundler::ConnectionPool
VERSION = "2.4.1" VERSION = "2.5.0"
end end

View file

@ -79,29 +79,17 @@ module Bundler::PubGrub
dependencies_for(@root_package, @root_version) dependencies_for(@root_package, @root_version)
end end
# Override me (maybe)
#
# If not overridden, the order returned by all_versions_for will be used
#
# Returns: Array of versions in preferred order
def sort_versions_by_preferred(package, sorted_versions)
indexes = @version_indexes[package]
sorted_versions.sort_by { |version| indexes[version] }
end
def initialize def initialize
@root_package = Package.root @root_package = Package.root
@root_version = Package.root_version @root_version = Package.root_version
@cached_versions = Hash.new do |h,k| @sorted_versions = Hash.new do |h,k|
if k == @root_package if k == @root_package
h[k] = [@root_version] h[k] = [@root_version]
else else
h[k] = all_versions_for(k) h[k] = all_versions_for(k).sort
end end
end end
@sorted_versions = Hash.new { |h,k| h[k] = @cached_versions[k].sort }
@version_indexes = Hash.new { |h,k| h[k] = @cached_versions[k].each.with_index.to_h }
@cached_dependencies = Hash.new do |packages, package| @cached_dependencies = Hash.new do |packages, package|
if package == @root_package if package == @root_package
@ -117,15 +105,7 @@ module Bundler::PubGrub
end end
def versions_for(package, range=VersionRange.any) def versions_for(package, range=VersionRange.any)
versions = range.select_versions(@sorted_versions[package]) range.select_versions(@sorted_versions[package])
# Conditional avoids (among other things) calling
# sort_versions_by_preferred with the root package
if versions.size > 1
sort_versions_by_preferred(package, versions)
else
versions
end
end end
def no_versions_incompatibility_for(_package, unsatisfied_term) def no_versions_incompatibility_for(_package, unsatisfied_term)
@ -164,7 +144,7 @@ module Bundler::PubGrub
sorted_versions[high] sorted_versions[high]
end end
range = VersionRange.new(min: low, max: high, include_min: true) range = VersionRange.new(min: low, max: high, include_min: !low.nil?)
self_constraint = VersionConstraint.new(package, range: range) self_constraint = VersionConstraint.new(package, range: range)

View file

@ -0,0 +1,42 @@
module Bundler::PubGrub
class Strategy
def initialize(source)
@source = source
@root_package = Package.root
@root_version = Package.root_version
@version_indexes = Hash.new do |h,k|
if k == @root_package
h[k] = { @root_version => 0 }
else
h[k] = @source.all_versions_for(k).each.with_index.to_h
end
end
end
def next_package_and_version(unsatisfied)
package, range = next_term_to_try_from(unsatisfied)
[package, most_preferred_version_of(package, range)]
end
private
def most_preferred_version_of(package, range)
versions = @source.versions_for(package, range)
indexes = @version_indexes[package]
versions.min_by { |version| indexes[version] }
end
def next_term_to_try_from(unsatisfied)
unsatisfied.min_by do |package, range|
matching_versions = @source.versions_for(package, range)
higher_versions = @source.versions_for(package, range.upper_invert)
[matching_versions.count <= 1 ? 0 : 1, higher_versions.count]
end
end
end
end

View file

@ -76,6 +76,9 @@ module Bundler::PubGrub
end end
def initialize(min: nil, max: nil, include_min: false, include_max: false, name: nil) def initialize(min: nil, max: nil, include_min: false, include_max: false, name: nil)
raise ArgumentError, "Ranges without a lower bound cannot have include_min == true" if !min && include_min == true
raise ArgumentError, "Ranges without an upper bound cannot have include_max == true" if !max && include_max == true
@min = min @min = min
@max = max @max = max
@include_min = include_min @include_min = include_min
@ -311,10 +314,19 @@ module Bundler::PubGrub
def contiguous_to?(other) def contiguous_to?(other)
return false if other.empty? return false if other.empty?
return true if any?
intersects?(other) || intersects?(other) || contiguous_below?(other) || contiguous_above?(other)
(min == other.max && (include_min || other.include_max)) || end
(max == other.min && (include_max || other.include_min))
def contiguous_below?(other)
return false if !max || !other.min
max == other.min && (include_max || other.include_min)
end
def contiguous_above?(other)
other.contiguous_below?(self)
end end
def allows_all?(other) def allows_all?(other)
@ -375,15 +387,15 @@ module Bundler::PubGrub
def invert def invert
return self.class.empty if any? return self.class.empty if any?
low = VersionRange.new(max: min, include_max: !include_min) low = -> { VersionRange.new(max: min, include_max: !include_min) }
high = VersionRange.new(min: max, include_min: !include_max) high = -> { VersionRange.new(min: max, include_min: !include_max) }
if !min if !min
high high.call
elsif !max elsif !max
low low.call
else else
low.union(high) low.call.union(high.call)
end end
end end

View file

@ -2,17 +2,20 @@ require_relative 'partial_solution'
require_relative 'term' require_relative 'term'
require_relative 'incompatibility' require_relative 'incompatibility'
require_relative 'solve_failure' require_relative 'solve_failure'
require_relative 'strategy'
module Bundler::PubGrub module Bundler::PubGrub
class VersionSolver class VersionSolver
attr_reader :logger attr_reader :logger
attr_reader :source attr_reader :source
attr_reader :solution attr_reader :solution
attr_reader :strategy
def initialize(source:, root: Package.root, logger: Bundler::PubGrub.logger) def initialize(source:, root: Package.root, strategy: Strategy.new(source), logger: Bundler::PubGrub.logger)
@logger = logger @logger = logger
@source = source @source = source
@strategy = strategy
# { package => [incompatibility, ...]} # { package => [incompatibility, ...]}
@incompatibilities = Hash.new do |h, k| @incompatibilities = Hash.new do |h, k|
@ -36,26 +39,25 @@ module Bundler::PubGrub
# Returns true if there is more work to be done, false otherwise # Returns true if there is more work to be done, false otherwise
def work def work
return false if solved? unsatisfied_terms = solution.unsatisfied
if unsatisfied_terms.empty?
next_package = choose_package_version
propagate(next_package)
if solved?
logger.info { "Solution found after #{solution.attempted_solutions} attempts:" } logger.info { "Solution found after #{solution.attempted_solutions} attempts:" }
solution.decisions.each do |package, version| solution.decisions.each do |package, version|
next if Package.root?(package) next if Package.root?(package)
logger.info { "* #{package} #{version}" } logger.info { "* #{package} #{version}" }
end end
false return false
else
true
end end
next_package = choose_package_version_from(unsatisfied_terms)
propagate(next_package)
true
end end
def solve def solve
work until solved? while work; end
solution.decisions solution.decisions
end end
@ -105,29 +107,15 @@ module Bundler::PubGrub
unsatisfied.package unsatisfied.package
end end
def next_package_to_try def choose_package_version_from(unsatisfied_terms)
solution.unsatisfied.min_by do |term| remaining = unsatisfied_terms.map { |t| [t.package, t.constraint.range] }.to_h
package = term.package
range = term.constraint.range
matching_versions = source.versions_for(package, range)
higher_versions = source.versions_for(package, range.upper_invert)
[matching_versions.count <= 1 ? 0 : 1, higher_versions.count] package, version = strategy.next_package_and_version(remaining)
end.package
end
def choose_package_version
if solution.unsatisfied.empty?
logger.info "No packages unsatisfied. Solving complete!"
return nil
end
package = next_package_to_try
unsatisfied_term = solution.unsatisfied.find { |t| t.package == package }
version = source.versions_for(package, unsatisfied_term.constraint.range).first
logger.debug { "attempting #{package} #{version}" } logger.debug { "attempting #{package} #{version}" }
if version.nil? if version.nil?
unsatisfied_term = unsatisfied_terms.find { |t| t.package == package }
add_incompatibility source.no_versions_incompatibility_for(package, unsatisfied_term) add_incompatibility source.no_versions_incompatibility_for(package, unsatisfied_term)
return package return package
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: false # frozen_string_literal: false
module Bundler module Bundler
VERSION = "2.6.6".freeze VERSION = "2.6.7".freeze
def self.bundler_major_version def self.bundler_major_version
@bundler_major_version ||= VERSION.split(".").first.to_i @bundler_major_version ||= VERSION.split(".").first.to_i

View file

@ -9,7 +9,7 @@
require "rbconfig" require "rbconfig"
module Gem module Gem
VERSION = "3.6.6" VERSION = "3.6.7"
end end
# Must be first since it unloads the prelude from 1.9.2 # Must be first since it unloads the prelude from 1.9.2
@ -156,6 +156,13 @@ module Gem
specifications/default specifications/default
].freeze ].freeze
##
# The default value for SOURCE_DATE_EPOCH if not specified.
# We want a date after 1980-01-01, to prevent issues with Zip files.
# This particular timestamp is for 1980-01-02 00:00:00 GMT.
DEFAULT_SOURCE_DATE_EPOCH = 315_619_200
@@win_platform = nil @@win_platform = nil
@configuration = nil @configuration = nil
@ -1155,8 +1162,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
## ##
# If the SOURCE_DATE_EPOCH environment variable is set, returns it's value. # If the SOURCE_DATE_EPOCH environment variable is set, returns it's value.
# Otherwise, returns the time that +Gem.source_date_epoch_string+ was # Otherwise, returns DEFAULT_SOURCE_DATE_EPOCH as a string.
# first called in the same format as SOURCE_DATE_EPOCH.
# #
# NOTE(@duckinator): The implementation is a tad weird because we want to: # NOTE(@duckinator): The implementation is a tad weird because we want to:
# 1. Make builds reproducible by default, by having this function always # 1. Make builds reproducible by default, by having this function always
@ -1171,15 +1177,12 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
# https://reproducible-builds.org/specs/source-date-epoch/ # https://reproducible-builds.org/specs/source-date-epoch/
def self.source_date_epoch_string def self.source_date_epoch_string
# The value used if $SOURCE_DATE_EPOCH is not set.
@default_source_date_epoch ||= Time.now.to_i.to_s
specified_epoch = ENV["SOURCE_DATE_EPOCH"] specified_epoch = ENV["SOURCE_DATE_EPOCH"]
# If it's empty or just whitespace, treat it like it wasn't set at all. # If it's empty or just whitespace, treat it like it wasn't set at all.
specified_epoch = nil if !specified_epoch.nil? && specified_epoch.strip.empty? specified_epoch = nil if !specified_epoch.nil? && specified_epoch.strip.empty?
epoch = specified_epoch || @default_source_date_epoch epoch = specified_epoch || DEFAULT_SOURCE_DATE_EPOCH.to_s
epoch.strip epoch.strip
end end

View file

@ -195,7 +195,7 @@ to the same gem path as user-installed gems.
argv = ARGV.clone argv = ARGV.clone
ARGV.replace options[:args] ARGV.replace options[:args]
exe = executable = options[:executable] executable = options[:executable]
contains_executable = Gem.loaded_specs.values.select do |spec| contains_executable = Gem.loaded_specs.values.select do |spec|
spec.executables.include?(executable) spec.executables.include?(executable)
@ -206,13 +206,22 @@ to the same gem path as user-installed gems.
end end
if contains_executable.empty? if contains_executable.empty?
if (spec = Gem.loaded_specs[executable]) && (exe = spec.executable) spec = Gem.loaded_specs[executable]
contains_executable << spec
else if spec.nil? || spec.executables.empty?
alert_error "Failed to load executable `#{executable}`," \ alert_error "Failed to load executable `#{executable}`," \
" are you sure the gem `#{options[:gem_name]}` contains it?" " are you sure the gem `#{options[:gem_name]}` contains it?"
terminate_interaction 1 terminate_interaction 1
end end
if spec.executables.size > 1
alert_error "Ambiguous which executable from gem `#{executable}` should be run: " \
"the options are #{spec.executables.sort}, specify one via COMMAND, and use `-g` and `-v` to specify gem and version"
terminate_interaction 1
end
contains_executable << spec
executable = spec.executable
end end
if contains_executable.size > 1 if contains_executable.size > 1
@ -223,8 +232,8 @@ to the same gem path as user-installed gems.
end end
old_exe = $0 old_exe = $0
$0 = exe $0 = executable
load Gem.activate_bin_path(contains_executable.first.name, exe, ">= 0.a") load Gem.activate_bin_path(contains_executable.first.name, executable, ">= 0.a")
ensure ensure
$0 = old_exe if old_exe $0 = old_exe if old_exe
ARGV.replace argv ARGV.replace argv

View file

@ -239,7 +239,7 @@ module Gem
# Enables automatic installation into user directory # Enables automatic installation into user directory
def self.default_user_install # :nodoc: def self.default_user_install # :nodoc:
if !ENV.key?("GEM_HOME") && (File.exist?(Gem.dir) && !File.writable?(Gem.dir)) if !ENV.key?("GEM_HOME") && File.exist?(Gem.dir) && !File.writable?(Gem.dir)
Gem.ui.say "Defaulting to user installation because default installation directory (#{Gem.dir}) is not writable." Gem.ui.say "Defaulting to user installation because default installation directory (#{Gem.dir}) is not writable."
return true return true
end end

View file

@ -1,15 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
class Gem::Resolver::APISet::GemParser class Gem::Resolver::APISet::GemParser
EMPTY_ARRAY = [].freeze
private_constant :EMPTY_ARRAY
def parse(line) def parse(line)
version_and_platform, rest = line.split(" ", 2) version_and_platform, rest = line.split(" ", 2)
version, platform = version_and_platform.split("-", 2) version, platform = version_and_platform.split("-", 2)
dependencies, requirements = rest.split("|", 2).map! {|s| s.split(",") } if rest dependencies, requirements = rest.split("|", 2).map! {|s| s.split(",") } if rest
dependencies = dependencies ? dependencies.map! {|d| parse_dependency(d) } : EMPTY_ARRAY dependencies = dependencies ? dependencies.map! {|d| parse_dependency(d) } : []
requirements = requirements ? requirements.map! {|d| parse_dependency(d) } : EMPTY_ARRAY requirements = requirements ? requirements.map! {|d| parse_dependency(d) } : []
[version, platform, dependencies, requirements] [version, platform, dependencies, requirements]
end end

View file

@ -2144,11 +2144,11 @@ class Gem::Specification < Gem::BasicSpecification
@files.concat(@extra_rdoc_files) @files.concat(@extra_rdoc_files)
end end
@files = @files.uniq if @files @files = @files.uniq.sort if @files
@extensions = @extensions.uniq if @extensions @extensions = @extensions.uniq.sort if @extensions
@test_files = @test_files.uniq if @test_files @test_files = @test_files.uniq.sort if @test_files
@executables = @executables.uniq if @executables @executables = @executables.uniq.sort if @executables
@extra_rdoc_files = @extra_rdoc_files.uniq if @extra_rdoc_files @extra_rdoc_files = @extra_rdoc_files.uniq.sort if @extra_rdoc_files
end end
## ##

View file

@ -368,13 +368,13 @@ class Gem::Version
lhsize = lhsegments.size lhsize = lhsegments.size
rhsize = rhsegments.size rhsize = rhsegments.size
limit = (lhsize > rhsize ? lhsize : rhsize) - 1 limit = (lhsize > rhsize ? rhsize : lhsize)
i = 0 i = 0
while i <= limit while i < limit
lhs = lhsegments[i] || 0 lhs = lhsegments[i]
rhs = rhsegments[i] || 0 rhs = rhsegments[i]
i += 1 i += 1
next if lhs == rhs next if lhs == rhs
@ -384,6 +384,24 @@ class Gem::Version
return lhs <=> rhs return lhs <=> rhs
end end
lhs = lhsegments[i]
if lhs.nil?
rhs = rhsegments[i]
while i < rhsize
return 1 if String === rhs
return -1 unless rhs.zero?
rhs = rhsegments[i += 1]
end
else
while i < lhsize
return -1 if String === lhs
return 1 unless lhs.zero?
lhs = lhsegments[i += 1]
end
end
0 0
end end

View file

@ -33,7 +33,7 @@ RSpec.describe Bundler::Definition do
before { Bundler::Definition.no_lock = true } before { Bundler::Definition.no_lock = true }
after { Bundler::Definition.no_lock = false } after { Bundler::Definition.no_lock = false }
it "does not create a lock file" do it "does not create a lockfile" do
subject.lock subject.lock
expect(bundled_app_lock).not_to be_file expect(bundled_app_lock).not_to be_file
end end

View file

@ -138,7 +138,7 @@ RSpec.describe Bundler::LockfileParser do
expect(subject.ruby_version).to eq ruby_version expect(subject.ruby_version).to eq ruby_version
rake_spec = specs.last rake_spec = specs.last
checksums = subject.sources.last.checksum_store.to_lock(specs.last) checksums = subject.sources.last.checksum_store.to_lock(specs.last)
expect(checksums).to eq("#{rake_spec.name_tuple.lock_name} #{rake_checksums.map(&:to_lock).sort.join(",")}") expect(checksums).to eq("#{rake_spec.lock_name} #{rake_checksums.map(&:to_lock).sort.join(",")}")
end end
end end

View file

@ -59,7 +59,7 @@ RSpec.describe Bundler::SharedHelpers do
before { allow(subject).to receive(:default_gemfile).and_return(gemfile_path) } before { allow(subject).to receive(:default_gemfile).and_return(gemfile_path) }
it "returns the lock file path" do it "returns the lockfile path" do
expect(subject.default_lockfile).to eq(expected_lockfile_path) expect(subject.default_lockfile).to eq(expected_lockfile_path)
end end
end end

View file

@ -258,7 +258,7 @@ RSpec.describe "bundle check" do
expect(err).not_to include("Unfortunately, a fatal error has occurred. ") expect(err).not_to include("Unfortunately, a fatal error has occurred. ")
end end
it "fails when there's no lock file and frozen is set" do it "fails when there's no lockfile and frozen is set" do
install_gemfile <<-G install_gemfile <<-G
source "https://gem.repo1" source "https://gem.repo1"
gem "foo" gem "foo"

View file

@ -49,7 +49,7 @@ RSpec.describe "bundle install with gem sources" do
expect(bundled_app(".bundle")).not_to exist expect(bundled_app(".bundle")).not_to exist
end end
it "creates lock files based on the Gemfile name" do it "creates lockfiles based on the Gemfile name" do
gemfile bundled_app("OmgFile"), <<-G gemfile bundled_app("OmgFile"), <<-G
source "https://gem.repo1" source "https://gem.repo1"
gem "myrack", "1.0" gem "myrack", "1.0"
@ -1368,7 +1368,7 @@ RSpec.describe "bundle install with gem sources" do
it "adds the current platform to the lockfile" do it "adds the current platform to the lockfile" do
bundle "install --verbose" bundle "install --verbose"
expect(out).to include("re-resolving dependencies because your lockfile does not include the current platform") expect(out).to include("re-resolving dependencies because your lockfile is missing the current platform")
expect(out).not_to include("you are adding a new platform to your lockfile") expect(out).not_to include("you are adding a new platform to your lockfile")
expect(lockfile).to eq <<~L expect(lockfile).to eq <<~L

View file

@ -80,7 +80,7 @@ RSpec.describe "bundle update" do
end end
describe "with --gemfile" do describe "with --gemfile" do
it "creates lock files based on the Gemfile name" do it "creates lockfiles based on the Gemfile name" do
gemfile bundled_app("OmgFile"), <<-G gemfile bundled_app("OmgFile"), <<-G
source "https://gem.repo1" source "https://gem.repo1"
gem "myrack", "1.0" gem "myrack", "1.0"
@ -640,7 +640,7 @@ RSpec.describe "bundle update" do
myrack myrack
PLATFORMS PLATFORMS
#{local_platform} x86-darwin-100
DEPENDENCIES DEPENDENCIES
activesupport activesupport

View file

@ -321,7 +321,7 @@ RSpec.describe "install in deployment or frozen mode" do
L L
bundle :install, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false, artifice: "compact_index" bundle :install, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false, artifice: "compact_index"
expect(err).to include("Your lock file is missing \"bar\", but the lockfile can't be updated because frozen mode is set") expect(err).to include("Your lockfile is missing \"bar\", but can't be updated because frozen mode is set")
end end
it "explodes if a path gem is missing" do it "explodes if a path gem is missing" do
@ -550,7 +550,7 @@ RSpec.describe "install in deployment or frozen mode" do
pristine_system_gems :bundler pristine_system_gems :bundler
bundle "config set --local deployment true" bundle "config set --local deployment true"
bundle "install --verbose" bundle "install --verbose"
expect(out).not_to include("but the lockfile can't be updated because frozen mode is set") expect(out).not_to include("can't be updated because frozen mode is set")
expect(out).not_to include("You have added to the Gemfile") expect(out).not_to include("You have added to the Gemfile")
expect(out).not_to include("You have deleted from the Gemfile") expect(out).not_to include("You have deleted from the Gemfile")
expect(out).to include("vendor/cache/foo") expect(out).to include("vendor/cache/foo")

View file

@ -273,7 +273,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0", source: "remote3") expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0", source: "remote3")
# In https://github.com/bundler/bundler/issues/3585 this failed # In https://github.com/bundler/bundler/issues/3585 this failed
# when there is already a lock file, and the gems are missing, so try again # when there is already a lockfile, and the gems are missing, so try again
system_gems [] system_gems []
bundle :install, artifice: "compact_index" bundle :install, artifice: "compact_index"
@ -482,7 +482,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0") expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0")
# In https://github.com/rubygems/bundler/issues/3585 this failed # In https://github.com/rubygems/bundler/issues/3585 this failed
# when there is already a lock file, and the gems are missing, so try again # when there is already a lockfile, and the gems are missing, so try again
system_gems [] system_gems []
bundle :install, artifice: "compact_index" bundle :install, artifice: "compact_index"
@ -1221,7 +1221,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
DEPENDENCIES DEPENDENCIES
myrack! myrack!
#{checksums_section}
BUNDLED WITH BUNDLED WITH
#{Bundler::VERSION} #{Bundler::VERSION}
L L

View file

@ -91,7 +91,7 @@ RSpec.describe "bundle install with specific platforms" do
#{Bundler::VERSION} #{Bundler::VERSION}
L L
# force strict usage of the lock file by setting frozen mode # force strict usage of the lockfile by setting frozen mode
bundle "config set --local frozen true" bundle "config set --local frozen true"
# make sure the platform that got actually installed with the old bundler is used # make sure the platform that got actually installed with the old bundler is used
@ -752,6 +752,80 @@ RSpec.describe "bundle install with specific platforms" do
L L
end end
it "automatically fixes the lockfile when adding a gem that introduces dependencies with no ruby platform variants transitively" do
simulate_platform "x86_64-linux" do
build_repo4 do
build_gem "nokogiri", "1.18.2"
build_gem "nokogiri", "1.18.2" do |s|
s.platform = "x86_64-linux"
end
build_gem("sorbet", "0.5.11835") do |s|
s.add_dependency "sorbet-static", "= 0.5.11835"
end
build_gem "sorbet-static", "0.5.11835" do |s|
s.platform = "x86_64-linux"
end
end
gemfile <<~G
source "https://gem.repo4"
gem "nokogiri"
gem "sorbet"
G
lockfile <<~L
GEM
remote: https://gem.repo4/
specs:
nokogiri (1.18.2)
nokogiri (1.18.2-x86_64-linux)
PLATFORMS
ruby
x86_64-linux
DEPENDENCIES
nokogiri
BUNDLED WITH
#{Bundler::VERSION}
L
bundle "lock"
checksums = checksums_section_when_enabled do |c|
c.checksum gem_repo4, "nokogiri", "1.18.2", "x86_64-linux"
c.checksum gem_repo4, "sorbet", "0.5.11835"
c.checksum gem_repo4, "sorbet-static", "0.5.11835", "x86_64-linux"
end
expect(lockfile).to eq <<~L
GEM
remote: https://gem.repo4/
specs:
nokogiri (1.18.2)
nokogiri (1.18.2-x86_64-linux)
sorbet (0.5.11835)
sorbet-static (= 0.5.11835)
sorbet-static (0.5.11835-x86_64-linux)
PLATFORMS
x86_64-linux
DEPENDENCIES
nokogiri
sorbet
#{checksums}
BUNDLED WITH
#{Bundler::VERSION}
L
end
end
it "automatically fixes the lockfile if multiple platforms locked, but no valid versions of direct dependencies for all of them" do it "automatically fixes the lockfile if multiple platforms locked, but no valid versions of direct dependencies for all of them" do
simulate_platform "x86_64-linux" do simulate_platform "x86_64-linux" do
build_repo4 do build_repo4 do

View file

@ -96,7 +96,6 @@ RSpec.describe "compact index api" do
bundle :install, artifice: "compact_index" bundle :install, artifice: "compact_index"
bundle "config set --local deployment true" bundle "config set --local deployment true"
bundle "config set --local path vendor/bundle"
bundle :install, artifice: "compact_index" bundle :install, artifice: "compact_index"
expect(out).to include("Fetching gem metadata from #{source_uri}") expect(out).to include("Fetching gem metadata from #{source_uri}")
expect(the_bundle).to include_gems "myrack 1.0.0" expect(the_bundle).to include_gems "myrack 1.0.0"
@ -1090,4 +1089,11 @@ Running `bundle update rails` should fix the problem.
count = lockfile.match?("CHECKSUMS") ? 2 : 1 # Once in the specs, and once in CHECKSUMS count = lockfile.match?("CHECKSUMS") ? 2 : 1 # Once in the specs, and once in CHECKSUMS
expect(lockfile.scan(/activemerchant \(/).size).to eq(count) expect(lockfile.scan(/activemerchant \(/).size).to eq(count)
end end
it "handles an API that does not provide checksums info (undocumented, support may get removed)" do
install_gemfile <<-G, artifice: "compact_index_no_checksums"
source "https://gem.repo1"
gem "rake"
G
end
end end

View file

@ -61,7 +61,6 @@ RSpec.describe "gemcutter's dependency API" do
bundle :install, artifice: "endpoint" bundle :install, artifice: "endpoint"
bundle "config set --local deployment true" bundle "config set --local deployment true"
bundle "config set --local path vendor/bundle"
bundle :install, artifice: "endpoint" bundle :install, artifice: "endpoint"
expect(out).to include("Fetching gem metadata from #{source_uri}") expect(out).to include("Fetching gem metadata from #{source_uri}")
expect(the_bundle).to include_gems "myrack 1.0.0" expect(the_bundle).to include_gems "myrack 1.0.0"

View file

@ -394,7 +394,7 @@ RSpec.describe "bundle install with install-time dependencies" do
end end
end end
context "with a Gemfile and lock file that don't resolve under the current platform" do context "with a Gemfile and lockfile that don't resolve under the current platform" do
before do before do
build_repo4 do build_repo4 do
build_gem "sorbet", "0.5.10554" do |s| build_gem "sorbet", "0.5.10554" do |s|

View file

@ -248,6 +248,7 @@ RSpec.describe "global gem caching" do
describe "extension caching" do describe "extension caching" do
it "works" do it "works" do
skip "gets incorrect ref in path" if Gem.win_platform? skip "gets incorrect ref in path" if Gem.win_platform?
skip "fails for unknown reason when run by ruby-core" if ruby_core?
build_git "very_simple_git_binary", &:add_c_extension build_git "very_simple_git_binary", &:add_c_extension
build_lib "very_simple_path_binary", &:add_c_extension build_lib "very_simple_path_binary", &:add_c_extension

View file

@ -24,7 +24,7 @@ RSpec.describe "process lock spec" do
context "when creating a lock raises Errno::ENOTSUP" do context "when creating a lock raises Errno::ENOTSUP" do
before { allow(File).to receive(:open).and_raise(Errno::ENOTSUP) } before { allow(File).to receive(:open).and_raise(Errno::ENOTSUP) }
it "skips creating the lock file and yields" do it "skips creating the lockfile and yields" do
processed = false processed = false
Bundler::ProcessLock.lock(default_bundle_path) { processed = true } Bundler::ProcessLock.lock(default_bundle_path) { processed = true }
@ -35,7 +35,7 @@ RSpec.describe "process lock spec" do
context "when creating a lock raises Errno::EPERM" do context "when creating a lock raises Errno::EPERM" do
before { allow(File).to receive(:open).and_raise(Errno::EPERM) } before { allow(File).to receive(:open).and_raise(Errno::EPERM) }
it "skips creating the lock file and yields" do it "skips creating the lockfile and yields" do
processed = false processed = false
Bundler::ProcessLock.lock(default_bundle_path) { processed = true } Bundler::ProcessLock.lock(default_bundle_path) { processed = true }
@ -46,7 +46,7 @@ RSpec.describe "process lock spec" do
context "when creating a lock raises Errno::EROFS" do context "when creating a lock raises Errno::EROFS" do
before { allow(File).to receive(:open).and_raise(Errno::EROFS) } before { allow(File).to receive(:open).and_raise(Errno::EROFS) }
it "skips creating the lock file and yields" do it "skips creating the lockfile and yields" do
processed = false processed = false
Bundler::ProcessLock.lock(default_bundle_path) { processed = true } Bundler::ProcessLock.lock(default_bundle_path) { processed = true }

View file

@ -1613,6 +1613,39 @@ RSpec.describe "the lockfile format" do
expect(the_bundle).not_to include_gems "myrack_middleware 1.0" expect(the_bundle).not_to include_gems "myrack_middleware 1.0"
end end
it "raises a clear error when frozen mode is set and lockfile is missing entries in CHECKSUMS section, and does not install any gems" do
lockfile <<-L
GEM
remote: https://gem.repo2/
specs:
myrack_middleware (1.0)
PLATFORMS
#{lockfile_platforms}
DEPENDENCIES
myrack_middleware
CHECKSUMS
BUNDLED WITH
#{Bundler::VERSION}
L
install_gemfile <<-G, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false
source "https://gem.repo2"
gem "myrack_middleware"
G
expect(err).to eq <<~L.strip
Your lockfile is missing a checksums entry for \"myrack_middleware\", but can't be updated because frozen mode is set
Run `bundle install` elsewhere and add the updated Gemfile.lock to version control.
L
expect(the_bundle).not_to include_gems "myrack_middleware 1.0"
end
it "automatically fixes the lockfile when it's missing deps, they conflict with other locked deps, but conflicts are fixable" do it "automatically fixes the lockfile when it's missing deps, they conflict with other locked deps, but conflicts are fixable" do
build_repo4 do build_repo4 do
build_gem "other_dep", "0.9" build_gem "other_dep", "0.9"
@ -1875,6 +1908,120 @@ RSpec.describe "the lockfile format" do
L L
end end
it "automatically fixes the lockfile when it includes a gem under the correct GIT section, but also under an incorrect GEM section, with a higher version, and with no explicit Gemfile requirement" do
git = build_git "foo"
gemfile <<~G
source "https://gem.repo1/"
gem "foo", git: "#{lib_path("foo-1.0")}"
G
# If the lockfile erroneously lists platform versions of the gem
# that don't match the locked version of the git repo we should remove them.
lockfile <<~L
GIT
remote: #{lib_path("foo-1.0")}
revision: #{git.ref_for("main")}
specs:
foo (1.0)
GEM
remote: https://gem.repo1/
specs:
foo (1.1-x86_64-linux-gnu)
PLATFORMS
#{lockfile_platforms}
DEPENDENCIES
foo!
BUNDLED WITH
#{Bundler::VERSION}
L
bundle "install"
expect(lockfile).to eq <<~L
GIT
remote: #{lib_path("foo-1.0")}
revision: #{git.ref_for("main")}
specs:
foo (1.0)
GEM
remote: https://gem.repo1/
specs:
PLATFORMS
#{lockfile_platforms}
DEPENDENCIES
foo!
BUNDLED WITH
#{Bundler::VERSION}
L
end
it "automatically fixes the lockfile when it includes a gem under the correct GIT section, but also under an incorrect GEM section, with a higher version" do
git = build_git "foo"
gemfile <<~G
source "https://gem.repo1/"
gem "foo", "= 1.0", git: "#{lib_path("foo-1.0")}"
G
# If the lockfile erroneously lists platform versions of the gem
# that don't match the locked version of the git repo we should remove them.
lockfile <<~L
GIT
remote: #{lib_path("foo-1.0")}
revision: #{git.ref_for("main")}
specs:
foo (1.0)
GEM
remote: https://gem.repo1/
specs:
foo (1.1-x86_64-linux-gnu)
PLATFORMS
#{lockfile_platforms}
DEPENDENCIES
foo (= 1.0)!
BUNDLED WITH
#{Bundler::VERSION}
L
bundle "install"
expect(lockfile).to eq <<~L
GIT
remote: #{lib_path("foo-1.0")}
revision: #{git.ref_for("main")}
specs:
foo (1.0)
GEM
remote: https://gem.repo1/
specs:
PLATFORMS
#{lockfile_platforms}
DEPENDENCIES
foo (= 1.0)!
BUNDLED WITH
#{Bundler::VERSION}
L
end
it "automatically fixes the lockfile when it has incorrect deps, keeping the locked version" do it "automatically fixes the lockfile when it has incorrect deps, keeping the locked version" do
build_repo4 do build_repo4 do
build_gem "net-smtp", "0.5.0" do |s| build_gem "net-smtp", "0.5.0" do |s|
@ -2030,7 +2177,7 @@ RSpec.describe "the lockfile format" do
L L
bundle "install --verbose" bundle "install --verbose"
expect(out).to include("re-resolving dependencies because your lock file includes \"minitest-bisect\" but not some of its dependencies") expect(out).to include("re-resolving dependencies because your lockfile includes \"minitest-bisect\" but not some of its dependencies")
expect(lockfile).to eq <<~L expect(lockfile).to eq <<~L
GEM GEM

View file

@ -67,7 +67,7 @@ RSpec.describe "real source plugins" do
expect(the_bundle).to include_gems("a-path-gem 1.0") expect(the_bundle).to include_gems("a-path-gem 1.0")
end end
it "writes to lock file" do it "writes to lockfile" do
bundle "install" bundle "install"
checksums = checksums_section_when_enabled do |c| checksums = checksums_section_when_enabled do |c|
@ -336,7 +336,7 @@ RSpec.describe "real source plugins" do
expect(the_bundle).to include_gems("ma-gitp-gem 1.0") expect(the_bundle).to include_gems("ma-gitp-gem 1.0")
end end
it "writes to lock file" do it "writes to lockfile" do
revision = revision_for(lib_path("ma-gitp-gem-1.0")) revision = revision_for(lib_path("ma-gitp-gem-1.0"))
bundle "install" bundle "install"

View file

@ -1683,7 +1683,7 @@ end
RUBY RUBY
end end
expect(err).to include("Your lockfile does not include the current platform, but the lockfile can't be updated because file system is read-only") expect(err).to include("Your lockfile is missing the current platform, but can't be updated because file system is read-only")
end end
end end
end end

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
require_relative "helpers/compact_index"
class CompactIndexNoChecksums < CompactIndexAPI
get "/info/:name" do
etag_response do
gem = gems.find {|g| g.name == params[:name] }
gem.versions.map(&:number).join("\n")
end
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexNoChecksums)

View file

@ -370,8 +370,11 @@ class TestGemCommandsExecCommand < Gem::TestCase
util_clear_gems util_clear_gems
use_ui @ui do use_ui @ui do
@cmd.invoke "a:2" e = assert_raise Gem::MockGemUi::TermError do
assert_equal "a-2 foo\n", @ui.output @cmd.invoke "a:2"
end
assert_equal 1, e.exit_code
assert_equal "ERROR: Ambiguous which executable from gem `a` should be run: the options are [\"bar\", \"foo\"], specify one via COMMAND, and use `-g` and `-v` to specify gem and version\n", @ui.error
end end
end end

View file

@ -29,7 +29,7 @@ class TestGemExtCmakeBuilder < Gem::TestCase
def test_self_build def test_self_build
File.open File.join(@ext, "CMakeLists.txt"), "w" do |cmakelists| File.open File.join(@ext, "CMakeLists.txt"), "w" do |cmakelists|
cmakelists.write <<-EO_CMAKE cmakelists.write <<-EO_CMAKE
cmake_minimum_required(VERSION 2.6) cmake_minimum_required(VERSION 3.5)
project(self_build NONE) project(self_build NONE)
install (FILES test.txt DESTINATION bin) install (FILES test.txt DESTINATION bin)
EO_CMAKE EO_CMAKE

View file

@ -33,7 +33,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
f.write "a" * 10 f.write "a" * 10
end end
assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now), assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512]) @io.string[0, 512])
end end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@ -54,7 +54,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
Time.stub :now, Time.at(1_458_518_157) do Time.stub :now, Time.at(1_458_518_157) do
@tar_writer.add_symlink "x", "y", 0o644 @tar_writer.add_symlink "x", "y", 0o644
assert_headers_equal(tar_symlink_header("x", "", 0o644, Time.now, "y"), assert_headers_equal(tar_symlink_header("x", "", 0o644, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc, "y"),
@io.string[0, 512]) @io.string[0, 512])
end end
assert_equal 512, @io.pos assert_equal 512, @io.pos
@ -86,7 +86,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
"e1cf14b0", "e1cf14b0",
digests["SHA512"].hexdigest digests["SHA512"].hexdigest
assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now), assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512]) @io.string[0, 512])
end end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@ -109,7 +109,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
"e1cf14b0", "e1cf14b0",
digests["SHA512"].hexdigest digests["SHA512"].hexdigest
assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now), assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512]) @io.string[0, 512])
end end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@ -126,7 +126,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
io.write "a" * 10 io.write "a" * 10
end end
assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now), assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512]) @io.string[0, 512])
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@ -137,7 +137,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
signature = signer.sign digest.digest signature = signer.sign digest.digest
assert_headers_equal(tar_file_header("x.sig", "", 0o444, signature.length, assert_headers_equal(tar_file_header("x.sig", "", 0o444, signature.length,
Time.now), Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[1024, 512]) @io.string[1024, 512])
assert_equal "#{signature}#{"\0" * (512 - signature.length)}", assert_equal "#{signature}#{"\0" * (512 - signature.length)}",
@io.string[1536, 512] @io.string[1536, 512]
@ -154,7 +154,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
io.write "a" * 10 io.write "a" * 10
end end
assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now), assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512]) @io.string[0, 512])
end end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@ -168,7 +168,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
io.write "a" * 10 io.write "a" * 10
end end
assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now), assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512]) @io.string[0, 512])
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@ -192,7 +192,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
Time.stub :now, Time.at(1_458_518_157) do Time.stub :now, Time.at(1_458_518_157) do
@tar_writer.add_file_simple "x", 0, 100 @tar_writer.add_file_simple "x", 0, 100
assert_headers_equal tar_file_header("x", "", 0, 100, Time.now), assert_headers_equal tar_file_header("x", "", 0, 100, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512] @io.string[0, 512]
end end
@ -250,7 +250,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
Time.stub :now, Time.at(1_458_518_157) do Time.stub :now, Time.at(1_458_518_157) do
@tar_writer.mkdir "foo", 0o644 @tar_writer.mkdir "foo", 0o644
assert_headers_equal tar_dir_header("foo", "", 0o644, Time.now), assert_headers_equal tar_dir_header("foo", "", 0o644, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512] @io.string[0, 512]
assert_equal 512, @io.pos assert_equal 512, @io.pos

View file

@ -16,7 +16,7 @@ rubygems_version: "1.0"
name: keyedlist name: keyedlist
version: !ruby/object:Gem::Version version: !ruby/object:Gem::Version
version: 0.4.0 version: 0.4.0
date: 2004-03-28 15:37:49.828000 +02:00 date: 1980-01-02 00:00:00 UTC
platform: platform:
summary: A Hash which automatically computes keys. summary: A Hash which automatically computes keys.
require_paths: require_paths:
@ -75,7 +75,7 @@ end
def assert_date(date) def assert_date(date)
assert_kind_of Time, date assert_kind_of Time, date
assert_equal [0, 0, 0], [date.hour, date.min, date.sec] assert_equal [0, 0, 0], [date.hour, date.min, date.sec]
assert_operator (Gem::Specification::TODAY..Time.now), :cover?, date assert_equal Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc, date
end end
def setup def setup