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 }   unless  source?   name 
214+       raise  UnknownSourceError ,  "Source #{ name }   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