自定义编译器插件
本教程演示如何编写提供全新编译器的插件。如果您希望与 3.7.0 之前的 Rebar3 版本兼容,或者当您的编译器需要编译器插件行为范围之外的功能时,应该使用此方法。
应用程序通常包含需要编译的非 Erlang 代码,例如 DTL 模板、用于从 PEG 文件生成解析器的 C 代码等。实现这些编译器的插件提供程序应该位于其自己的命名空间中,并且如果要在用户调用 compile
时自动运行,则必须挂接到主 compile
提供程序。
{provider_hooks, [
{post, [{compile, {pc, compile}}]}
]}.
在上面的示例中,命名空间 pc
(端口编译器)具有名为 compile
的提供程序,我们已将其设置为在主 compile
提供程序之后运行。
示例提供程序
我们将实现一个示例提供程序,该提供程序将 exc_files/
目录中扩展名为 .exc
的文件“编译”到应用程序的 priv 目录。完整的源代码可以在 github 上找到。
定义与 构建插件 教程中的定义类似,但在这种情况下,我们还有一个 NAMESPACE
宏。这很重要,因为提供程序名称为 compile
,如果不定义新的命名空间,它将与现有的 default
命名空间 compile
提供程序冲突。
-module(rebar3_prv_ex_compiler).
-export([init/1, do/1, format_error/1]).
-define(PROVIDER, compile).
-define(NAMESPACE, exc).
-define(DEPS, [{default, app_discovery}]).
对于 init/1
也是如此,类似于之前的教程,但将 namespace
添加到属性中。
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
Provider = providers:create([
{name, ?PROVIDER},
{namespace, ?NAMESPACE},
{module, ?MODULE},
{bare, true},
{deps, ?DEPS},
{example, "rebar3 exc compile"},
{opts, []},
{short_desc, "An example rebar compile plugin"},
{desc, ""}
]),
{ok, rebar_state:add_provider(State, Provider)}.
现在来看提供程序的核心部分。由于该提供程序用于编译 Erlang 应用程序的一部分,因此我们必须找到当前正在构建的应用程序。如果提供程序作为钩子运行,则 current_app
将包含要使用的应用程序记录。否则它将未定义,例如用户运行 rebar3 exc compile
的情况。在这种情况下,要为其编译文件的应用程序列表是 project_apps
,位于 State
中。
对于每个应用程序,都会运行 rebar_base_compiler:run/4
函数,它将在每个源文件上运行 CompileFun
(在本例中为 exc_compile/3
)。
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
Apps = case rebar_state:current_app(State) of
undefined ->
rebar_state:project_apps(State);
AppInfo ->
[AppInfo]
end,
[begin
Opts = rebar_app_info:opts(AppInfo),
OutDir = rebar_app_info:out_dir(AppInfo),
SourceDir = filename:join(rebar_app_info:dir(AppInfo), "exc_files"),
FoundFiles = rebar_utils:find_files(SourceDir, ".*\\.exc\$"),
CompileFun = fun(Source, Opts1) ->
exc_compile(Opts1, Source, OutDir)
end,
rebar_base_compiler:run(Opts, [], FoundFiles, CompileFun)
end || AppInfo <- Apps],
{ok, State}.
最后,exc_compile/3
读取源文件并将其写入应用程序的输出 priv
目录。是的,我们实际上并没有“编译”任何东西,但如果您想这样做,这就是您应该进行操作的地方。
exc_compile(_Opts, Source, OutDir) ->
{ok, Binary} = file:read_file(Source),
OutFile = filename:join([OutDir, "priv", filename:basename(Source)]),
filelib:ensure_dir(OutFile),
rebar_api:info("Writing out ~s", [OutFile]),
file:write_file(OutFile, Binary).
最后,在项目的 rebar.config
中,我们的提供程序可以使用 provider_hook
挂接到默认的 compile
提供程序,并在每次执行 rebar3 compile
时运行。在这种情况下,rebar_state:current_app/1
将返回一个 AppInfo
记录,用于表示我们当前正在构建的应用程序。
{provider_hooks, [
{pre, [{compile, {exc, compile}}]}
]}.