自訂編譯器插件

本教學將示範如何撰寫提供全新編譯器的插件。如果您希望與 3.7.0 之前的 Rebar3 版本相容,或者您的編譯器需要編譯器插件行為所提供範圍之外的功能,則應該使用此方法。

應用程式通常包含需要編譯的非 Erlang 程式碼,例如 DTL 範本、用於從 PEG 檔案產生剖析器的 C 程式碼等。實作這些編譯器的插件提供者應位於其自己的命名空間中,並且如果要在使用者呼叫 `compile` 時自動執行,則必須掛接到主要的 `compile` 提供者。

{provider_hooks, [
    {post, [{compile, {pc, compile}}]}
]}.

在上面的範例中,命名空間 `pc`(port compiler,埠編譯器)具有一個名為 `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)