Skip to content

Commit c9aa3a2

Browse files
ccutrerdfop02
andcommitted
Fix plugin installation from gemfile
Several things are fixed: * Don't re-install a plugin referenced from the gemfile with every call to `bundle install` * If the version of a plugin referenced in the gemfile conflicts with what's in the plugin index, _do_ re-install it * If plugins aren't installed yet, don't throw cryptic errors from commands that don't implicitly install gems, such as `bundle check` and `bundle info`. This also applies if the plugin index references a system gem, and that gem is removed. This is all accomplished by actuallying including plugins as regular dependencies in the Gemfile, so that they end up in the lockfile, and then just using the regular lockfile with the plugins-only pass of the gemfile in the Plugin infrastructure. This also means that non-specific version constraints can be used for plugins, and you can update them with `bundle update <plugin>` just like any other gem. Co-authored-by: Diogo Fernandes <diogofernandesop@gmail.com>
1 parent a15e2cb commit c9aa3a2

22 files changed

+480
-97
lines changed

Manifest.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ bundler/lib/bundler/plugin.rb
161161
bundler/lib/bundler/plugin/api.rb
162162
bundler/lib/bundler/plugin/api/source.rb
163163
bundler/lib/bundler/plugin/dsl.rb
164+
bundler/lib/bundler/plugin/dummy_source.rb
164165
bundler/lib/bundler/plugin/events.rb
165166
bundler/lib/bundler/plugin/index.rb
166167
bundler/lib/bundler/plugin/installer.rb

bundler/lib/bundler/cli/install.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def run
5555
removed_message: "The --binstubs option have been removed in favor of `bundle binstubs --all`"
5656
end
5757

58-
Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
58+
Plugin.gemfile_install(Bundler.default_gemfile, Bundler.default_lockfile) if Bundler.feature_flag.plugins?
5959

6060
definition = Bundler.definition
6161
definition.validate_runtime!

bundler/lib/bundler/cli/update.rb

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ def run
1515

1616
Bundler.self_manager.update_bundler_and_restart_with_it_if_needed(update_bundler) if update_bundler
1717

18-
Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
19-
2018
sources = Array(options[:source])
2119
groups = Array(options[:group]).map(&:to_sym)
2220

@@ -33,29 +31,39 @@ def run
3331

3432
conservative = options[:conservative]
3533

36-
if full_update
34+
unlock = if full_update
3735
if conservative
38-
Bundler.definition(conservative: conservative)
36+
{ conservative: conservative }
3937
else
40-
Bundler.definition(true)
38+
true
4139
end
4240
else
4341
unless Bundler.default_lockfile.exist?
4442
raise GemfileLockNotFound, "This Bundle hasn't been installed yet. " \
4543
"Run `bundle install` to update and install the bundled gems."
4644
end
47-
Bundler::CLI::Common.ensure_all_gems_in_lockfile!(gems)
45+
explicit_gems = gems
4846

4947
if groups.any?
5048
deps = Bundler.definition.dependencies.select {|d| (d.groups & groups).any? }
5149
gems.concat(deps.map(&:name))
5250
end
5351

54-
Bundler.definition(gems: gems, sources: sources, ruby: options[:ruby],
55-
conservative: conservative,
56-
bundler: update_bundler)
52+
{
53+
gems: gems,
54+
sources: sources,
55+
ruby: options[:ruby],
56+
conservative: conservative,
57+
bundler: update_bundler,
58+
}
5759
end
5860

61+
Plugin.gemfile_install(Bundler.default_gemfile, Bundler.default_lockfile, unlock.dup) if Bundler.feature_flag.plugins?
62+
63+
Bundler::CLI::Common.ensure_all_gems_in_lockfile!(explicit_gems) if explicit_gems
64+
65+
Bundler.definition(unlock)
66+
5967
Bundler::CLI::Common.configure_gem_version_promoter(Bundler.definition, options)
6068

6169
Bundler::Fetcher.disable_endpoint = options["full-index"]

bundler/lib/bundler/definition.rb

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
105105
@locked_specs = SpecSet.new([])
106106
@locked_sources = []
107107
end
108+
109+
remove_plugin_dependencies_if_necessary
108110
else
109111
@unlock = {}
110112
@platforms = []
@@ -201,23 +203,20 @@ def removed_specs
201203
@locked_specs - specs
202204
end
203205

206+
def plugins
207+
@plugins ||= resolve.materialize(plugin_dependencies)
208+
end
209+
210+
def missing_plugins?
211+
missing_specs_from_set? { plugins.missing_specs }
212+
end
213+
204214
def missing_specs
205215
resolve.materialize(requested_dependencies).missing_specs
206216
end
207217

208218
def missing_specs?
209-
missing = missing_specs
210-
return false if missing.empty?
211-
Bundler.ui.debug "The definition is missing #{missing.map(&:full_name)}"
212-
true
213-
rescue BundlerError => e
214-
@resolve = nil
215-
@resolver = nil
216-
@resolution_packages = nil
217-
@specs = nil
218-
219-
Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})"
220-
true
219+
missing_specs_from_set? { missing_specs }
221220
end
222221

223222
def requested_specs
@@ -228,6 +227,10 @@ def requested_dependencies
228227
dependencies_for(requested_groups)
229228
end
230229

230+
def plugin_dependencies
231+
requested_dependencies.select {|dep| dep.type == :plugin }
232+
end
233+
231234
def current_dependencies
232235
filter_relevant(dependencies)
233236
end
@@ -417,6 +420,7 @@ def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
417420
def validate_runtime!
418421
validate_ruby!
419422
validate_platforms!
423+
validate_plugins!
420424
end
421425

422426
def validate_ruby!
@@ -452,6 +456,19 @@ def validate_platforms!
452456
"Add the current platform to the lockfile with\n`bundle lock --add-platform #{local_platform}` and try again."
453457
end
454458

459+
def validate_plugins!
460+
missing_plugins_list = []
461+
plugin_dependencies.each do |plugin|
462+
missing_plugins_list << plugin unless Plugin.installed?(plugin.name)
463+
end
464+
missing_plugins_list.map! {|p| "#{p.name} (#{p.requirement})" }
465+
if missing_plugins_list.size > 1
466+
raise GemNotFound, "Plugins #{missing_plugins_list.join(", ")} are not installed"
467+
elsif missing_plugins_list.any?
468+
raise GemNotFound, "Plugin #{missing_plugins_list.join(", ")} is not installed"
469+
end
470+
end
471+
455472
def add_platform(platform)
456473
@new_platform ||= !@platforms.include?(platform)
457474
@platforms |= [platform]
@@ -497,6 +514,21 @@ def unlocking?
497514

498515
private
499516

517+
def missing_specs_from_set?
518+
missing = yield
519+
return false if missing.empty?
520+
Bundler.ui.debug "The definition is missing #{missing.map(&:full_name)}"
521+
true
522+
rescue BundlerError => e
523+
@resolve = nil
524+
@resolver = nil
525+
@resolution_packages = nil
526+
@specs = nil
527+
528+
Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})"
529+
true
530+
end
531+
500532
def should_add_extra_platforms?
501533
!lockfile_exists? && generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform]
502534
end
@@ -1067,5 +1099,14 @@ def remove_invalid_platforms!(dependencies)
10671099
def source_map
10681100
@source_map ||= SourceMap.new(sources, dependencies, @locked_specs)
10691101
end
1102+
1103+
def remove_plugin_dependencies_if_necessary
1104+
return if Bundler.feature_flag.plugins_in_lockfile?
1105+
# we already have plugin dependencies in the lockfile; continue to do so regardless
1106+
# of the current setting
1107+
return if @dependencies.any? {|d| d.type == :plugin && @locked_deps.key?(d.name) }
1108+
1109+
@dependencies.reject! {|d| d.type == :plugin }
1110+
end
10701111
end
10711112
end

bundler/lib/bundler/dependency.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,15 @@ class Dependency < Gem::Dependency
2929

3030
def initialize(name, version, options = {}, &blk)
3131
type = options["type"] || :runtime
32-
super(name, version, type)
32+
if type == :plugin
33+
# RubyGems doesn't support plugin type, which only
34+
# makes sense in the context of Bundler, so bypass
35+
# the RubyGems validation
36+
super(name, version, :runtime)
37+
@type = type
38+
else
39+
super(name, version, type)
40+
end
3341

3442
@autorequire = nil
3543
@groups = Array(options["group"] || :default).map(&:to_sym)

bundler/lib/bundler/dsl.rb

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,15 @@ def source(source, *args, &blk)
160160

161161
if options.key?("type")
162162
options["type"] = options["type"].to_s
163-
unless Plugin.source?(options["type"])
163+
unless (source_plugin = Plugin.source_plugin(options["type"]))
164164
raise InvalidOption, "No plugin sources available for #{options["type"]}"
165165
end
166+
# Implicitly add a dependency on source plugins who are named bundler-source-<type>,
167+
# and aren't already mentioned in the Gemfile.
168+
# See also Plugin::DSL#source
169+
if source_plugin.start_with?("bundler-source-") && !@dependencies.any? {|d| d.name == source_plugin }
170+
plugin(source_plugin)
171+
end
166172

167173
unless block_given?
168174
raise InvalidOption, "You need to pass a block to #source with :type option"
@@ -271,7 +277,13 @@ def env(name)
271277
end
272278

273279
def plugin(*args)
274-
# Pass on
280+
options = args.last.is_a?(Hash) ? args.pop.dup : {}
281+
282+
normalize_hash(options)
283+
options["type"] = :plugin
284+
options["require"] = false
285+
286+
gem(*args, options)
275287
end
276288

277289
def method_missing(name, *args)

bundler/lib/bundler/feature_flag.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def self.settings_method(name, key, &default)
3535
settings_flag(:global_gem_cache) { bundler_3_mode? }
3636
settings_flag(:path_relative_to_cwd) { bundler_3_mode? }
3737
settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") }
38+
settings_flag(:plugins_in_lockfile) { bundler_3_mode? }
3839
settings_flag(:print_only_version_number) { bundler_3_mode? }
3940
settings_flag(:setup_makes_kernel_gem_public) { !bundler_3_mode? }
4041
settings_flag(:update_requires_all_flag) { bundler_4_mode? }

bundler/lib/bundler/man/bundle-config.1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ The following is a list of all configuration keys and their purpose\. You can le
163163
.IP "\(bu" 4
164164
\fBplugins\fR (\fBBUNDLE_PLUGINS\fR): Enable Bundler's experimental plugin system\.
165165
.IP "\(bu" 4
166+
\fBplugins_in_lockfile\fR (\fBBUNDLE_PLUGINS_IN_LOCKFILE\fR): Include plugins as regular dependencies in the lockfile\.
167+
.IP "\(bu" 4
166168
\fBprefer_patch\fR (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\.
167169
.IP "\(bu" 4
168170
\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR): Print only version number from \fBbundler \-\-version\fR\.

bundler/lib/bundler/man/bundle-config.1.ronn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
234234
Makes `--path` relative to the CWD instead of the `Gemfile`.
235235
* `plugins` (`BUNDLE_PLUGINS`):
236236
Enable Bundler's experimental plugin system.
237+
* `plugins_in_lockfile` (`BUNDLE_PLUGINS_IN_LOCKFILE`):
238+
Include plugins as regular dependencies in the lockfile.
237239
* `prefer_patch` (BUNDLE_PREFER_PATCH):
238240
Prefer updating only to next patch version during updates. Makes `bundle update` calls equivalent to `bundler update --patch`.
239241
* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`):

0 commit comments

Comments
 (0)