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 :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__ )
12+ autoload :UnloadedSource , File . expand_path ( "plugin/unloaded_source" , __dir__ )
1213
1314 class MalformattedPlugin < PluginError ; end
1415 class UndefinedCommandError < PluginError ; end
@@ -17,6 +18,14 @@ class PluginInstallError < PluginError; end
1718
1819 PLUGIN_FILE_NAME = "plugins.rb"
1920
21+ # Module-level flag set while .gemfile_install parses the Gemfile and
22+ # consulted by .from_lock to substitute plugin sources with
23+ # UnloadedSource. It relies on definitions being built one at a time in
24+ # a single thread; if they are ever built concurrently or reentrantly,
25+ # this needs to be replaced by explicit state passed down to the
26+ # lockfile parser.
27+ @gemfile_parse = false
28+
2029 module_function
2130
2231 def reset!
@@ -26,6 +35,7 @@ def reset!
2635 @commands = { }
2736 @hooks_by_event = Hash . new { |h , k | h [ k ] = [ ] }
2837 @loaded_plugin_names = [ ]
38+ @index = nil
2939 end
3040
3141 reset!
@@ -40,7 +50,7 @@ def install(names, options)
4050
4151 specs = Installer . new . install ( names , options )
4252
43- save_plugins names , specs
53+ save_plugins specs . slice ( * names )
4454 rescue PluginError
4555 specs_to_delete = specs . select { |k , _v | names . include? ( k ) && !index . commands . values . include? ( k ) }
4656 specs_to_delete . each_value { |spec | Bundler . rm_rf ( spec . full_gem_path ) }
@@ -100,29 +110,44 @@ def list
100110 #
101111 # @param [Pathname] gemfile path
102112 # @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
105- builder = DSL . new
106- if block_given?
107- builder . instance_eval ( &inline )
108- else
109- builder . eval_gemfile ( gemfile )
110- end
111- builder . check_primary_source_safety
112- definition = builder . to_definition ( nil , true )
113-
114- return if definition . dependencies . empty?
113+ def gemfile_install ( gemfile = nil , lockfile = nil , unlock = { } , &inline )
114+ @gemfile_parse = true
115+ Bundler . configure
116+ builder = DSL . new
117+ if block_given?
118+ builder . instance_eval ( &inline )
119+ else
120+ builder . eval_gemfile ( gemfile )
121+ end
122+ builder . check_primary_source_safety
115123
116- plugins = definition . dependencies . map ( &:name )
117- installed_specs = Installer . new . install_definition ( definition )
124+ plugins = builder . dependencies . map ( &:name )
125+ return if plugins . empty?
118126
119- save_plugins plugins , installed_specs , builder . inferred_plugins
127+ # skip the update if unlocking specific gems, but none of them are plugins
128+ # declared in the Gemfile
129+ if unlock . is_a? ( Hash ) && unlock [ :gems ] && !unlock [ :gems ] . empty? &&
130+ ( unlock [ :gems ] & plugins ) . empty?
131+ unlock = { }
120132 end
133+
134+ # resolve remotely when unlocking, so that plugins can be updated.
135+ # Definition#initialize consumes the unlock hash, so this must be decided
136+ # before building the definition.
137+ updating = unlock == true || ( unlock . is_a? ( Hash ) && !unlock . empty? )
138+
139+ definition = builder . to_definition ( lockfile , unlock )
140+
141+ installed_specs = Installer . new . install_definition ( definition , updating )
142+
143+ save_plugins installed_specs . slice ( *plugins ) , builder . inferred_plugins
121144 rescue RuntimeError => e
122145 unless e . is_a? ( GemfileError )
123146 Bundler . ui . error "Failed to install plugin: #{ e . message } \n #{ e . backtrace [ 0 ] } "
124147 end
125148 raise
149+ ensure
150+ @gemfile_parse = false
126151 end
127152
128153 # The index object used to store the details about the plugin
@@ -183,12 +208,17 @@ def add_source(source, cls)
183208
184209 # Checks if any plugin declares the source
185210 def source? ( name )
186- !index . source_plugin ( name . to_s ) . nil?
211+ !!source_plugin ( name )
212+ end
213+
214+ # Returns the plugin that handles the source +name+ if any
215+ def source_plugin ( name )
216+ index . source_plugin ( name . to_s )
187217 end
188218
189219 # @return [Class] that handles the source. The class includes API::Source
190220 def source ( name )
191- raise UnknownSourceError , "Source #{ name } not found" unless source? name
221+ raise UnknownSourceError , "Source #{ name } not found" unless source_plugin ( name )
192222
193223 load_plugin ( index . source_plugin ( name ) ) unless @sources . key? name
194224
@@ -199,9 +229,14 @@ def source(name)
199229 # @return [API::Source] the instance of the class that handles the source
200230 # type passed in locked_opts
201231 def from_lock ( locked_opts )
232+ opts = locked_opts . merge ( "uri" => locked_opts [ "remote" ] )
233+ # when reading the lockfile while doing the plugin-install-from-gemfile phase,
234+ # we need to ignore any plugin sources
235+ return UnloadedSource . new ( opts ) if @gemfile_parse
236+
202237 src = source ( locked_opts [ "type" ] )
203238
204- src . new ( locked_opts . merge ( "uri" => locked_opts [ "remote" ] ) )
239+ src . new ( opts )
205240 end
206241
207242 # To be called via the API to register a hooks and corresponding block that
@@ -237,7 +272,9 @@ def hook(event, *args, &arg_blk)
237272 #
238273 # @return [String, nil] installed path
239274 def installed? ( plugin )
240- Index . new . installed? ( plugin )
275+ ( path = index . installed? ( plugin ) ) &&
276+ index . plugin_path ( plugin ) . join ( PLUGIN_FILE_NAME ) . file? &&
277+ path
241278 end
242279
243280 # @return [true, false] whether the plugin is loaded
@@ -247,19 +284,11 @@ def loaded?(plugin)
247284
248285 # Post installation processing and registering with index
249286 #
250- # @param [Array<String>] plugins list to be installed
251287 # @param [Hash] specs of plugins mapped to installation path (currently they
252288 # contain all the installed specs, including plugins)
253289 # @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- spec = specs [ name ]
257-
258- # It's possible that the `plugin` found in the Gemfile don't appear in the specs. For instance when
259- # calling `BUNDLE_WITHOUT=default bundle install`, the plugins will not get installed.
260- next if spec . nil?
261- next if index . up_to_date? ( spec )
262-
290+ def save_plugins ( specs , optional_plugins = [ ] )
291+ specs . each do |name , spec |
263292 save_plugin ( name , spec , optional_plugins . include? ( name ) )
264293 end
265294 end
@@ -284,6 +313,8 @@ def validate_plugin!(plugin_path)
284313 #
285314 # @raise [PluginInstallError] if validation or registration raises any error
286315 def save_plugin ( name , spec , optional_plugin = false )
316+ return if index . up_to_date? ( spec )
317+
287318 validate_plugin! Pathname . new ( spec . full_gem_path )
288319 installed = register_plugin ( name , spec , optional_plugin )
289320 Bundler . ui . info "Installed plugin #{ name } " if installed
@@ -319,7 +350,7 @@ def register_plugin(name, spec, optional_plugin = false)
319350 raise MalformattedPlugin , "#{ e . class } : #{ e . message } "
320351 end
321352
322- if optional_plugin && @sources . keys . any? { |s | source? s }
353+ if optional_plugin && @sources . keys . any? { |s | source_plugin ( s ) }
323354 Bundler . rm_rf ( path )
324355 false
325356 else
0 commit comments