44
55module 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__ )
11- autoload :SourceList , File . expand_path ( "plugin/source_list" , __dir__ )
7+ autoload :DSL , File . expand_path ( "plugin/dsl" , __dir__ )
8+ autoload :DummySource , File . expand_path ( "plugin/dummy_source" , __dir__ )
9+ autoload :Events , File . expand_path ( "plugin/events" , __dir__ )
10+ autoload :Index , File . expand_path ( "plugin/index" , __dir__ )
11+ autoload :Installer , File . expand_path ( "plugin/installer" , __dir__ )
12+ autoload :SourceList , File . expand_path ( "plugin/source_list" , __dir__ )
1213
1314 class MalformattedPlugin < PluginError ; end
1415 class UndefinedCommandError < PluginError ; end
1516 class UnknownSourceError < PluginError ; end
1617 class PluginInstallError < PluginError ; end
1718
1819 PLUGIN_FILE_NAME = "plugins.rb"
20+ @gemfile_parse = false
1921
2022 module_function
2123
@@ -26,6 +28,7 @@ def reset!
2628 @commands = { }
2729 @hooks_by_event = Hash . new { |h , k | h [ k ] = [ ] }
2830 @loaded_plugin_names = [ ]
31+ @index = nil
2932 end
3033
3134 reset!
@@ -40,7 +43,7 @@ def install(names, options)
4043
4144 specs = Installer . new . install ( names , options )
4245
43- save_plugins names , specs
46+ save_plugins specs
4447 rescue PluginError
4548 specs_to_delete = specs . select { |k , _v | names . include? ( k ) && !index . commands . values . include? ( k ) }
4649 specs_to_delete . each_value { |spec | Bundler . rm_rf ( spec . full_gem_path ) }
@@ -100,29 +103,44 @@ def list
100103 #
101104 # @param [Pathname] gemfile path
102105 # @param [Proc] block that can be evaluated for (inline) Gemfile
103- def gemfile_install ( gemfile = nil , &inline )
104- Bundler . settings . temporary ( frozen : false , deployment : false ) do
106+ def gemfile_install ( gemfile = nil , lockfile = nil , unlock = { } , &inline )
107+ # skip the update if unlocking specific gems, but none of them are our plugins
108+ if unlock . is_a? ( Hash ) && unlock [ :gems ] && !unlock [ :gems ] . empty? &&
109+ ( unlock [ :gems ] & index . installed_plugins ) . empty?
110+ unlock = { }
111+ end
112+
113+ @gemfile_parse = true
114+ # plugins_in_lockfile is the user facing setting to force plugins to be
115+ # included in the lockfile as regular dependencies. But during this
116+ # first pass over the Gemfile where we're installing the plugins, we
117+ # need that setting to be set, so that we can find the plugins and
118+ # install them. We don't persist a lockfile during this pass, so it won't
119+ # have any user-facing impact.
120+ Bundler . settings . temporary ( plugins_in_lockfile : true ) do
121+ Bundler . configure
105122 builder = DSL . new
106123 if block_given?
107124 builder . instance_eval ( &inline )
108125 else
109126 builder . eval_gemfile ( gemfile )
110127 end
111128 builder . check_primary_source_safety
112- definition = builder . to_definition ( nil , true )
129+ definition = builder . to_definition ( lockfile , unlock )
113130
114131 return if definition . dependencies . empty?
115132
116- plugins = definition . dependencies . map ( &:name ) . reject { |p | index . installed? p }
117133 installed_specs = Installer . new . install_definition ( definition )
118134
119- save_plugins plugins , installed_specs , builder . inferred_plugins
135+ save_plugins installed_specs , builder . inferred_plugins
120136 end
121137 rescue RuntimeError => e
122138 unless e . is_a? ( GemfileError )
123139 Bundler . ui . error "Failed to install plugin: #{ e . message } \n #{ e . backtrace [ 0 ] } "
124140 end
125141 raise
142+ ensure
143+ @gemfile_parse = false
126144 end
127145
128146 # The index object used to store the details about the plugin
@@ -183,12 +201,17 @@ def add_source(source, cls)
183201
184202 # Checks if any plugin declares the source
185203 def source? ( name )
186- !index . source_plugin ( name . to_s ) . nil?
204+ !!source_plugin ( name )
205+ end
206+
207+ # Returns the plugin that handles the source +name+ if any
208+ def source_plugin ( name )
209+ index . source_plugin ( name . to_s )
187210 end
188211
189212 # @return [Class] that handles the source. The class includes API::Source
190213 def source ( name )
191- raise UnknownSourceError , "Source #{ name } not found" unless source? name
214+ raise UnknownSourceError , "Source #{ name } not found" unless source_plugin ( name )
192215
193216 load_plugin ( index . source_plugin ( name ) ) unless @sources . key? name
194217
@@ -199,9 +222,14 @@ def source(name)
199222 # @return [API::Source] the instance of the class that handles the source
200223 # type passed in locked_opts
201224 def from_lock ( locked_opts )
225+ opts = locked_opts . merge ( "uri" => locked_opts [ "remote" ] )
226+ # when reading the lockfile while doing the plugin-install-from-gemfile phase,
227+ # we need to ignore any plugin sources
228+ return DummySource . new ( opts ) if @gemfile_parse
229+
202230 src = source ( locked_opts [ "type" ] )
203231
204- src . new ( locked_opts . merge ( "uri" => locked_opts [ "remote" ] ) )
232+ src . new ( opts )
205233 end
206234
207235 # To be called via the API to register a hooks and corresponding block that
@@ -237,7 +265,9 @@ def hook(event, *args, &arg_blk)
237265 #
238266 # @return [String, nil] installed path
239267 def installed? ( plugin )
240- Index . new . installed? ( plugin )
268+ ( path = index . installed? ( plugin ) ) &&
269+ index . plugin_path ( plugin ) . join ( PLUGIN_FILE_NAME ) . file? &&
270+ path
241271 end
242272
243273 # @return [true, false] whether the plugin is loaded
@@ -251,12 +281,8 @@ def loaded?(plugin)
251281 # @param [Hash] specs of plugins mapped to installation path (currently they
252282 # contain all the installed specs, including plugins)
253283 # @param [Array<String>] names of inferred source plugins that can be ignored
254- def save_plugins ( plugins , specs , optional_plugins = [ ] )
255- plugins . each do |name |
256- next if index . installed? ( name )
257-
258- spec = specs [ name ]
259-
284+ def save_plugins ( specs , optional_plugins = [ ] )
285+ specs . each do |name , spec |
260286 save_plugin ( name , spec , optional_plugins . include? ( name ) )
261287 end
262288 end
@@ -281,7 +307,10 @@ def validate_plugin!(plugin_path)
281307 #
282308 # @raise [PluginInstallError] if validation or registration raises any error
283309 def save_plugin ( name , spec , optional_plugin = false )
284- validate_plugin! Pathname . new ( spec . full_gem_path )
310+ path = Pathname . new ( spec . full_gem_path )
311+ return if index . installed? ( name ) && index . plugin_path ( name ) == path
312+
313+ validate_plugin! ( path )
285314 installed = register_plugin ( name , spec , optional_plugin )
286315 Bundler . ui . info "Installed plugin #{ name } " if installed
287316 rescue PluginError => e
@@ -316,7 +345,7 @@ def register_plugin(name, spec, optional_plugin = false)
316345 raise MalformattedPlugin , "#{ e . class } : #{ e . message } "
317346 end
318347
319- if optional_plugin && @sources . keys . any? { |s | source? s }
348+ if optional_plugin && @sources . keys . any? { |s | source_plugin ( s ) }
320349 Bundler . rm_rf ( path )
321350 false
322351 else
0 commit comments