mirror of
https://github.com/correl/rebar.git
synced 2024-11-23 11:09:55 +00:00
Load plugins dynamically from source
This patch updates rebar_core to look for missing plugins (i.e. those that aren't found on the code path at runtime) in a configurable plugin directory, and dynamically compile and load them at runtime. By default, the directory "plugins" is searched, although this can be overriden by setting the plugin_dir in your rebar.config.
This commit is contained in:
parent
7a1c88228b
commit
3b58935b86
8 changed files with 125 additions and 5 deletions
2
inttest/tplugins/bad.config
Normal file
2
inttest/tplugins/bad.config
Normal file
|
@ -0,0 +1,2 @@
|
|||
{plugins, [bad_plugin]}.
|
||||
{plugin_dir, "bad_plugins"}.
|
7
inttest/tplugins/bad_plugin.erl
Normal file
7
inttest/tplugins/bad_plugin.erl
Normal file
|
@ -0,0 +1,7 @@
|
|||
-module(bad_plugin).
|
||||
-compile(export_all).
|
||||
|
||||
%% this plugin contains numerous DELIBERATE syntax errors
|
||||
|
||||
fwibble(Config, _) >
|
||||
file:delete("fwibble.test")
|
5
inttest/tplugins/fish.erl
Normal file
5
inttest/tplugins/fish.erl
Normal file
|
@ -0,0 +1,5 @@
|
|||
-module(fish).
|
||||
|
||||
-compile(export_all).
|
||||
|
||||
fish() -> fish.
|
1
inttest/tplugins/rebar.config
Normal file
1
inttest/tplugins/rebar.config
Normal file
|
@ -0,0 +1 @@
|
|||
{plugins, [test_plugin]}.
|
8
inttest/tplugins/test_plugin.erl
Normal file
8
inttest/tplugins/test_plugin.erl
Normal file
|
@ -0,0 +1,8 @@
|
|||
-module(test_plugin).
|
||||
-compile(export_all).
|
||||
|
||||
fwibble(Config, _) ->
|
||||
Pwd = rebar_utils:get_cwd(),
|
||||
Ok = filelib:is_regular(filename:join(Pwd, "fwibble.test")),
|
||||
rebar_log:log(info, "~p:~p in ~s :: ~p~n", [test_plugin, clean, Pwd, Ok]),
|
||||
ok = file:delete("fwibble.test").
|
40
inttest/tplugins/tplugins_rt.erl
Normal file
40
inttest/tplugins/tplugins_rt.erl
Normal file
|
@ -0,0 +1,40 @@
|
|||
-module(tplugins_rt).
|
||||
-compile(export_all).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-define(COMPILE_ERROR,
|
||||
"ERROR: Plugin bad_plugin contains compilation errors:").
|
||||
|
||||
files() ->
|
||||
[
|
||||
{copy, "../../rebar", "rebar"},
|
||||
{copy, "rebar.config", "rebar.config"},
|
||||
{copy, "bad.config", "bad.config"},
|
||||
{copy, "fish.erl", "src/fish.erl"},
|
||||
{copy, "test_plugin.erl", "plugins/test_plugin.erl"},
|
||||
{copy, "bad_plugin.erl", "bad_plugins/bad_plugin.erl"},
|
||||
{create, "fwibble.test", <<"fwibble">>},
|
||||
{create, "ebin/fish.app", app(fish, [fish])}
|
||||
].
|
||||
|
||||
run(Dir) ->
|
||||
?assertMatch({ok, _}, retest_sh:run("./rebar fwibble -v", [])),
|
||||
?assertEqual(false, filelib:is_regular("fwibble.test")),
|
||||
Ref = retest:sh("./rebar -C bad.config -v clean", [{async, true}]),
|
||||
{ok, _} = retest:sh_expect(Ref, "ERROR: Plugin .*bad_plugin.erl "
|
||||
"contains compilation errors:.*",
|
||||
[{newline, any}]),
|
||||
ok.
|
||||
|
||||
%%
|
||||
%% Generate the contents of a simple .app file
|
||||
%%
|
||||
app(Name, Modules) ->
|
||||
App = {application, Name,
|
||||
[{description, atom_to_list(Name)},
|
||||
{vsn, "1"},
|
||||
{modules, Modules},
|
||||
{registered, []},
|
||||
{applications, [kernel, stdlib]}]},
|
||||
io_lib:format("~p.\n", [App]).
|
|
@ -123,6 +123,19 @@
|
|||
%% Subdirectories?
|
||||
{sub_dirs, ["dir1", "dir2"]}.
|
||||
|
||||
%% == Plugins ==
|
||||
|
||||
%% Plugins you wish to include.
|
||||
%% These can include any module on the code path, including deps.
|
||||
%% Alternatively, plugins can be placed as source files in the plugin_dir, in
|
||||
%% which case they will be compiled and loaded dynamically at runtime.
|
||||
{plugins, [plugin1, plugin2]}.
|
||||
|
||||
%% Override the directory in which plugin sources can be found.
|
||||
%% Defaults to ./plugins
|
||||
{plugin_dir, "some_other_directory"}.
|
||||
|
||||
|
||||
%% == Pre/Post Command Hooks ==
|
||||
|
||||
{pre_hooks, [{clean, "./prepare_package_files.sh"},
|
||||
|
|
|
@ -380,13 +380,57 @@ ulist([H | T], Acc) ->
|
|||
|
||||
plugin_modules(_Config, []) ->
|
||||
{ok, []};
|
||||
plugin_modules(_Config, Modules) ->
|
||||
plugin_modules(Config, Modules) ->
|
||||
FoundModules = [M || M <- Modules, code:which(M) =/= non_existing],
|
||||
case (Modules =:= FoundModules) of
|
||||
plugin_modules(Config, FoundModules, Modules -- FoundModules).
|
||||
|
||||
plugin_modules(_Config, FoundModules, []) ->
|
||||
{ok, FoundModules};
|
||||
plugin_modules(Config, FoundModules, MissingModules) ->
|
||||
{Loaded, NotLoaded} = load_plugin_modules(Config, MissingModules),
|
||||
AllViablePlugins = FoundModules ++ Loaded,
|
||||
case length(NotLoaded) > 0 of
|
||||
true ->
|
||||
ok;
|
||||
%% NB: we continue to ignore this situation, as did the original code
|
||||
?WARN("Missing plugins: ~p\n", NotLoaded);
|
||||
false ->
|
||||
?WARN("Missing plugins: ~p\n", [Modules -- FoundModules]),
|
||||
?DEBUG("Loaded plugins: ~p~n", [AllViablePlugins]),
|
||||
ok
|
||||
end,
|
||||
{ok, FoundModules}.
|
||||
{ok, AllViablePlugins}.
|
||||
|
||||
load_plugin_modules(Config, Modules) ->
|
||||
PluginDir = case rebar_config:get_local(Config, plugin_dir, undefined) of
|
||||
undefined ->
|
||||
filename:join(rebar_utils:get_cwd(), "plugins");
|
||||
Dir ->
|
||||
Dir
|
||||
end,
|
||||
Sources = rebar_utils:find_files(PluginDir, ".*\.erl\$"),
|
||||
Loaded = [load_plugin(Src) || Src <- Sources],
|
||||
FilterMissing = is_missing_plugin(Loaded),
|
||||
NotLoaded = [V || V <- Modules, FilterMissing(V)],
|
||||
{Loaded, NotLoaded}.
|
||||
|
||||
is_missing_plugin(Loaded) ->
|
||||
fun(Mod) -> not lists:member(Mod, Loaded) end.
|
||||
|
||||
load_plugin(Src) ->
|
||||
case compile:file(Src, [binary, return_errors]) of
|
||||
{ok, Mod, Bin} ->
|
||||
load_plugin_module(Mod, Bin, Src);
|
||||
{error, Errors, _Warnings} ->
|
||||
?ABORT("Plugin ~s contains compilation errors: ~p~n",
|
||||
[Src, Errors])
|
||||
end.
|
||||
|
||||
load_plugin_module(Mod, Bin, Src) ->
|
||||
case code:is_loaded(Mod) of
|
||||
{file, Loaded} ->
|
||||
?ABORT("Plugin ~p clashes with previously loaded module ~p~n",
|
||||
[Mod, Loaded]);
|
||||
false ->
|
||||
?INFO("Loading plugin ~p from ~s~n", [Mod, Src]),
|
||||
{module, Mod} = code:load_binary(Mod, Src, Bin),
|
||||
Mod
|
||||
end.
|
||||
|
|
Loading…
Reference in a new issue