自定义编译器插件

本教程演示如何编写提供全新编译器的插件。如果您希望与 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}}]}
]}.
最后修改时间:2022年12月13日:修复错误的 `构建插件` 链接 (a5a8880)