class Bundler::Definition
Attributes
Do not create or modify a lockfile (Makes lock
a noop)
Public Class Methods
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]
# File bundler/definition.rb, line 31 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
# File bundler/definition.rb, line 58 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 @prefer_local = false @specs = nil @ruby_version = ruby_version @gemfiles = gemfiles @lockfile = lockfile @lockfile_contents = String.new @locked_bundler_version = nil @locked_ruby_version = nil @new_platform = nil @removed_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 @originally_locked_specs = SpecSet.new(@locked_gems.specs) if unlock != true @locked_deps = @locked_gems.dependencies @locked_specs = @originally_locked_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([]) @originally_locked_specs = @locked_specs @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 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 = (@unlock[:gems] || []).map {|name| Dependency.new(name, ">= 0") } @unlock[:gems] = @locked_specs.for(eager_unlock, false, platforms).map(&:name).uniq end @dependency_changes = converge_dependencies @local_changes = converge_locals @incomplete_lockfile = check_missing_lockfile_specs end
Public Instance Methods
# File bundler/definition.rb, line 454 def add_platform(platform) @new_platform ||= !@platforms.include?(platform) @platforms |= [platform] end
# File bundler/definition.rb, line 235 def current_dependencies dependencies.select do |d| d.should_include? && !d.gem_platforms([generic_local_platform]).empty? end end
# File bundler/definition.rb, line 249 def deleted_deps @deleted_deps ||= locked_dependencies - @dependencies end
# File bundler/definition.rb, line 259 def dependencies_for(groups) groups.map!(&:to_sym) current_dependencies.reject do |d| (d.groups & groups).empty? end end
# File bundler/definition.rb, line 357 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}`." if 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}" } added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any? deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } if deleted_deps.any? both_sources = Hash.new {|h, k| h[k] = [] } @dependencies.each {|d| both_sources[d.name][0] = d } 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 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) gemfile_source_name = dep.source ? gemfile_source.identifier : "no specified source" lockfile_source_name = lock_dep.source ? lock_source.identifier : "no specified source" changed << "* #{name} from `#{lockfile_source_name}` to `#{gemfile_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
# File bundler/definition.rb, line 152 def gem_version_promoter @gem_version_promoter ||= GemVersionPromoter.new end
# File bundler/definition.rb, line 297 def groups dependencies.map(&:groups).flatten.uniq end
# File bundler/definition.rb, line 301 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 = Bundler.gem_version.segments.first updating_major = locked_major < current_major 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
# File bundler/definition.rb, line 241 def locked_dependencies @locked_deps.values end
# File bundler/definition.rb, line 331 def locked_ruby_version return unless ruby_version if @unlock[:ruby] || !@locked_ruby_version Bundler::RubyVersion.system else @locked_ruby_version end end
# File bundler/definition.rb, line 340 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
# File bundler/definition.rb, line 208 def missing_specs resolve.materialize(requested_dependencies).missing_specs end
# File bundler/definition.rb, line 212 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 @resolver = nil @specs = nil @gem_version_promoter = nil Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})" true end
# File bundler/definition.rb, line 466 def most_specific_locked_platform @platforms.min_by do |bundle_platform| platform_specificity_match(bundle_platform, local_platform) end end
# File bundler/definition.rb, line 245 def new_deps @new_deps ||= @dependencies - locked_dependencies end
# File bundler/definition.rb, line 200 def new_specs specs - @locked_specs end
# File bundler/definition.rb, line 475 def nothing_changed? !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@incomplete_lockfile end
# File bundler/definition.rb, line 459 def remove_platform(platform) removed_platform = @platforms.delete(Gem::Platform.new(platform)) @removed_platform ||= removed_platform return if removed_platform raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}" end
# File bundler/definition.rb, line 204 def removed_specs @locked_specs - specs end
# File bundler/definition.rb, line 231 def requested_dependencies dependencies_for(requested_groups) end
# File bundler/definition.rb, line 227 def requested_specs specs_for(requested_groups) end
# File bundler/definition.rb, line 173 def resolution_mode=(options) if options["local"] @remote = false else @remote = true @prefer_local = options["prefer-local"] 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
# File bundler/definition.rb, line 271 def resolve @resolve ||= if Bundler.frozen_bundle? Bundler.ui.debug "Frozen, using resolution from the lockfile" @locked_specs elsif !unlocking? && nothing_changed? 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 @removed_platform || @locked_gems.may_include_redundant_platform_specific_gems? SpecSet.new(filter_specs(@locked_specs, @dependencies)) else @locked_specs end end else Bundler.ui.debug "Found changes from the lockfile, re-resolving dependencies because #{change_reason}" start_resolution end end
# File bundler/definition.rb, line 156 def resolve_only_locally! @remote = false sources.local_only! resolve end
# File bundler/definition.rb, line 167 def resolve_remotely! @remote = true sources.remote! resolve end
# File bundler/definition.rb, line 162 def resolve_with_cache! sources.cached! resolve end
# File bundler/definition.rb, line 182 def setup_sources_for_resolve if @remote == false sources.cached! else sources.remote! end end
# File bundler/definition.rb, line 293 def spec_git_paths sources.git_sources.map {|s| File.realpath(s.path) if File.exist?(s.path) }.compact 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]
# File bundler/definition.rb, line 196 def specs @specs ||= materialize(requested_dependencies) end
# File bundler/definition.rb, line 253 def specs_for(groups) return specs if groups.empty? deps = dependencies_for(groups) materialize(deps) end
# File bundler/definition.rb, line 352 def to_lock require_relative "lockfile_generator" LockfileGenerator.generate(self) end
# File bundler/definition.rb, line 479 def unlocking? @unlocking end
# File bundler/definition.rb, line 446 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\n`bundle lock --add-platform #{Bundler.local_platform}` and try again." end
# File bundler/definition.rb, line 421 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
# File bundler/definition.rb, line 416 def validate_runtime! validate_ruby! validate_platforms! end