mirror of
https://github.com/correl/rebar.git
synced 2024-11-23 19:19:54 +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?
|
%% Subdirectories?
|
||||||
{sub_dirs, ["dir1", "dir2"]}.
|
{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/Post Command Hooks ==
|
||||||
|
|
||||||
{pre_hooks, [{clean, "./prepare_package_files.sh"},
|
{pre_hooks, [{clean, "./prepare_package_files.sh"},
|
||||||
|
|
|
@ -380,13 +380,57 @@ ulist([H | T], Acc) ->
|
||||||
|
|
||||||
plugin_modules(_Config, []) ->
|
plugin_modules(_Config, []) ->
|
||||||
{ok, []};
|
{ok, []};
|
||||||
plugin_modules(_Config, Modules) ->
|
plugin_modules(Config, Modules) ->
|
||||||
FoundModules = [M || M <- Modules, code:which(M) =/= non_existing],
|
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 ->
|
true ->
|
||||||
ok;
|
%% NB: we continue to ignore this situation, as did the original code
|
||||||
|
?WARN("Missing plugins: ~p\n", NotLoaded);
|
||||||
false ->
|
false ->
|
||||||
?WARN("Missing plugins: ~p\n", [Modules -- FoundModules]),
|
?DEBUG("Loaded plugins: ~p~n", [AllViablePlugins]),
|
||||||
ok
|
ok
|
||||||
end,
|
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