1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/lib/bundler/definition.rb
David Rodríguez 1b9b41472f [rubygems/rubygems] Completely avoid replacing sources when in multisource compatibility mode
Since this mode is only enabled in frozen mode, it's fine to use the
lockfile and means we don't have to "prepare" the replacement for
materialization.

https://github.com/rubygems/rubygems/commit/dda01b288e
2021-08-31 19:06:14 +09:00

901 lines
32 KiB
Ruby

# frozen_string_literal: true
require_relative "lockfile_parser"
module Bundler
class Definition
include GemHelpers
class << self
# Do not create or modify a lockfile (Makes #lock a noop)
attr_accessor :no_lock
end
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)
@unlocking = unlock.any? {|_k, v| !Array(v).empty? }
end
@dependencies = dependencies
@sources = sources
@unlock = unlock
@optional_groups = optional_groups
@remote = false
@specs = nil
@ruby_version = ruby_version
@gemfiles = gemfiles
@lockfile = lockfile
@lockfile_contents = String.new
@locked_bundler_version = nil
@locked_ruby_version = nil
@locked_specs_incomplete_for_platform = false
@new_platform = nil
if lockfile && File.exist?(lockfile)
@lockfile_contents = Bundler.read_file(lockfile)
@locked_gems = LockfileParser.new(@lockfile_contents)
@locked_platforms = @locked_gems.platforms
@platforms = @locked_platforms.dup
@locked_bundler_version = @locked_gems.bundler_version
@locked_ruby_version = @locked_gems.ruby_version
if unlock != true
@locked_deps = @locked_gems.dependencies
@locked_specs = SpecSet.new(@locked_gems.specs)
@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([])
@locked_sources = []
@locked_platforms = []
end
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?
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."
Bundler::SharedHelpers.major_deprecation 2, msg
end
@sources.merged_gem_lockfile_sections!(locked_gem_sources.first)
end
@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)
add_current_platform unless current_ruby_platform_locked? || Bundler.frozen_bundle?
converge_path_sources_to_gemspec_sources
@path_changes = converge_paths
@source_changes = converge_sources
if @unlock[:conservative]
@unlock[:gems] ||= @dependencies.map(&:name)
else
eager_unlock = expand_dependencies(@unlock[:gems] || [], true)
@unlock[:gems] = @locked_specs.for(eager_unlock, false, false).map(&:name)
end
@dependency_changes = converge_dependencies
@local_changes = converge_locals
@requires = compute_requires
end
def gem_version_promoter
@gem_version_promoter ||= begin
locked_specs =
if unlocking? && @locked_specs.empty? && !@lockfile_contents.empty?
# Definition uses an empty set of locked_specs to indicate all gems
# are unlocked, but GemVersionPromoter needs the locked_specs
# for conservative comparison.
Bundler::SpecSet.new(@locked_gems.specs)
else
@locked_specs
end
GemVersionPromoter.new(locked_specs, @unlock[:gems])
end
end
def multisource_allowed?
@multisource_allowed
end
def resolve_only_locally!
@remote = false
sources.local_only!
resolve
end
def resolve_with_cache!
sources.cached!
resolve
end
def resolve_remotely!
@remote = true
sources.remote!
resolve
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
@specs ||= materialize(requested_dependencies)
end
def new_specs
specs - @locked_specs
end
def removed_specs
@locked_specs - specs
end
def missing_specs
resolve.materialize(requested_dependencies).missing_specs
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
@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
specs_for(requested_groups)
end
def requested_dependencies
dependencies_for(requested_groups)
end
def current_dependencies
dependencies.select do |d|
d.should_include? && !d.gem_platforms(@platforms).empty?
end
end
def specs_for(groups)
groups = requested_groups if groups.empty?
deps = dependencies_for(groups)
materialize(expand_dependencies(deps))
end
def dependencies_for(groups)
groups.map!(&:to_sym)
current_dependencies.reject do |d|
(d.groups & groups).empty?
end
end
# 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
@resolve ||= begin
last_resolve = converge_locked_specs
if Bundler.frozen_bundle?
Bundler.ui.debug "Frozen, using resolution from the lockfile"
last_resolve
elsif !unlocking? && nothing_changed?
Bundler.ui.debug("Found no changes, using resolution from the lockfile")
last_resolve
else
# Run a resolve against the locally available gems
Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}")
expanded_dependencies = expand_dependencies(dependencies + metadata_dependencies, @remote)
Resolver.resolve(expanded_dependencies, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
end
end
end
def spec_git_paths
sources.git_sources.map {|s| File.realpath(s.path) if File.exist?(s.path) }.compact
end
def groups
dependencies.map(&:groups).flatten.uniq
end
def lock(file, preserve_unknown_sections = false)
return if Definition.no_lock
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")
if @locked_bundler_version
locked_major = @locked_bundler_version.segments.first
current_major = Gem::Version.create(Bundler::VERSION).segments.first
if updating_major = locked_major < current_major
Bundler.ui.warn "Warning: the lockfile is being updated to Bundler #{current_major}, " \
"after which you will be unable to return to Bundler #{@locked_bundler_version.segments.first}."
end
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_bundler_version
if @locked_bundler_version && @locked_bundler_version < Gem::Version.new(Bundler::VERSION)
new_version = Bundler::VERSION
end
new_version || @locked_bundler_version || Bundler::VERSION
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
require_relative "lockfile_generator"
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
suggested_command = if Bundler.settings.locations("frozen").keys.&([:global, :local]).any?
"bundle config unset frozen"
elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any?
"bundle config unset deployment"
end
msg << "\n\nIf this is a development machine, remove the #{Bundler.default_gemfile} " \
"freeze \nby running `#{suggested_command}`."
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}" }
gemfile_sources = sources.lock_sources
new_sources = gemfile_sources - @locked_sources
deleted_sources = @locked_sources - gemfile_sources
new_deps = @dependencies - @locked_deps.values
deleted_deps = @locked_deps.values - @dependencies
# Check if it is possible that the source is only changed thing
if (new_deps.empty? && deleted_deps.empty?) && (!new_sources.empty? && !deleted_sources.empty?)
new_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
deleted_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
end
if @locked_sources != gemfile_sources
if new_sources.any?
added.concat new_sources.map {|source| "* source: #{source}" }
end
if deleted_sources.any?
deleted.concat deleted_sources.map {|source| "* source: #{source}" }
end
end
added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any?
if deleted_deps.any?
deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" }
end
both_sources = Hash.new {|h, k| h[k] = [] }
@dependencies.each {|d| both_sources[d.name][0] = d }
@locked_deps.each {|name, d| both_sources[name][1] = d.source }
both_sources.each do |name, (dep, lock_source)|
next if lock_source.nil? || (dep && lock_source.can_lock?(dep))
gemfile_source_name = (dep && dep.source) || "no specified source"
lockfile_source_name = lock_source
changed << "* #{name} from `#{gemfile_source_name}` to `#{lockfile_source_name}`"
end
reason = change_reason
msg << "\n\n#{reason.split(", ").map(&:capitalize).join("\n")}" unless reason.strip.empty?
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 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!
return if current_platform_locked?
raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \
"but your local platform is #{Bundler.local_platform}. " \
"Add the current platform to the lockfile with `bundle lock --add-platform #{Bundler.local_platform}` and try again."
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
def most_specific_locked_platform
@platforms.min_by do |bundle_platform|
platform_specificity_match(bundle_platform, local_platform)
end
end
attr_reader :sources
private :sources
def nothing_changed?
!@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@locked_specs_incomplete_for_platform
end
def unlocking?
@unlocking
end
private
def materialize(dependencies)
specs = resolve.materialize(dependencies)
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
raise GemNotFound, "Could not find #{missing_specs.map(&:full_name).join(", ")} in any of the sources"
end
unless specs["bundler"].any?
bundler = sources.metadata_source.specs.search(Gem::Dependency.new("bundler", VERSION)).last
specs["bundler"] = bundler
end
specs
end
def precompute_source_requirements_for_indirect_dependencies?
sources.non_global_rubygems_sources.all?(&:dependency_api_available?) && !sources.aggregate_global_source?
end
def current_ruby_platform_locked?
return false unless generic_local_platform == Gem::Platform::RUBY
current_platform_locked?
end
def current_platform_locked?
@platforms.any? do |bundle_platform|
MatchPlatform.platforms_match?(bundle_platform, Bundler.local_platform)
end
end
def add_current_platform
add_platform(local_platform)
end
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"],
[@locked_specs_incomplete_for_platform, "the lockfile does not have all gems needed for the current platform"],
].select(&:first).map(&:last).join(", ")
end
def pretty_dep(dep, source = false)
SharedHelpers.pretty_dependency(dep, source)
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 }
locked_deps_for_source = @locked_deps.values.select {|dep| dep.source == locked_source }
deps_for_source.uniq.sort != locked_deps_for_source.sort
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.
changes = sources.replace_sources!(@locked_sources)
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
frozen = Bundler.frozen_bundle?
(@dependencies + @locked_deps.values).each do |dep|
locked_source = @locked_deps[dep.name]
# This is to make sure that if bundler is installing in deployment mode and
# after locked_source and sources don't match, we still use locked_source.
if frozen && !locked_source.nil? &&
locked_source.respond_to?(:source) && locked_source.source.instance_of?(Source::Path) && locked_source.source.path.exist?
dep.source = locked_source.source
elsif dep.source
dep.source = sources.get(dep.source)
end
end
changes = false
# We want to know if all match, but don't want to check all entries
# This means we need to return false if any dependency doesn't match
# the lock or doesn't exist in the lock.
@dependencies.each do |dependency|
unless locked_dep = @locked_deps[dependency.name]
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.
locked_dep.instance_variable_set(:@type, dependency.type)
# We already know the name matches from the hash lookup
# so we only need to check the requirement now
changes ||= dependency.requirement != locked_dep.requirement
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
deps = []
# Build a list of dependencies that are the same in the Gemfile
# and Gemfile.lock. If the Gemfile modified a dependency, but
# the gem in the Gemfile.lock still satisfies it, this is fine
# too.
@dependencies.each do |dep|
locked_dep = @locked_deps[dep.name]
# If the locked_dep doesn't match the dependency we're looking for then we ignore the locked_dep
locked_dep = nil unless locked_dep == dep
if in_locked_deps?(dep, locked_dep) || satisfies_locked_spec?(dep)
deps << dep
elsif dep.source.is_a?(Source::Path) && dep.current_platform? && (!locked_dep || dep.source != locked_dep.source)
@locked_specs.each do |s|
@unlock[:gems] << s.name if s.source == dep.source
end
dep.source.unlock! if dep.source.respond_to?(:unlock!)
dep.source.specs.each {|s| @unlock[:gems] << s.name }
end
end
converged = []
@locked_specs.each do |s|
# Replace the locked dependency's source with the equivalent source from the Gemfile
dep = @dependencies.find {|d| s.satisfies?(d) }
s.source = (dep && dep.source) || sources.get(s.source) unless multisource_allowed?
# Don't add a spec to the list if its source is expired. For example,
# if you change a Git gem to RubyGems.
next if s.source.nil?
next if @unlock[:sources].include?(s.source.name)
# If the spec is from a path source and it doesn't exist anymore
# then we unlock it.
# Path sources have special logic
if s.source.instance_of?(Source::Path) || s.source.instance_of?(Source::Gemspec)
new_specs = begin
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
next if @locked_specs.
for(requested_dependencies, false, true).
none? {|locked_spec| locked_spec.source == s.source }
raise
end
new_spec = new_specs[s].first
# If the spec is no longer in the path source, unlock it. This
# commonly happens if the version changed in the gemspec
next unless new_spec
s.dependencies.replace(new_spec.dependencies)
end
converged << s
end
resolve = SpecSet.new(converged)
@locked_specs_incomplete_for_platform = !resolve.for(expand_dependencies(requested_dependencies & deps), true, true)
resolve = SpecSet.new(resolve.for(expand_dependencies(deps, true), false, false).reject{|s| @unlock[:gems].include?(s.name) })
diff = nil
# 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 in_locked_deps?(dep, locked_dep)
# Because the lockfile can't link a dep to a specific remote, we need to
# treat sources as equivalent anytime the locked dep has all the remotes
# that the Gemfile dep does.
locked_dep && locked_dep.source && dep.source && locked_dep.source.include?(dep.source)
end
def satisfies_locked_spec?(dep)
@locked_specs[dep].any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) }
end
def metadata_dependencies
@metadata_dependencies ||= begin
ruby_versions = ruby_version_requirements(@ruby_version)
[
Dependency.new("Ruby\0", ruby_versions),
Dependency.new("RubyGems\0", Gem::VERSION),
]
end
end
def ruby_version_requirements(ruby_version)
return [] unless ruby_version
if ruby_version.patchlevel
[ruby_version.to_gem_version_with_patchlevel]
else
ruby_version.versions.map do |version|
requirement = Gem::Requirement.new(version)
if requirement.exact?
"~> #{version}.0"
else
requirement
end
end
end
end
def expand_dependencies(dependencies, remote = false)
deps = []
dependencies.each do |dep|
dep = Dependency.new(dep, ">= 0") unless dep.respond_to?(:name)
next unless remote || dep.current_platform?
target_platforms = dep.gem_platforms(remote ? @platforms : [generic_local_platform])
deps += expand_dependency_with_platforms(dep, target_platforms)
end
deps
end
def expand_dependency_with_platforms(dep, platforms)
platforms.map do |p|
DepProxy.get_proxy(dep, p)
end
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)
source_requirements = if precompute_source_requirements_for_indirect_dependencies?
{ :default => sources.default_source }.merge(source_map.all_requirements)
else
{ :default => Source::RubygemsAggregate.new(sources, source_map) }.merge(source_map.direct_requirements)
end
metadata_dependencies.each do |dep|
source_requirements[dep.name] = sources.metadata_source
end
source_requirements[:default_bundler] = source_requirements["bundler"] || sources.default_source
source_requirements["bundler"] = sources.metadata_source # needs to come last to override
source_requirements
end
def requested_groups
groups - Bundler.settings[:without] - @optional_groups + Bundler.settings[:with]
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
def additional_base_requirements_for_resolve
return [] unless @locked_gems && unlocking? && !sources.expired_sources?(@locked_gems.sources)
dependencies_by_name = dependencies.inject({}) {|memo, dep| memo.update(dep.name => dep) }
@locked_gems.specs.reduce({}) do |requirements, locked_spec|
name = locked_spec.name
dependency = dependencies_by_name[name]
next requirements if @locked_gems.dependencies[name] != dependency
next requirements if dependency && dependency.source.is_a?(Source::Path)
dep = Gem::Dependency.new(name, ">= #{locked_spec.version}")
requirements[name] = DepProxy.get_proxy(dep, locked_spec.platform)
requirements
end.values
end
def equivalent_rubygems_remotes?(source)
return false unless source.is_a?(Source::Rubygems)
Bundler.settings[:allow_deployment_source_credential_changes] && source.equivalent_remotes?(sources.rubygems_remotes)
end
def source_map
@source_map ||= SourceMap.new(sources, dependencies)
end
end
end