2018-11-03 02:07:56 +03:00
# frozen_string_literal: true
2019-06-01 12:49:40 +03:00
require_relative " lockfile_parser "
2018-11-03 02:07:56 +03:00
module Bundler
class Definition
include GemHelpers
2021-01-04 04:11:34 +03:00
class << self
# Do not create or modify a lockfile (Makes #lock a noop)
attr_accessor :no_lock
end
2018-11-03 02:07:56 +03:00
attr_reader (
:dependencies ,
:locked_deps ,
:locked_gems ,
:platforms ,
:requires ,
:ruby_version ,
:lockfile ,
:gemfiles
)
# Given a gemfile and lockfile creates a Bundler definition
#
# @param gemfile [Pathname] Path to Gemfile
# @param lockfile [Pathname,nil] Path to Gemfile.lock
# @param unlock [Hash, Boolean, nil] Gems that have been requested
# to be updated or true if all gems should be updated
# @return [Bundler::Definition]
def self . build ( gemfile , lockfile , unlock )
unlock || = { }
gemfile = Pathname . new ( gemfile ) . expand_path
raise GemfileNotFound , " #{ gemfile } not found " unless gemfile . file?
Dsl . evaluate ( gemfile , lockfile , unlock )
end
#
# How does the new system work?
#
# * Load information from Gemfile and Lockfile
# * Invalidate stale locked specs
# * All specs from stale source are stale
# * All specs that are reachable only through a stale
# dependency are stale.
# * If all fresh dependencies are satisfied by the locked
# specs, then we can try to resolve locally.
#
# @param lockfile [Pathname] Path to Gemfile.lock
# @param dependencies [Array(Bundler::Dependency)] array of dependencies from Gemfile
# @param sources [Bundler::SourceList]
# @param unlock [Hash, Boolean, nil] Gems that have been requested
# to be updated or true if all gems should be updated
# @param ruby_version [Bundler::RubyVersion, nil] Requested Ruby Version
# @param optional_groups [Array(String)] A list of optional groups
def initialize ( lockfile , dependencies , sources , unlock , ruby_version = nil , optional_groups = [ ] , gemfiles = [ ] )
if [ true , false ] . include? ( unlock )
@unlocking_bundler = false
@unlocking = unlock
else
@unlocking_bundler = unlock . delete ( :bundler )
2021-07-07 08:07:29 +03:00
@unlocking = unlock . any? { | _k , v | ! Array ( v ) . empty? }
2018-11-03 02:07:56 +03:00
end
@dependencies = dependencies
@sources = sources
@unlock = unlock
@optional_groups = optional_groups
@remote = false
2022-07-29 08:59:56 +03:00
@prefer_local = false
2018-11-03 02:07:56 +03:00
@specs = nil
@ruby_version = ruby_version
@gemfiles = gemfiles
@lockfile = lockfile
@lockfile_contents = String . new
@locked_bundler_version = nil
@locked_ruby_version = nil
2020-05-08 08:19:04 +03:00
@new_platform = nil
2018-11-03 02:07:56 +03:00
if lockfile && File . exist? ( lockfile )
@lockfile_contents = Bundler . read_file ( lockfile )
@locked_gems = LockfileParser . new ( @lockfile_contents )
@locked_platforms = @locked_gems . platforms
2021-02-01 18:17:16 +03:00
@platforms = @locked_platforms . dup
2018-11-03 02:07:56 +03:00
@locked_bundler_version = @locked_gems . bundler_version
@locked_ruby_version = @locked_gems . ruby_version
2021-12-27 03:41:55 +03:00
@originally_locked_specs = SpecSet . new ( @locked_gems . specs )
2018-11-03 02:07:56 +03:00
if unlock != true
@locked_deps = @locked_gems . dependencies
2021-12-27 03:41:55 +03:00
@locked_specs = @originally_locked_specs
2018-11-03 02:07:56 +03:00
@locked_sources = @locked_gems . sources
else
@unlock = { }
@locked_deps = { }
@locked_specs = SpecSet . new ( [ ] )
@locked_sources = [ ]
end
else
@unlock = { }
@platforms = [ ]
@locked_gems = nil
@locked_deps = { }
@locked_specs = SpecSet . new ( [ ] )
2022-08-24 11:17:04 +03:00
@originally_locked_specs = @locked_specs
2018-11-03 02:07:56 +03:00
@locked_sources = [ ]
@locked_platforms = [ ]
end
2021-07-07 08:07:29 +03:00
locked_gem_sources = @locked_sources . select { | s | s . is_a? ( Source :: Rubygems ) }
@multisource_allowed = locked_gem_sources . size == 1 && locked_gem_sources . first . multiple_remotes? && Bundler . frozen_bundle?
2021-04-15 06:47:04 +03:00
2021-05-28 13:47:49 +03:00
if @multisource_allowed
unless sources . aggregate_global_source?
msg = " Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure. "
2021-04-15 06:47:04 +03:00
2021-05-28 13:47:49 +03:00
Bundler :: SharedHelpers . major_deprecation 2 , msg
end
2021-04-15 06:47:04 +03:00
2021-07-07 08:07:29 +03:00
@sources . merged_gem_lockfile_sections! ( locked_gem_sources . first )
2021-04-15 06:47:04 +03:00
end
2018-11-03 02:07:56 +03:00
@unlock [ :sources ] || = [ ]
@unlock [ :ruby ] || = if @ruby_version && locked_ruby_version_object
@ruby_version . diff ( locked_ruby_version_object )
end
@unlocking || = @unlock [ :ruby ] || = ( ! @locked_ruby_version ^ ! @ruby_version )
2020-12-23 02:45:19 +03:00
add_current_platform unless current_ruby_platform_locked? || Bundler . frozen_bundle?
2018-11-03 02:07:56 +03:00
converge_path_sources_to_gemspec_sources
@path_changes = converge_paths
@source_changes = converge_sources
2021-05-28 13:47:49 +03:00
if @unlock [ :conservative ]
@unlock [ :gems ] || = @dependencies . map ( & :name )
else
2022-07-06 14:47:11 +03:00
eager_unlock = ( @unlock [ :gems ] || [ ] ) . map { | name | Dependency . new ( name , " >= 0 " ) }
2022-10-03 13:10:05 +03:00
@unlock [ :gems ] = @locked_specs . for ( eager_unlock , false , platforms ) . map ( & :name ) . uniq
2018-11-03 02:07:56 +03:00
end
@dependency_changes = converge_dependencies
@local_changes = converge_locals
@requires = compute_requires
end
def gem_version_promoter
2022-11-12 00:00:58 +03:00
@gem_version_promoter || = GemVersionPromoter . new
2018-11-03 02:07:56 +03:00
end
2021-08-18 10:58:51 +03:00
def resolve_only_locally!
@remote = false
sources . local_only!
resolve
end
2022-07-29 08:59:56 +03:00
def resolve_prefering_local!
@prefer_local = true
@remote = true
sources . remote!
resolve
end
2018-11-03 02:07:56 +03:00
def resolve_with_cache!
sources . cached!
2021-04-21 14:54:29 +03:00
resolve
2018-11-03 02:07:56 +03:00
end
def resolve_remotely!
@remote = true
sources . remote!
2021-04-21 14:54:29 +03:00
resolve
2018-11-03 02:07:56 +03:00
end
# For given dependency list returns a SpecSet with Gemspec of all the required
# dependencies.
# 1. The method first resolves the dependencies specified in Gemfile
# 2. After that it tries and fetches gemspec of resolved dependencies
#
# @return [Bundler::SpecSet]
def specs
2021-07-24 00:06:29 +03:00
@specs || = materialize ( requested_dependencies )
2018-11-03 02:07:56 +03:00
end
def new_specs
specs - @locked_specs
end
def removed_specs
@locked_specs - specs
end
def missing_specs
2021-07-24 00:49:13 +03:00
resolve . materialize ( requested_dependencies ) . missing_specs
2018-11-03 02:07:56 +03:00
end
def missing_specs?
missing = missing_specs
return false if missing . empty?
Bundler . ui . debug " The definition is missing #{ missing . map ( & :full_name ) } "
true
rescue BundlerError = > e
@resolve = nil
2022-08-22 05:52:51 +03:00
@resolver = nil
2018-11-03 02:07:56 +03:00
@specs = nil
@gem_version_promoter = nil
Bundler . ui . debug " The definition is missing dependencies, failed to resolve & materialize locally ( #{ e } ) "
true
end
def requested_specs
2021-07-13 14:58:08 +03:00
specs_for ( requested_groups )
2018-11-03 02:07:56 +03:00
end
2020-10-15 07:20:25 +03:00
def requested_dependencies
2021-07-13 14:58:08 +03:00
dependencies_for ( requested_groups )
2020-10-15 07:20:25 +03:00
end
2018-11-03 02:07:56 +03:00
def current_dependencies
2020-05-08 08:19:04 +03:00
dependencies . select do | d |
2022-07-06 23:21:32 +03:00
d . should_include? && ! d . gem_platforms ( [ generic_local_platform ] ) . empty?
2020-05-08 08:19:04 +03:00
end
2018-11-03 02:07:56 +03:00
end
2021-09-29 21:07:08 +03:00
def locked_dependencies
@locked_deps . values
end
2022-07-13 06:56:36 +03:00
def new_deps
@new_deps || = @dependencies - locked_dependencies
end
def deleted_deps
@deleted_deps || = locked_dependencies - @dependencies
end
2018-11-03 02:07:56 +03:00
def specs_for ( groups )
2021-10-28 10:51:14 +03:00
return specs if groups . empty?
2020-05-08 08:19:04 +03:00
deps = dependencies_for ( groups )
2021-10-28 10:51:14 +03:00
materialize ( deps )
2018-11-03 02:07:56 +03:00
end
2020-10-15 07:20:25 +03:00
def dependencies_for ( groups )
2021-07-13 14:58:08 +03:00
groups . map! ( & :to_sym )
2022-07-06 23:21:32 +03:00
current_dependencies . reject do | d |
2020-10-15 07:20:25 +03:00
( d . groups & groups ) . empty?
end
end
2018-11-03 02:07:56 +03:00
# Resolve all the dependencies specified in Gemfile. It ensures that
# dependencies that have been already resolved via locked file and are fresh
# are reused when resolving dependencies
#
# @return [SpecSet] resolved dependencies
def resolve
2022-06-23 12:22:36 +03:00
@resolve || = if Bundler . frozen_bundle?
Bundler . ui . debug " Frozen, using resolution from the lockfile "
@locked_specs
elsif ! unlocking? && nothing_changed?
2022-07-13 06:56:36 +03:00
if deleted_deps . any?
Bundler . ui . debug ( " Some dependencies were deleted, using a subset of the resolution from the lockfile " )
SpecSet . new ( filter_specs ( @locked_specs , @dependencies - deleted_deps ) )
else
Bundler . ui . debug ( " Found no changes, using resolution from the lockfile " )
if @locked_gems . may_include_redundant_platform_specific_gems?
SpecSet . new ( filter_specs ( @locked_specs , @dependencies ) )
else
@locked_specs
end
end
2022-06-23 12:22:36 +03:00
else
Bundler . ui . debug ( " Found changes from the lockfile, re-resolving dependencies because #{ change_reason } " )
2022-11-12 00:00:58 +03:00
start_resolution
2018-11-03 02:07:56 +03:00
end
end
def spec_git_paths
2019-09-04 19:20:09 +03:00
sources . git_sources . map { | s | File . realpath ( s . path ) if File . exist? ( s . path ) } . compact
2018-11-03 02:07:56 +03:00
end
def groups
dependencies . map ( & :groups ) . flatten . uniq
end
def lock ( file , preserve_unknown_sections = false )
2021-01-04 04:11:34 +03:00
return if Definition . no_lock
2018-11-03 02:07:56 +03:00
contents = to_lock
# Convert to \r\n if the existing lock has them
# i.e., Windows with `git config core.autocrlf=true`
contents . gsub! ( / \ n / , " \r \n " ) if @lockfile_contents . match ( " \r \n " )
2019-04-14 09:01:35 +03:00
if @locked_bundler_version
2018-11-03 02:07:56 +03:00
locked_major = @locked_bundler_version . segments . first
2022-11-12 00:00:58 +03:00
current_major = Bundler . gem_version . segments . first
2018-11-03 02:07:56 +03:00
2021-12-24 03:32:59 +03:00
updating_major = locked_major < current_major
2018-11-03 02:07:56 +03:00
end
preserve_unknown_sections || = ! updating_major && ( Bundler . frozen_bundle? || ! ( unlocking? || @unlocking_bundler ) )
return if file && File . exist? ( file ) && lockfiles_equal? ( @lockfile_contents , contents , preserve_unknown_sections )
if Bundler . frozen_bundle?
Bundler . ui . error " Cannot write a changed lockfile while frozen. "
return
end
SharedHelpers . filesystem_access ( file ) do | p |
File . open ( p , " wb " ) { | f | f . puts ( contents ) }
end
end
def locked_ruby_version
return unless ruby_version
if @unlock [ :ruby ] || ! @locked_ruby_version
Bundler :: RubyVersion . system
else
@locked_ruby_version
end
end
def locked_ruby_version_object
return unless @locked_ruby_version
@locked_ruby_version_object || = begin
unless version = RubyVersion . from_string ( @locked_ruby_version )
raise LockfileError , " The Ruby version #{ @locked_ruby_version } from " \
" #{ @lockfile } could not be parsed. " \
" Try running bundle update --ruby to resolve this. "
end
version
end
end
def to_lock
2019-06-01 12:49:40 +03:00
require_relative " lockfile_generator "
2018-11-03 02:07:56 +03:00
LockfileGenerator . generate ( self )
end
def ensure_equivalent_gemfile_and_lockfile ( explicit_flag = false )
msg = String . new
msg << " You are trying to install in deployment mode after changing \n " \
" your Gemfile. Run `bundle install` elsewhere and add the \n " \
" updated #{ Bundler . default_lockfile . relative_path_from ( SharedHelpers . pwd ) } to version control. "
unless explicit_flag
2020-05-25 13:18:44 +03:00
suggested_command = if Bundler . settings . locations ( " frozen " ) . keys . & ( [ :global , :local ] ) . any?
2019-04-14 09:01:35 +03:00
" bundle config unset frozen "
2018-11-03 02:07:56 +03:00
elsif Bundler . settings . locations ( " deployment " ) . keys . & ( [ :global , :local ] ) . any?
2019-04-14 09:01:35 +03:00
" bundle config unset deployment "
2018-11-03 02:07:56 +03:00
end
msg << " \n \n If this is a development machine, remove the #{ Bundler . default_gemfile } " \
2022-11-11 09:05:59 +03:00
" freeze \n by running ` #{ suggested_command } `. " if suggested_command
2018-11-03 02:07:56 +03:00
end
added = [ ]
deleted = [ ]
changed = [ ]
new_platforms = @platforms - @locked_platforms
deleted_platforms = @locked_platforms - @platforms
added . concat new_platforms . map { | p | " * platform: #{ p } " }
deleted . concat deleted_platforms . map { | p | " * platform: #{ p } " }
added . concat new_deps . map { | d | " * #{ pretty_dep ( d ) } " } if new_deps . any?
2021-11-17 14:01:55 +03:00
deleted . concat deleted_deps . map { | d | " * #{ pretty_dep ( d ) } " } if deleted_deps . any?
2018-11-03 02:07:56 +03:00
both_sources = Hash . new { | h , k | h [ k ] = [ ] }
@dependencies . each { | d | both_sources [ d . name ] [ 0 ] = d }
2021-12-08 20:36:13 +03:00
locked_dependencies . each do | d |
next if ! Bundler . feature_flag . bundler_3_mode? && @locked_specs [ d . name ] . empty?
both_sources [ d . name ] [ 1 ] = d
end
2021-11-17 17:14:30 +03:00
both_sources . each do | name , ( dep , lock_dep ) |
next if dep . nil? || lock_dep . nil?
gemfile_source = dep . source || sources . default_source
lock_source = lock_dep . source || sources . default_source
next if lock_source . include? ( gemfile_source )
2018-11-03 02:07:56 +03:00
2021-11-17 17:14:30 +03:00
gemfile_source_name = dep . source ? gemfile_source . identifier : " no specified source "
lockfile_source_name = lock_dep . source ? lock_source . identifier : " no specified source "
2021-11-17 16:21:48 +03:00
changed << " * #{ name } from ` #{ lockfile_source_name } ` to ` #{ gemfile_source_name } ` "
2018-11-03 02:07:56 +03:00
end
reason = change_reason
msg << " \n \n #{ reason . split ( " , " ) . map ( & :capitalize ) . join ( " \n " ) } " unless reason . strip . empty?
msg << " \n \n You have added to the Gemfile: \n " << added . join ( " \n " ) if added . any?
msg << " \n \n You have deleted from the Gemfile: \n " << deleted . join ( " \n " ) if deleted . any?
msg << " \n \n You have changed in the Gemfile: \n " << changed . join ( " \n " ) if changed . any?
msg << " \n "
raise ProductionError , msg if added . any? || deleted . any? || changed . any? || ! nothing_changed?
end
def validate_runtime!
validate_ruby!
validate_platforms!
end
def validate_ruby!
return unless ruby_version
if diff = ruby_version . diff ( Bundler :: RubyVersion . system )
problem , expected , actual = diff
msg = case problem
when :engine
" Your Ruby engine is #{ actual } , but your Gemfile specified #{ expected } "
when :version
" Your Ruby version is #{ actual } , but your Gemfile specified #{ expected } "
when :engine_version
" Your #{ Bundler :: RubyVersion . system . engine } version is #{ actual } , but your Gemfile specified #{ ruby_version . engine } #{ expected } "
when :patchlevel
if ! expected . is_a? ( String )
" The Ruby patchlevel in your Gemfile must be a string "
else
" Your Ruby patchlevel is #{ actual } , but your Gemfile specified #{ expected } "
end
end
raise RubyVersionMismatch , msg
end
end
def validate_platforms!
2020-12-23 02:45:19 +03:00
return if current_platform_locked?
2018-11-03 02:07:56 +03:00
raise ProductionError , " Your bundle only supports platforms #{ @platforms . map ( & :to_s ) } " \
2020-12-23 02:45:19 +03:00
" but your local platform is #{ Bundler . local_platform } . " \
2022-02-12 10:04:13 +03:00
" Add the current platform to the lockfile with \n `bundle lock --add-platform #{ Bundler . local_platform } ` and try again. "
2018-11-03 02:07:56 +03:00
end
def add_platform ( platform )
@new_platform || = ! @platforms . include? ( platform )
@platforms |= [ platform ]
end
def remove_platform ( platform )
return if @platforms . delete ( Gem :: Platform . new ( platform ) )
raise InvalidOption , " Unable to remove the platform ` #{ platform } ` since the only platforms are #{ @platforms . join " , " } "
end
2020-12-23 02:45:19 +03:00
def most_specific_locked_platform
@platforms . min_by do | bundle_platform |
platform_specificity_match ( bundle_platform , local_platform )
end
end
2018-11-03 02:07:56 +03:00
attr_reader :sources
private :sources
def nothing_changed?
2022-07-26 07:43:48 +03:00
! @source_changes && ! @dependency_changes && ! @new_platform && ! @path_changes && ! @local_changes
2018-11-03 02:07:56 +03:00
end
def unlocking?
@unlocking
end
2020-10-15 07:20:25 +03:00
private
2018-11-03 02:07:56 +03:00
2022-08-22 05:52:51 +03:00
def resolver
@resolver || = begin
last_resolve = converge_locked_specs
2022-08-23 09:36:30 +03:00
remove_ruby_from_platforms_if_necessary! ( current_dependencies )
2022-11-12 00:00:58 +03:00
Resolver . new ( source_requirements , last_resolve , gem_version_promoter , additional_base_requirements_for_resolve ( last_resolve ) )
2022-08-22 05:52:51 +03:00
end
end
def expanded_dependencies
2022-10-18 09:24:42 +03:00
@expanded_dependencies || = dependencies + metadata_dependencies
2022-07-26 07:43:48 +03:00
end
2022-11-12 00:00:58 +03:00
def resolution_packages
@resolution_packages || = begin
packages = Hash . new do | h , k |
h [ k ] = Resolver :: Package . new ( k , @platforms , @originally_locked_specs , @unlock [ :gems ] )
end
expanded_dependencies . each do | dep |
name = dep . name
platforms = dep . gem_platforms ( @platforms )
packages [ name ] = Resolver :: Package . new ( name , platforms , @originally_locked_specs , @unlock [ :gems ] , :dependency = > dep )
end
packages
end
end
2022-04-28 11:20:33 +03:00
def filter_specs ( specs , deps )
2022-07-04 15:47:53 +03:00
SpecSet . new ( specs ) . for ( deps , false , platforms )
2022-04-28 11:20:33 +03:00
end
2021-07-24 00:06:29 +03:00
def materialize ( dependencies )
specs = resolve . materialize ( dependencies )
2021-07-24 00:49:13 +03:00
missing_specs = specs . missing_specs
if missing_specs . any?
missing_specs . each do | s |
locked_gem = @locked_specs [ s . name ] . last
next if locked_gem . nil? || locked_gem . version != s . version || ! @remote
raise GemNotFound , " Your bundle is locked to #{ locked_gem } from #{ locked_gem . source } , but that version can " \
" no longer be found in that source. That means the author of #{ locked_gem } has removed it. " \
" You'll need to update your bundle to a version other than #{ locked_gem } that hasn't been " \
" removed in order to install. "
end
2021-11-19 15:13:19 +03:00
missing_specs_list = missing_specs . group_by ( & :source ) . map do | source , missing_specs_for_source |
" #{ missing_specs_for_source . map ( & :full_name ) . join ( " , " ) } in #{ source } "
end
raise GemNotFound , " Could not find #{ missing_specs_list . join ( " nor " ) } "
2021-07-24 00:49:13 +03:00
end
2021-07-24 00:06:29 +03:00
2022-08-22 05:52:51 +03:00
loop do
2022-07-26 07:43:48 +03:00
incomplete_specs = specs . incomplete_specs
2022-08-22 05:52:51 +03:00
break if incomplete_specs . empty?
2022-07-26 07:43:48 +03:00
2022-08-22 05:52:51 +03:00
Bundler . ui . debug ( " The lockfile does not have all gems needed for the current platform though, Bundler will still re-resolve dependencies " )
2022-11-12 00:00:58 +03:00
@resolve = start_resolution ( :exclude_specs = > incomplete_specs )
2022-08-22 05:52:51 +03:00
specs = resolve . materialize ( dependencies )
2022-07-26 07:43:48 +03:00
end
2022-11-12 00:00:58 +03:00
bundler = sources . metadata_source . specs . search ( [ " bundler " , Bundler . gem_version ] ) . last
2022-07-23 10:19:34 +03:00
specs [ " bundler " ] = bundler
2021-07-13 14:58:08 +03:00
specs
end
2022-11-12 00:00:58 +03:00
def start_resolution ( exclude_specs : [ ] )
result = resolver . start ( expanded_dependencies , resolution_packages , :exclude_specs = > exclude_specs )
SpecSet . new ( SpecSet . new ( result ) . for ( dependencies , false , @platforms ) )
end
2021-05-28 13:47:49 +03:00
def precompute_source_requirements_for_indirect_dependencies?
2021-07-31 16:05:29 +03:00
@remote && sources . non_global_rubygems_sources . all? ( & :dependency_api_available? ) && ! sources . aggregate_global_source?
2021-05-28 13:47:49 +03:00
end
2022-07-29 08:59:56 +03:00
def pin_locally_available_names ( source_requirements )
source_requirements . each_with_object ( { } ) do | ( name , original_source ) , new_source_requirements |
local_source = original_source . dup
local_source . local_only!
new_source_requirements [ name ] = if local_source . specs . search ( name ) . any?
local_source
else
original_source
end
end
end
2020-12-23 02:45:19 +03:00
def current_ruby_platform_locked?
return false unless generic_local_platform == Gem :: Platform :: RUBY
2022-01-24 14:04:11 +03:00
return false if Bundler . settings [ :force_ruby_platform ] && ! @platforms . include? ( Gem :: Platform :: RUBY )
2020-12-23 02:45:19 +03:00
current_platform_locked?
end
def current_platform_locked?
@platforms . any? do | bundle_platform |
MatchPlatform . platforms_match? ( bundle_platform , Bundler . local_platform )
end
end
2020-12-08 10:36:29 +03:00
def add_current_platform
2020-12-15 02:32:54 +03:00
add_platform ( local_platform )
2019-06-19 16:29:02 +03:00
end
2018-11-03 02:07:56 +03:00
def change_reason
if unlocking?
unlock_reason = @unlock . reject { | _k , v | Array ( v ) . empty? } . map do | k , v |
if v == true
k . to_s
else
v = Array ( v )
" #{ k } : ( #{ v . join ( " , " ) } ) "
end
end . join ( " , " )
return " bundler is unlocking #{ unlock_reason } "
end
[
[ @source_changes , " the list of sources changed " ] ,
[ @dependency_changes , " the dependencies in your gemfile changed " ] ,
[ @new_platform , " you added a new platform to your gemfile " ] ,
[ @path_changes , " the gemspecs for path gems changed " ] ,
[ @local_changes , " the gemspecs for git local gems changed " ] ,
] . select ( & :first ) . map ( & :last ) . join ( " , " )
end
2022-09-29 08:30:43 +03:00
def pretty_dep ( dep )
SharedHelpers . pretty_dependency ( dep )
2018-11-03 02:07:56 +03:00
end
# Check if the specs of the given source changed
# according to the locked source.
def specs_changed? ( source )
locked = @locked_sources . find { | s | s == source }
! locked || dependencies_for_source_changed? ( source , locked ) || specs_for_source_changed? ( source )
end
def dependencies_for_source_changed? ( source , locked_source = source )
deps_for_source = @dependencies . select { | s | s . source == source }
2021-09-29 21:07:08 +03:00
locked_deps_for_source = locked_dependencies . select { | dep | dep . source == locked_source }
2018-11-03 02:07:56 +03:00
2021-02-01 18:17:16 +03:00
deps_for_source . uniq . sort != locked_deps_for_source . sort
2018-11-03 02:07:56 +03:00
end
def specs_for_source_changed? ( source )
locked_index = Index . new
locked_index . use ( @locked_specs . select { | s | source . can_lock? ( s ) } )
# order here matters, since Index#== is checking source.specs.include?(locked_index)
locked_index != source . specs
rescue PathError , GitError = > e
Bundler . ui . debug " Assuming that #{ source } has not changed since fetching its specs errored ( #{ e } ) "
false
end
# Get all locals and override their matching sources.
# Return true if any of the locals changed (for example,
# they point to a new revision) or depend on new specs.
def converge_locals
locals = [ ]
Bundler . settings . local_overrides . map do | k , v |
spec = @dependencies . find { | s | s . name == k }
source = spec && spec . source
if source && source . respond_to? ( :local_override! )
source . unlock! if @unlock [ :gems ] . include? ( spec . name )
locals << [ source , source . local_override! ( v ) ]
end
end
sources_with_changes = locals . select do | source , changed |
changed || specs_changed? ( source )
end . map ( & :first )
! sources_with_changes . each { | source | @unlock [ :sources ] << source . name } . empty?
end
def converge_paths
sources . path_sources . any? do | source |
specs_changed? ( source )
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
# 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
# source in the Gemfile.lock, use the one from the Gemfile.
2021-07-07 08:07:29 +03:00
changes = sources . replace_sources! ( @locked_sources )
2018-11-03 02:07:56 +03:00
sources . all_sources . each do | source |
# If the source is unlockable and the current command allows an unlock of
# the source (for example, you are doing a `bundle update <foo>` of a git-pinned
# gem), unlock it. For git sources, this means to unlock the revision, which
# will cause the `ref` used to be the most recent for the branch (or master) if
# an explicit `ref` is not used.
if source . respond_to? ( :unlock! ) && @unlock [ :sources ] . include? ( source . name )
source . unlock!
changes = true
end
end
changes
end
def converge_dependencies
2021-11-17 17:26:06 +03:00
changes = false
2021-11-17 16:09:50 +03:00
@dependencies . each do | dep |
2021-11-16 14:19:13 +03:00
if dep . source
2018-11-03 02:07:56 +03:00
dep . source = sources . get ( dep . source )
end
2021-11-16 14:19:13 +03:00
unless locked_dep = @locked_deps [ dep . name ]
2018-11-03 02:07:56 +03:00
changes = true
next
end
# Gem::Dependency#== matches Gem::Dependency#type. As the lockfile
# doesn't carry a notion of the dependency type, if you use
# add_development_dependency in a gemspec that's loaded with the gemspec
# directive, the lockfile dependencies and resolved dependencies end up
# with a mismatch on #type. Work around that by setting the type on the
# dep from the lockfile.
2021-11-16 14:19:13 +03:00
locked_dep . instance_variable_set ( :@type , dep . type )
2018-11-03 02:07:56 +03:00
# We already know the name matches from the hash lookup
# so we only need to check the requirement now
2021-11-16 14:19:13 +03:00
changes || = dep . requirement != locked_dep . requirement
2018-11-03 02:07:56 +03:00
end
changes
end
# Remove elements from the locked specs that are expired. This will most
# commonly happen if the Gemfile has changed since the lockfile was last
# generated
def converge_locked_specs
2022-08-19 00:28:26 +03:00
converged = converge_specs ( @locked_specs )
resolve = SpecSet . new ( converged . reject { | s | @unlock [ :gems ] . include? ( s . name ) } )
2021-11-17 23:13:40 +03:00
diff = nil
# Now, we unlock any sources that do not have anymore gems pinned to it
sources . all_sources . each do | source |
next unless source . respond_to? ( :unlock! )
unless resolve . any? { | s | s . source == source }
diff || = @locked_specs . to_a - resolve . to_a
source . unlock! if diff . any? { | s | s . source == source }
end
end
resolve
end
def converge_specs ( specs )
2018-11-03 02:07:56 +03:00
converged = [ ]
2021-11-16 17:01:18 +03:00
2022-04-28 11:28:31 +03:00
deps = @dependencies . select do | dep |
specs [ dep ] . any? { | s | s . satisfies? ( dep ) && ( ! dep . source || s . source . include? ( dep . source ) ) }
2021-12-27 03:41:55 +03:00
end
2022-08-03 20:03:50 +03:00
@specs_that_changed_sources = [ ]
2021-12-27 03:41:55 +03:00
specs . each do | s |
dep = @dependencies . find { | d | s . satisfies? ( d ) }
2021-12-22 01:52:59 +03:00
2022-01-18 01:01:01 +03:00
# Replace the locked dependency's source with the equivalent source from the Gemfile
2022-08-03 20:03:50 +03:00
s . source = if dep && dep . source
gemfile_source = dep . source
lockfile_source = s . source
@specs_that_changed_sources << s if gemfile_source != lockfile_source
gemfile_source
else
sources . get_with_fallback ( s . source )
end
2018-11-03 02:07:56 +03:00
next if @unlock [ :sources ] . include? ( s . source . name )
# Path sources have special logic
if s . source . instance_of? ( Source :: Path ) || s . source . instance_of? ( Source :: Gemspec )
2019-06-01 12:49:40 +03:00
new_specs = begin
2018-11-03 02:07:56 +03:00
s . source . specs
rescue PathError , GitError
# if we won't need the source (according to the lockfile),
# don't error if the path/git source isn't available
2021-11-17 23:13:40 +03:00
next if specs .
2022-07-26 07:43:48 +03:00
for ( requested_dependencies , false ) .
2018-11-03 02:07:56 +03:00
none? { | locked_spec | locked_spec . source == s . source }
raise
end
2019-06-01 12:49:40 +03:00
new_spec = new_specs [ s ] . first
2018-11-03 02:07:56 +03:00
# If the spec is no longer in the path source, unlock it. This
# commonly happens if the version changed in the gemspec
2019-06-01 12:49:40 +03:00
next unless new_spec
2018-11-03 02:07:56 +03:00
2019-06-01 12:49:40 +03:00
s . dependencies . replace ( new_spec . dependencies )
2018-11-03 02:07:56 +03:00
end
2021-11-25 00:01:45 +03:00
if dep . nil? && requested_dependencies . find { | d | s . name == d . name }
2021-11-16 14:19:13 +03:00
@unlock [ :gems ] << s . name
else
converged << s
end
2018-11-03 02:07:56 +03:00
end
2022-08-19 00:28:26 +03:00
filter_specs ( converged , deps )
2018-11-03 02:07:56 +03:00
end
def metadata_dependencies
2022-06-23 12:22:36 +03:00
@metadata_dependencies || = [
2022-07-29 08:59:56 +03:00
Dependency . new ( " Ruby \0 " , Gem . ruby_version ) ,
2022-06-23 12:22:36 +03:00
Dependency . new ( " RubyGems \0 " , Gem :: VERSION ) ,
]
2018-11-03 02:07:56 +03:00
end
def source_requirements
# Record the specs available in each gem's source, so that those
# specs will be available later when the resolver knows where to
# look for that gemspec (or its dependencies)
2021-05-28 13:47:49 +03:00
source_requirements = if precompute_source_requirements_for_indirect_dependencies?
2022-07-29 08:59:56 +03:00
all_requirements = source_map . all_requirements
all_requirements = pin_locally_available_names ( all_requirements ) if @prefer_local
{ :default = > sources . default_source } . merge ( all_requirements )
2021-05-28 13:47:49 +03:00
else
{ :default = > Source :: RubygemsAggregate . new ( sources , source_map ) } . merge ( source_map . direct_requirements )
end
2022-02-09 19:11:52 +03:00
source_requirements . merge! ( source_map . locked_requirements ) unless @remote
2018-11-03 02:07:56 +03:00
metadata_dependencies . each do | dep |
source_requirements [ dep . name ] = sources . metadata_source
end
2021-05-28 13:47:49 +03:00
source_requirements [ :default_bundler ] = source_requirements [ " bundler " ] || sources . default_source
2018-11-03 02:07:56 +03:00
source_requirements [ " bundler " ] = sources . metadata_source # needs to come last to override
2022-08-03 20:03:50 +03:00
verify_changed_sources!
2018-11-03 02:07:56 +03:00
source_requirements
end
2022-08-03 20:03:50 +03:00
def verify_changed_sources!
@specs_that_changed_sources . each do | s |
if s . source . specs . search ( s . name ) . empty?
raise GemNotFound , " Could not find gem ' #{ s . name } ' in #{ s . source } "
end
end
end
2018-11-03 02:07:56 +03:00
def requested_groups
2022-07-29 08:59:56 +03:00
values = groups - Bundler . settings [ :without ] - @optional_groups + Bundler . settings [ :with ]
values & = Bundler . settings [ :only ] unless Bundler . settings [ :only ] . empty?
values
2018-11-03 02:07:56 +03:00
end
def lockfiles_equal? ( current , proposed , preserve_unknown_sections )
if preserve_unknown_sections
sections_to_ignore = LockfileParser . sections_to_ignore ( @locked_bundler_version )
sections_to_ignore += LockfileParser . unknown_sections_in_lockfile ( current )
sections_to_ignore += LockfileParser :: ENVIRONMENT_VERSION_SECTIONS
pattern = / #{ Regexp . union ( sections_to_ignore ) } \ n( \ s{2,}.* \ n)+ /
whitespace_cleanup = / \ n{2,} /
current = current . gsub ( pattern , " \n " ) . gsub ( whitespace_cleanup , " \n \n " ) . strip
proposed = proposed . gsub ( pattern , " \n " ) . gsub ( whitespace_cleanup , " \n \n " ) . strip
end
current == proposed
end
def compute_requires
dependencies . reduce ( { } ) do | requires , dep |
next requires unless dep . should_include?
requires [ dep . name ] = Array ( dep . autorequire || dep . name ) . map do | file |
# Allow `require: true` as an alias for `require: <name>`
file == true ? dep . name : file
end
requires
end
end
2022-09-05 03:15:30 +03:00
def additional_base_requirements_for_resolve ( last_resolve )
2021-07-07 08:07:29 +03:00
return [ ] unless @locked_gems && unlocking? && ! sources . expired_sources? ( @locked_gems . sources )
2022-09-05 03:15:30 +03:00
converge_specs ( @originally_locked_specs - last_resolve ) . map do | locked_spec |
2022-08-22 05:52:51 +03:00
Dependency . new ( locked_spec . name , " >= #{ locked_spec . version } " )
end . uniq
2018-11-03 02:07:56 +03:00
end
2022-08-02 18:45:28 +03:00
def remove_ruby_from_platforms_if_necessary! ( dependencies )
return if Bundler . frozen_bundle? ||
2022-08-09 05:16:07 +03:00
Bundler . local_platform == Gem :: Platform :: RUBY ||
! platforms . include? ( Gem :: Platform :: RUBY ) ||
( @new_platform && platforms . last == Gem :: Platform :: RUBY ) ||
2022-08-23 09:36:30 +03:00
! @originally_locked_specs . incomplete_ruby_specs? ( dependencies )
2022-08-02 18:45:28 +03:00
remove_platform ( Gem :: Platform :: RUBY )
add_current_platform
end
2021-05-28 13:47:49 +03:00
def source_map
2022-02-09 19:11:52 +03:00
@source_map || = SourceMap . new ( sources , dependencies , @locked_specs )
2021-02-01 18:17:16 +03:00
end
2018-11-03 02:07:56 +03:00
end
end