Skip to content

Commit

Permalink
fix specs for bundler 3
Browse files Browse the repository at this point in the history
importantly, bundler 3 automatically caches, cleans, and prunes
gems by default, so need to always keep track of plugins, even for
the regular gemfile run, and include plugins in the list of known
gems when cleaning up
  • Loading branch information
ccutrer committed May 24, 2024
1 parent 49ce798 commit 9f5f8a9
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 127 deletions.
52 changes: 29 additions & 23 deletions bundler/lib/bundler/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class << self
attr_reader(
:dependencies,
:locked_deps,
:plugins,
:plugin_dependencies,
:locked_gems,
:platforms,
:ruby_version,
Expand Down Expand Up @@ -58,7 +58,8 @@ def self.build(gemfile, lockfile, unlock)
# @param ruby_version [Bundler::RubyVersion, nil] Requested Ruby Version
# @param optional_groups [Array(String)] A list of optional groups
# @param lockfile_contents [String, nil] The contents of the lockfile
# @param plugins [Array(Bundler::Dependency)] array of plugin dependencies from Gemfile
# @param plugin_dependencies [Array(Bundler::Dependency)] array of plugin dependencies from Gemfile
# @param plugin_sources [Bundler::SourceList]
def initialize(lockfile,
dependencies,
sources,
Expand All @@ -67,7 +68,8 @@ def initialize(lockfile,
optional_groups = [],
gemfiles = [],
lockfile_contents = nil,
plugins = [])
plugin_dependencies = [],
plugin_sources = SourceList.new)
if [true, false].include?(unlock)
@unlocking_bundler = false
@unlocking = unlock
Expand All @@ -76,15 +78,16 @@ def initialize(lockfile,
@unlocking = unlock.any? {|_k, v| !Array(v).empty? }
end

@dependencies = dependencies
@plugins = plugins
@sources = sources
@unlock = unlock
@optional_groups = optional_groups
@prefer_local = false
@specs = nil
@ruby_version = ruby_version
@gemfiles = gemfiles
@dependencies = dependencies
@sources = sources
@plugin_dependencies = plugin_dependencies
@plugin_sources = plugin_sources
@unlock = unlock
@optional_groups = optional_groups
@prefer_local = false
@specs = nil
@ruby_version = ruby_version
@gemfiles = gemfiles

@lockfile = lockfile

Expand Down Expand Up @@ -240,16 +243,16 @@ def requested_dependencies
dependencies_for(requested_groups)
end

def requested_plugins
plugins_for(requested_groups)
def requested_plugin_dependencies
plugin_dependencies_for(requested_groups)
end

def current_dependencies
filter_relevant(dependencies)
end

def current_plugins
filter_relevant(plugins)
def current_plugin_dependencies
filter_relevant(plugin_dependencies)
end

def current_locked_dependencies
Expand Down Expand Up @@ -294,9 +297,9 @@ def dependencies_for(groups)
deps
end

def plugins_for(groups)
def plugin_dependencies_for(groups)
groups.map!(&:to_sym)
plugins = current_plugins # always returns a new array
plugins = current_plugin_dependencies # always returns a new array
plugins.select! do |d|
if RUBY_VERSION >= "3.1"
d.groups.intersect?(groups)
Expand Down Expand Up @@ -340,11 +343,14 @@ def resolve
end

def spec_git_paths
sources.git_sources.map {|s| File.realpath(s.path) if File.exist?(s.path) }.compact
# plugin sources from the main Gemfile run we never instructed that they are cached,
# but they were, by the plugin run of the gemfile
plugin_sources.git_sources.each(&:cached!)
(sources.git_sources + plugin_sources.git_sources).uniq.filter_map {|s| File.realpath(s.path) if File.exist?(s.path) }
end

def groups
(dependencies + plugins).map(&:groups).flatten.uniq
(dependencies + plugin_dependencies).map(&:groups).flatten.uniq
end

def lock(file_or_preserve_unknown_sections = false, preserve_unknown_sections_or_unused = false)
Expand Down Expand Up @@ -488,7 +494,7 @@ def validate_platforms!

def validate_plugins!
missing_plugins_list = []
requested_plugins.each do |plugin|
requested_plugin_dependencies.each do |plugin|
missing_plugins_list << plugin unless Plugin.installed?(plugin.name)
end
if missing_plugins_list.size > 1
Expand Down Expand Up @@ -516,8 +522,8 @@ def most_specific_locked_platform
end
end

attr_reader :sources
private :sources
attr_reader :sources, :plugin_sources
private :sources, :plugin_sources

def nothing_changed?
return false unless lockfile_exists?
Expand Down
107 changes: 47 additions & 60 deletions bundler/lib/bundler/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ def self.evaluate(gemfile, lockfile, unlock)
def initialize
@source = nil
@sources = SourceList.new
@plugin_sources = SourceList.new
@current_sources = @sources
@git_sources = {}
@dependencies = []
@plugins = []
@plugin_dependencies = []
@current_dependencies = @dependencies
@groups = []
@install_conditionals = []
@optional_groups = []
Expand Down Expand Up @@ -98,12 +101,12 @@ def gem(name, *args)
options["gemfile"] = @gemfile
version = args || [">= 0"]

normalize_options(name, version, true, options)
normalize_options(name, version, options)

dep = Dependency.new(name, version, options)

# if there's already a dependency with this name we try to prefer one
if current = @dependencies.find {|d| d.name == dep.name }
if current = @current_dependencies.find {|d| d.name == dep.name }
if current.requirement != dep.requirement
current_requirement_open = current.requirements_list.include?(">= 0")

Expand Down Expand Up @@ -136,7 +139,7 @@ def gem(name, *args)

# Always prefer the dependency from the Gemfile
if current.gemspec_dev_dep?
@dependencies.delete(current)
@current_dependencies.delete(current)
elsif dep.gemspec_dev_dep?
return
elsif current.source != dep.source
Expand All @@ -151,7 +154,7 @@ def gem(name, *args)
end
end

@dependencies << dep
@current_dependencies << dep
end

def source(source, *args, &blk)
Expand All @@ -161,20 +164,22 @@ def source(source, *args, &blk)

if options.key?("type")
options["type"] = options["type"].to_s
unless Plugin.source?(options["type"])
unless (source_plugin = Plugin.source_plugin(options["type"]))
raise InvalidOption, "No plugin sources available for #{options["type"]}"
end

unless block_given?
raise InvalidOption, "You need to pass a block to #source with :type option"
end

plugin(source_plugin) unless @plugin_dependencies.any? {|d| d.name == source_plugin }

source_opts = options.merge("uri" => source)
with_source(@sources.add_plugin_source(options["type"], source_opts), &blk)
with_source(add_source(:add_plugin_source, options["type"], source_opts), &blk)
elsif block_given?
with_source(@sources.add_rubygems_source("remotes" => source), &blk)
with_source(add_source(:add_rubygems_source, "remotes" => source), &blk)
else
@sources.add_global_rubygems_remote(source)
add_source(:add_global_rubygems_remote, source)
end
end

Expand All @@ -200,8 +205,7 @@ def path(path, options = {}, &blk)

source_options["global"] = true unless block_given?

source = @sources.add_path_source(source_options)
with_source(source, &blk)
with_source(add_source(:add_path_source, source_options), &blk)
end

def git(uri, options = {}, &blk)
Expand All @@ -216,20 +220,29 @@ def git(uri, options = {}, &blk)
raise DeprecatedError, msg
end

with_source(@sources.add_git_source(normalize_hash(options).merge("uri" => uri)), &blk)
options = normalize_hash(options).merge("uri" => uri)
with_source(add_source(:add_git_source, options), &blk)
end

def github(repo, options = {})
def github(repo, options = {}, &blk)
raise ArgumentError, "GitHub sources require a block" unless block_given?
github_uri = @git_sources["github"].call(repo)
git_options = normalize_hash(options).merge("uri" => github_uri)
git_source = @sources.add_git_source(git_options)
with_source(git_source) { yield }
github_uri = @git_sources["github"].call(repo)
options = normalize_hash(options).merge("uri" => github_uri)
with_source(add_source(:add_git_source, options), &blk)
end

def to_definition(lockfile, unlock, lockfile_contents: nil)
check_primary_source_safety
Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles, lockfile_contents, @plugins)
Definition.new(lockfile,
@dependencies,
@sources,
unlock,
@ruby_version,
@optional_groups,
@gemfiles,
lockfile_contents,
@plugin_dependencies,
@plugin_sources)
end

def group(*args, &blk)
Expand Down Expand Up @@ -272,41 +285,12 @@ def env(name)
end

def plugin(name, *args)
options = args.last.is_a?(Hash) ? args.pop.dup : {}
options["gemfile"] = @gemfile
version = args || [">= 0"]

# We don't care to add sources for plugins in this pass over the gemfile
# since we're not installing plugins here (they should already be
# installed), only keeping track of them so that we can verify they
# are currently installed. This is important because otherwise sources
# unique to the plugin (like a git source) would end up in the lockfile,
# which we don't want.
normalize_options(name, version, false, options)

dep = Dependency.new(name, version, options)

# if there's already a plugin with this name we try to prefer one
if current = @plugins.find {|d| d.name == dep.name }
if current.requirement != dep.requirement
raise GemfileError, "You cannot specify the same plugin twice with different version requirements.\n" \
"You specified: #{current.name} (#{current.requirement}) and #{dep.name} (#{dep.requirement})" \
"#{update_prompt}"
end

if current.source != dep.source
raise GemfileError, "You cannot specify the same plugin twice coming from different sources.\n" \
"You specified that #{dep.name} (#{dep.requirement}) should come from " \
"#{current.source || "an unspecified source"} and #{dep.source}\n"
else
Bundler.ui.warn "Your Gemfile lists the plugin #{current.name} (#{current.requirement}) more than once.\n" \
"You should keep only one of them.\n" \
"Remove any duplicate entries and specify the plugin only once." \
"While it's not a problem now, it could cause errors if you change the version of one of them later."
end
end

@plugins << dep
@current_sources = @plugin_sources
@current_dependencies = @plugin_dependencies
gem(name, *args)
ensure
@current_sources = @sources
@current_dependencies = @dependencies
end

def method_missing(name, *args)
Expand All @@ -320,6 +304,11 @@ def check_primary_source_safety

private

def add_source(method, *args)
@plugin_sources.send(method, *args) unless @plugin_sources.equal?(@current_sources)
@current_sources.send(method, *args)
end

def add_git_sources
git_source(:github) do |repo_name|
if repo_name =~ GITHUB_PULL_REQUEST_URL
Expand Down Expand Up @@ -382,7 +371,7 @@ def valid_keys
@valid_keys ||= VALID_KEYS
end

def normalize_options(name, version, add_to_sources, opts)
def normalize_options(name, version, opts)
if name.is_a?(Symbol)
raise GemfileError, %(You need to specify gem names as Strings. Use 'gem "#{name}"' instead)
end
Expand Down Expand Up @@ -417,9 +406,9 @@ def normalize_options(name, version, add_to_sources, opts)
end

# Save sources passed in a key
if opts.key?("source") && add_to_sources
if opts.key?("source")
source = normalize_source(opts["source"])
opts["source"] = @sources.add_rubygems_source("remotes" => source)
opts["source"] = @current_sources.add_rubygems_source("remotes" => source)
end

git_name = (git_names & opts.keys).last
Expand All @@ -438,10 +427,8 @@ def normalize_options(name, version, add_to_sources, opts)
else
options = opts.dup
end
if add_to_sources
source = send(type, param, options) {}
opts["source"] = source
end
source = send(type, param, options) {}
opts["source"] = source
end

opts["source"] ||= @source
Expand Down

0 comments on commit 9f5f8a9

Please sign in to comment.