Skip to content

Commit 963eac1

Browse files
committed
Fix plugin installation from gemfile
1 parent e887efb commit 963eac1

9 files changed

Lines changed: 201 additions & 21 deletions

File tree

Manifest.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ bundler/lib/bundler/plugin/api/source.rb
162162
bundler/lib/bundler/plugin/dsl.rb
163163
bundler/lib/bundler/plugin/events.rb
164164
bundler/lib/bundler/plugin/index.rb
165+
bundler/lib/bundler/plugin/index_definition.rb
165166
bundler/lib/bundler/plugin/installer.rb
166167
bundler/lib/bundler/plugin/installer/git.rb
167168
bundler/lib/bundler/plugin/installer/path.rb

bundler/lib/bundler/cli/update.rb

Lines changed: 2 additions & 2 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,6 +31,8 @@ def run
3331

3432
conservative = options[:conservative]
3533

34+
Plugin.gemfile_install(Bundler.default_gemfile, :unlock => full_update && !conservative) if Bundler.feature_flag.plugins?
35+
3636
if full_update
3737
if conservative
3838
Bundler.definition(conservative: conservative)

bundler/lib/bundler/definition.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ def self.build(gemfile, lockfile, unlock)
5656
# to be updated or true if all gems should be updated
5757
# @param ruby_version [Bundler::RubyVersion, nil] Requested Ruby Version
5858
# @param optional_groups [Array(String)] A list of optional groups
59-
def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = [])
59+
# @param lockfile_contents [String, nil] The contents of the lockfile
60+
def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = [], lockfile_contents = nil)
6061
if [true, false].include?(unlock)
6162
@unlocking_bundler = false
6263
@unlocking = unlock
@@ -76,7 +77,6 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
7677
@gemfiles = gemfiles
7778

7879
@lockfile = lockfile
79-
@lockfile_contents = String.new
8080

8181
@locked_bundler_version = nil
8282
@resolved_bundler_version = nil
@@ -85,8 +85,8 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
8585
@new_platform = nil
8686
@removed_platform = nil
8787

88-
if lockfile_exists?
89-
@lockfile_contents = Bundler.read_file(lockfile)
88+
if lockfile_exists? || lockfile_contents
89+
@lockfile_contents = lockfile_contents || Bundler.read_file(lockfile)
9090
@locked_gems = LockfileParser.new(@lockfile_contents)
9191
@locked_platforms = @locked_gems.platforms
9292
@platforms = @locked_platforms.dup
@@ -106,6 +106,7 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
106106
@locked_sources = []
107107
end
108108
else
109+
@lockfile_contents = String.new
109110
@unlock = {}
110111
@platforms = []
111112
@locked_gems = nil

bundler/lib/bundler/dsl.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,9 @@ def github(repo, options = {})
226226
with_source(git_source) { yield }
227227
end
228228

229-
def to_definition(lockfile, unlock)
229+
def to_definition(lockfile, unlock, lockfile_contents: nil)
230230
check_primary_source_safety
231-
Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles)
231+
Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles, lockfile_contents)
232232
end
233233

234234
def group(*args, &blk)

bundler/lib/bundler/plugin.rb

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
module Bundler
66
module Plugin
7-
autoload :DSL, File.expand_path("plugin/dsl", __dir__)
8-
autoload :Events, File.expand_path("plugin/events", __dir__)
9-
autoload :Index, File.expand_path("plugin/index", __dir__)
10-
autoload :Installer, File.expand_path("plugin/installer", __dir__)
7+
autoload :DSL, File.expand_path("plugin/dsl", __dir__)
8+
autoload :Events, File.expand_path("plugin/events", __dir__)
9+
autoload :Index, File.expand_path("plugin/index", __dir__)
10+
autoload :IndexDefinition, File.expand_path("plugin/index_definition", __dir__)
11+
autoload :Installer, File.expand_path("plugin/installer", __dir__)
1112
autoload :SourceList, File.expand_path("plugin/source_list", __dir__)
1213

1314
class MalformattedPlugin < PluginError; end
@@ -100,21 +101,25 @@ def list
100101
#
101102
# @param [Pathname] gemfile path
102103
# @param [Proc] block that can be evaluated for (inline) Gemfile
103-
def gemfile_install(gemfile = nil, &inline)
104+
def gemfile_install(gemfile = nil, unlock: false, &inline)
104105
Bundler.settings.temporary(frozen: false, deployment: false) do
105106
builder = DSL.new
106107
if block_given?
107108
builder.instance_eval(&inline)
108109
else
109110
builder.eval_gemfile(gemfile)
110111
end
111-
builder.check_primary_source_safety
112-
definition = builder.to_definition(nil, true)
113112

114-
return if definition.dependencies.empty?
113+
return if builder.dependencies.empty?
115114

116-
plugins = definition.dependencies.map(&:name).reject {|p| index.installed? p }
117-
installed_specs = Installer.new.install_definition(definition)
115+
lockfile_contents = index.generate_lockfile(builder.instance_variable_get(:@sources), builder.dependencies)
116+
definition = builder.to_definition(nil, unlock || {}, :lockfile_contents => lockfile_contents)
117+
unless definition.no_resolve_needed?
118+
Installer.new.install_definition(definition)
119+
end
120+
121+
plugins = definition.dependencies.map(&:name)
122+
installed_specs = plugins.to_h {|p| [p, definition.specs[p].first] }
118123

119124
save_plugins plugins, installed_specs, builder.inferred_plugins
120125
end
@@ -253,8 +258,6 @@ def loaded?(plugin)
253258
# @param [Array<String>] names of inferred source plugins that can be ignored
254259
def save_plugins(plugins, specs, optional_plugins = [])
255260
plugins.each do |name|
256-
next if index.installed?(name)
257-
258261
spec = specs[name]
259262

260263
save_plugin(name, spec, optional_plugins.include?(name))
@@ -302,6 +305,8 @@ def register_plugin(name, spec, optional_plugin = false)
302305
sources = @sources
303306
hooks = @hooks_by_event
304307

308+
return false if index.installed?(name) == spec.full_gem_path
309+
305310
@commands = {}
306311
@sources = {}
307312
@hooks_by_event = Hash.new {|h, k| h[k] = [] }

bundler/lib/bundler/plugin/index.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,37 @@ def installed_in_plugin_root?(name)
144144
path.start_with?("#{Plugin.root}/")
145145
end
146146

147+
# generate an in-memory lockfile from the index
148+
def generate_lockfile(sources, dependencies)
149+
specs = []
150+
sources.cached!
151+
default_source = sources.global_rubygems_source
152+
153+
installed_plugins.each do |plugin|
154+
path = plugin_path(plugin)
155+
# path gems may have a gemspec, which is the most trustworthy
156+
# way to determine the version
157+
version = if (gemspec = path.join("#{plugin}.gemspec")).file?
158+
Gem::Specification.load(gemspec.to_s).version
159+
elsif (version_index = path.to_s.index("#{plugin}-"))
160+
path.to_s[(version_index + plugin.length + 1)..]
161+
end
162+
163+
next unless version
164+
165+
dep = dependencies.find {|d| d.name == plugin }
166+
next unless dep
167+
168+
spec = LazySpecification.new(plugin, version, nil, dep.source || default_source)
169+
next unless spec.satisfies?(dep)
170+
171+
specs << spec
172+
end
173+
174+
require_relative "../lockfile_generator"
175+
LockfileGenerator.generate(IndexDefinition.new(sources, specs, dependencies))
176+
end
177+
147178
private
148179

149180
# Reads the index file from the directory and initializes the instance
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
module Bundler
4+
module Plugin
5+
# duck-type of Definition for feeding to LockfileGenerator
6+
IndexDefinition = Struct.new(:sources, :specs, :dependencies) do
7+
def platforms
8+
[Bundler.local_platform]
9+
end
10+
11+
def locked_ruby_version; end
12+
13+
def bundler_version_to_lock
14+
VERSION
15+
end
16+
17+
alias_method :resolve, :specs
18+
end
19+
end
20+
end

bundler/spec/plugins/install_spec.rb

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,118 @@ def exec(command, args)
263263
expect(out).to include("Bundle complete!")
264264
end
265265

266+
it "upgrade plugins version listed in gemfile" do
267+
update_repo2 do
268+
build_plugin "foo", "1.4.0"
269+
build_plugin "foo", "1.5.0"
270+
end
271+
272+
gemfile <<-G
273+
source '#{file_uri_for(gem_repo2)}'
274+
plugin 'foo', "1.4.0"
275+
gem 'rack', "1.0.0"
276+
G
277+
278+
bundle "install"
279+
280+
expect(out).to include("Installing foo 1.4.0")
281+
expect(out).to include("Installed plugin foo")
282+
expect(out).to include("Bundle complete!")
283+
284+
expect(the_bundle).to include_gems("rack 1.0.0")
285+
plugin_should_be_installed_with_version("foo", "1.4.0")
286+
287+
gemfile <<-G
288+
source '#{file_uri_for(gem_repo2)}'
289+
plugin 'foo', "1.5.0"
290+
gem 'rack', "1.0.0"
291+
G
292+
293+
bundle "install"
294+
295+
expect(out).to include("Installing foo 1.5.0")
296+
expect(out).to include("Bundle complete!")
297+
298+
expect(the_bundle).to include_gems("rack 1.0.0")
299+
plugin_should_be_installed_with_version("foo", "1.5.0")
300+
end
301+
302+
it "downgrade plugins version listed in gemfile" do
303+
update_repo2 do
304+
build_plugin "foo", "1.4.0"
305+
build_plugin "foo", "1.5.0"
306+
end
307+
308+
gemfile <<-G
309+
source '#{file_uri_for(gem_repo2)}'
310+
plugin 'foo', "1.5.0"
311+
gem 'rack', "1.0.0"
312+
G
313+
314+
bundle "install"
315+
316+
expect(out).to include("Installing foo 1.5.0")
317+
expect(out).to include("Installed plugin foo")
318+
expect(out).to include("Bundle complete!")
319+
320+
expect(the_bundle).to include_gems("rack 1.0.0")
321+
plugin_should_be_installed_with_version("foo", "1.5.0")
322+
323+
gemfile <<-G
324+
source '#{file_uri_for(gem_repo2)}'
325+
plugin 'foo', "1.4.0"
326+
gem 'rack', "1.0.0"
327+
G
328+
329+
bundle "install"
330+
331+
expect(out).to include("Installing foo 1.4.0")
332+
expect(out).to include("Bundle complete!")
333+
334+
expect(the_bundle).to include_gems("rack 1.0.0")
335+
plugin_should_be_installed_with_version("foo", "1.4.0")
336+
end
337+
338+
it "install only plugins not installed yet listed in gemfile" do
339+
gemfile <<-G
340+
source '#{file_uri_for(gem_repo2)}'
341+
plugin 'foo'
342+
gem 'rack', "1.0.0"
343+
G
344+
345+
2.times { bundle "install" }
346+
347+
expect(out).to_not include("Fetching gem metadata")
348+
expect(out).to_not include("Installing foo")
349+
expect(out).to_not include("Installed plugin foo")
350+
351+
expect(out).to include("Bundle complete!")
352+
353+
expect(the_bundle).to include_gems("rack 1.0.0")
354+
plugin_should_be_installed("foo")
355+
356+
gemfile <<-G
357+
source '#{file_uri_for(gem_repo2)}'
358+
plugin 'foo'
359+
plugin 'kung-foo'
360+
gem 'rack', "1.0.0"
361+
G
362+
363+
bundle "install"
364+
365+
expect(out).to include("Installing kung-foo")
366+
expect(out).to include("Installed plugin kung-foo")
367+
368+
expect(out).to_not include("Installing foo")
369+
expect(out).to_not include("Installed plugin foo")
370+
371+
expect(out).to include("Bundle complete!")
372+
373+
expect(the_bundle).to include_gems("rack 1.0.0")
374+
plugin_should_be_installed("foo")
375+
plugin_should_be_installed("kung-foo")
376+
end
377+
266378
it "accepts git sources" do
267379
build_git "ga-plugin" do |s|
268380
s.write "plugins.rb"
@@ -327,7 +439,9 @@ def exec(command, args)
327439
RUBY
328440

329441
ruby code, env: { "BUNDLER_VERSION" => Bundler::VERSION }
330-
expect(local_plugin_gem("foo-1.0", "plugins.rb")).to exist
442+
443+
allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile)
444+
plugin_should_be_installed("foo")
331445
end
332446
end
333447

bundler/spec/support/matchers.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ def plugin_should_be_installed(*names)
216216
end
217217
end
218218

219+
def plugin_should_be_installed_with_version(name, version)
220+
expect(Bundler::Plugin).to be_installed(name)
221+
path = Pathname.new(Bundler::Plugin.installed?(name))
222+
223+
expect(File.basename(path)).to eq("#{name}-#{version}")
224+
expect(path + "plugins.rb").to exist
225+
end
226+
219227
def plugin_should_not_be_installed(*names)
220228
names.each do |name|
221229
expect(Bundler::Plugin).not_to be_installed(name)

0 commit comments

Comments
 (0)