@@ -134,46 +134,16 @@ active() ->
134
134
lists :member (App , InstalledPlugins )].
135
135
136
136
% % @doc Get the list of plugins which are ready to be enabled.
137
- list (PluginsDir ) ->
138
- list (PluginsDir , false ).
137
+ list (PluginsPath ) ->
138
+ list (PluginsPath , false ).
139
139
140
- list (PluginsDir , IncludeRequiredDeps ) ->
141
- EZs = [{ez , EZ } || EZ <- filelib :wildcard (" *.ez" , PluginsDir )],
142
- FreeApps = [{app , App } ||
143
- App <- filelib :wildcard (" */ebin/*.app" , PluginsDir )],
144
- % % We load the "rabbit" application to be sure we can get the
145
- % % "applications" key. This is required for rabbitmq-plugins for
146
- % % instance.
147
- application :load (rabbit ),
148
- {ok , RabbitDeps } = application :get_key (rabbit , applications ),
149
- AllPlugins = [plugin_info (PluginsDir , Plug ) || Plug <- EZs ++ FreeApps ],
150
- {AvailablePlugins , Problems } =
151
- lists :foldl (
152
- fun ({error , EZ , Reason }, {Plugins1 , Problems1 }) ->
153
- {Plugins1 , [{EZ , Reason } | Problems1 ]};
154
- (Plugin = # plugin {name = Name },
155
- {Plugins1 , Problems1 }) ->
156
- % % Applications RabbitMQ depends on (eg.
157
- % % "rabbit_common") can't be considered
158
- % % plugins, otherwise rabbitmq-plugins would
159
- % % list them and the user may believe he can
160
- % % disable them.
161
- case IncludeRequiredDeps orelse
162
- not lists :member (Name , RabbitDeps ) of
163
- true -> {[Plugin |Plugins1 ], Problems1 };
164
- false -> {Plugins1 , Problems1 }
165
- end
166
- end , {[], []},
167
- AllPlugins ),
168
- case Problems of
169
- [] -> ok ;
170
- _ -> rabbit_log :warning (
171
- " Problem reading some plugins: ~p~n " , [Problems ])
172
- end ,
173
-
174
- Plugins = lists :filter (fun (P ) -> not plugin_provided_by_otp (P ) end ,
175
- AvailablePlugins ),
176
- ensure_dependencies (Plugins ).
140
+ list (PluginsPath , IncludeRequiredDeps ) ->
141
+ {AllPlugins , LoadingProblems } = discover_plugins (split_path (PluginsPath )),
142
+ {UniquePlugins , DuplicateProblems } = remove_duplicate_plugins (AllPlugins ),
143
+ Plugins1 = maybe_keep_required_deps (IncludeRequiredDeps , UniquePlugins ),
144
+ Plugins2 = remove_otp_overrideable_plugins (Plugins1 ),
145
+ maybe_report_plugin_loading_problems (LoadingProblems ++ DuplicateProblems ),
146
+ ensure_dependencies (Plugins2 ).
177
147
178
148
% % @doc Read the list of enabled plugins from the supplied term file.
179
149
read_enabled (PluginsFile ) ->
@@ -425,14 +395,12 @@ prepare_plugin(#plugin{type = dir, name = Name, location = Location},
425
395
ExpandDir ) ->
426
396
rabbit_file :recursive_copy (Location , filename :join ([ExpandDir , Name ])).
427
397
428
- plugin_info (Base , {ez , EZ0 }) ->
429
- EZ = filename :join ([Base , EZ0 ]),
398
+ plugin_info ({ez , EZ }) ->
430
399
case read_app_file (EZ ) of
431
400
{application , Name , Props } -> mkplugin (Name , Props , ez , EZ );
432
401
{error , Reason } -> {error , EZ , Reason }
433
402
end ;
434
- plugin_info (Base , {app , App0 }) ->
435
- App = filename :join ([Base , App0 ]),
403
+ plugin_info ({app , App }) ->
436
404
case rabbit_file :read_term_file (App ) of
437
405
{ok , [{application , Name , Props }]} ->
438
406
mkplugin (Name , Props , dir ,
@@ -486,9 +454,99 @@ plugin_names(Plugins) ->
486
454
[Name || # plugin {name = Name } <- Plugins ].
487
455
488
456
lookup_plugins (Names , AllPlugins ) ->
489
- % Preserve order of Names
457
+ % % Preserve order of Names
490
458
lists :map (
491
459
fun (Name ) ->
492
460
lists :keyfind (Name , # plugin .name , AllPlugins )
493
461
end ,
494
462
Names ).
463
+
464
+ % % Split PATH-like value into its components.
465
+ split_path (PathString ) ->
466
+ Delimiters = case os :type () of
467
+ {unix , _ } -> " :" ;
468
+ {win32 , _ } -> " ;"
469
+ end ,
470
+ string :tokens (PathString , Delimiters ).
471
+
472
+ % % Search for files using glob in a given dir. Returns full filenames of those files.
473
+ full_path_wildcard (Glob , Dir ) ->
474
+ [filename :join ([Dir , File ]) || File <- filelib :wildcard (Glob , Dir )].
475
+
476
+ % % Returns list off all .ez files in a given set of directories
477
+ list_ezs ([]) ->
478
+ [];
479
+ list_ezs ([Dir |Rest ]) ->
480
+ [{ez , EZ } || EZ <- full_path_wildcard (" *.ez" , Dir )] ++ list_ezs (Rest ).
481
+
482
+ % % Returns list of all files that look like OTP applications in a
483
+ % % given set of directories.
484
+ list_free_apps ([]) ->
485
+ [];
486
+ list_free_apps ([Dir |Rest ]) ->
487
+ [{app , App } || App <- full_path_wildcard (" */ebin/*.app" , Dir )]
488
+ ++ list_free_apps (Rest ).
489
+
490
+ compare_by_name_and_version (# plugin {name = Name , version = VersionA },
491
+ # plugin {name = Name , version = VersionB }) ->
492
+ ec_semver :lte (VersionA , VersionB );
493
+ compare_by_name_and_version (# plugin {name = NameA },
494
+ # plugin {name = NameB }) ->
495
+ NameA =< NameB .
496
+
497
+ -spec discover_plugins ([Directory ]) -> {[# plugin {}], [Problem ]} when
498
+ Directory :: file :name (),
499
+ Problem :: {file :name (), term ()}.
500
+ discover_plugins (PluginsDirs ) ->
501
+ EZs = list_ezs (PluginsDirs ),
502
+ FreeApps = list_free_apps (PluginsDirs ),
503
+ read_plugins_info (EZs ++ FreeApps , {[], []}).
504
+
505
+ read_plugins_info ([], Acc ) ->
506
+ Acc ;
507
+ read_plugins_info ([Path |Paths ], {Plugins , Problems }) ->
508
+ case plugin_info (Path ) of
509
+ # plugin {} = Plugin ->
510
+ read_plugins_info (Paths , {[Plugin |Plugins ], Problems });
511
+ {error , Location , Reason } ->
512
+ read_plugins_info (Paths , {Plugins , [{Location , Reason }|Problems ]})
513
+ end .
514
+
515
+ remove_duplicate_plugins (Plugins ) ->
516
+ % % Reverse order ensures that if there are several versions of the
517
+ % % same plugin, the most recent one comes first.
518
+ Sorted = lists :reverse (
519
+ lists :sort (fun compare_by_name_and_version /2 , Plugins )),
520
+ remove_duplicate_plugins (Sorted , {[], []}).
521
+
522
+ remove_duplicate_plugins ([], Acc ) ->
523
+ Acc ;
524
+ remove_duplicate_plugins ([Best = # plugin {name = Name }, Offender = # plugin {name = Name } | Rest ],
525
+ {Plugins0 , Problems0 }) ->
526
+ Problems1 = [{Offender # plugin .location , duplicate_plugin }|Problems0 ],
527
+ remove_duplicate_plugins ([Best |Rest ], {Plugins0 , Problems1 });
528
+ remove_duplicate_plugins ([Plugin |Rest ], {Plugins0 , Problems0 }) ->
529
+ Plugins1 = [Plugin |Plugins0 ],
530
+ remove_duplicate_plugins (Rest , {Plugins1 , Problems0 }).
531
+
532
+ maybe_keep_required_deps (true , Plugins ) ->
533
+ Plugins ;
534
+ maybe_keep_required_deps (false , Plugins ) ->
535
+ % % We load the "rabbit" application to be sure we can get the
536
+ % % "applications" key. This is required for rabbitmq-plugins for
537
+ % % instance.
538
+ application :load (rabbit ),
539
+ {ok , RabbitDeps } = application :get_key (rabbit , applications ),
540
+ lists :filter (fun (# plugin {name = Name }) ->
541
+ not lists :member (Name , RabbitDeps )
542
+ end ,
543
+ Plugins ).
544
+
545
+ remove_otp_overrideable_plugins (Plugins ) ->
546
+ lists :filter (fun (P ) -> not plugin_provided_by_otp (P ) end ,
547
+ Plugins ).
548
+
549
+ maybe_report_plugin_loading_problems ([]) ->
550
+ ok ;
551
+ maybe_report_plugin_loading_problems (Problems ) ->
552
+ rabbit_log :warning (" Problem reading some plugins: ~p~n " , [Problems ]).
0 commit comments