自訂編譯器插件
本教學將示範如何撰寫提供全新編譯器的插件。如果您希望與 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}}]}
]}.