建置插件

Rebar3 的系統基於提供者的概念。一個提供者有三個回呼函式

  • init(State) -> {ok, NewState},用於設定所需的狀態、狀態相依性等。
  • do(State) -> {ok, NewState} | {error, Error},執行實際工作。
  • format_error(Error) -> String,在發生錯誤時印出錯誤訊息,並從狀態中過濾掉敏感元素。

提供者也應該是一個 OTP 應用程式庫,可以像其他 Erlang 相依性一樣被擷取,只是它是為了 Rebar3 而不是您自己的系統或應用程式。

本文檔包含以下元素

使用插件

要使用插件,請將其添加到 rebar.config

{plugins, [
  {plugin_name, {git, "git@host:user/name-of-plugin.git", {tag, "1.0.0"}}}
]}.

然後您可以直接調用它

$ rebar3 plugin_name
===> Fetching plugin_name
===> Compiling plugin_name
<PLUGIN OUTPUT>

參考

提供者介面

每個提供者都具有以下可用選項

  • name:任務的「使用者友善」名稱。
  • module:任務的模組實作。
  • hooks:前後鉤子提供者名稱的二元組 ({Pre, Post})。
  • bare:指示任務是否可以由使用者執行。應為 true
  • deps:相依性列表,需要在此提供者之前執行的提供者。您不需要包含相依性的相依性。
  • desc:任務的描述,由 rebar3 help 使用
  • short_desc:任務的單行簡短描述,用於提供者列表中
  • example:任務用法範例,例如 "rebar3 my-provider args"
  • opts:任務需要/理解的選項列表。每個選項的形式為 {Key, $Character, "StringName", Spec, HelpText},其中
    • Key 是一個原子,稍後用於擷取值;
    • $Character 是選項的簡短形式。因此,如果指令要輸入為 -c Arg,則 $c 是此欄位的值
    • Spec 是一個類型(atombinarybooleanfloatintegerstring)、具有預設值的類型 ({Type, Val}) 或原子 undefined
  • profiles:用於提供者的設定檔。預設為 [default]
  • namespace:提供者註冊的命名空間。預設為 default,這是主要的命名空間。

建立提供者時,應將這些選項新增到提供者中。

提供者具有以下實作

-module(provider_template).
-behaviour(provider).

-export([init/1, do/1, format_error/1]).

%% ===================================================================
%% Public API
%% ===================================================================

%% Called when rebar3 first boots, before even parsing the arguments
%% or commands to be run. Purely initiates the provider, and nothing
%% else should be done here.
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
    Provider = providers:create([Options]),
    {ok, rebar_state:add_provider(State, Provider)}.

%% Run the code for the plugin. The command line argument are parsed
%% and dependencies have been run.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
    {ok, State}.

%% When an exception is raised or a value returned as
%% `{error, {?MODULE, Reason}}` will see the `format_error(Reason)`
%% function called for them, so a string can be formatted explaining
%% the issue.
-spec format_error(any()) -> iolist().
format_error(Reason) ->
    io_lib:format("~p", [Reason]).

可能的相依性列表

所有相依性都在預設命名空間中,除非另有說明

名稱 函式 設定檔 也相依於
app_discovery 探索使用者應用程式並載入其設定。 default
clean 從應用程式中移除已編譯的 beam 檔案。 default app_discovery
compile 編譯應用程式的 .app.src 和 .erl 檔案。 default lock
cover 分析 cover 編譯的檔案 default lock
ct 執行 Common Test 測試套件。 test compile
deps 列出相依性。 default app_discovery
dialyzer 在專案上執行 Dialyzer 分析器。 default compile
edoc 使用 edoc 產生文件。 default app_discovery
eunit 執行 EUnit 測試。 test compile
help 顯示任務列表或給定任務或子任務的說明。 default
install_deps 下載相依性 default app_discovery
lock 鎖定相依性並新增 rebar.lock default install_deps
new 從範本建立新專案。 default
pkgs 列出可用的套件。 default
release 建置專案的發布版本。 default compile
report 提供要發送到 rebar3 問題頁面的當機報告 default
shell 執行 shell,並將專案應用程式和相依性添加到路徑中。 default compile
tar 專案建置版本的 Tar 封存檔。 default compile
update 更新套件索引。 default
upgrade 升級相依性。 default
version 印出 rebar 和目前 Erlang 的版本。 default
xref 執行交叉參考分析 default compile

請注意,您可以相依於多個提供者,但它們*必須在同一個命名空間中*

Rebar API

Rebar 附帶一個名為 rebar_api 的模組,在編寫提供者時匯出常用的函式。函式包括

函式 用法
abort() 中斷程式流程
abort(FormatString, Args) 中斷程式流程;同時顯示錯誤訊息。

相當於調用 rebar_api:error(FormatString, Args) 後跟 rebar_api:abort()
console(FormatString, Args) 印出到控制台。
info(FormatString, Args) 以 INFO 嚴重性記錄
warn(FormatString, Args) 以 WARNING 嚴重性記錄
error(FormatString, Args) 以 ERROR 嚴重性記錄
debug(FormatString, Args) 以 DEBUG 嚴重性記錄
expand_env_variable(InStr, VarName, RawVarValue) 給定環境變數 FOO,我們想要在 InStr 中展開所有對它的引用。

引用可以有兩種形式:$FOO 和 ${FOO}。$FOO 的形式由空格字元或行尾 (eol) 分隔。
get_arch() 以 “$OTP_VSN-$SYSTEM_$ARCH-WORDSIZE” 形式的字串傳回「架構」。

最終字串看起來像 “17-x86_64-apple-darwin13.4.0-8” 或 “17-x86_64-unknown-linux-gnu-8”
wordsize() 傳回模擬器的實際字組大小,即指標的大小,以位元組為單位的字串。
add_deps_to_path(RebarState) 將專案的相依性新增到程式碼路徑。當調用工具且需要對程式庫進行全域狀態存取時很有用。
restore_code_path(RebarState) 將程式碼路徑還原為僅包含執行 Rebar3 及其插件所需的程式庫。這是 Rebar3 的期望狀態,以避免與使用者提供的工具發生衝突。
ssl_opts(Url) 傳回與 httpc 一起使用的 ssl 選項,以發出安全且經過驗證的 HTTP 請求。

請注意,所有記錄函式都會自動在每個記錄的表達式中新增一個換行符 (~n)。

Rebar 狀態操作

傳遞給插件提供者的 State 參數可以使用 rebar_state 模組透過以下介面進行操作

函式 用法
get(State, Key, [DefaultValue]) -> Value 當 rebar.config 元素的形式為 {Key, Value} 時,擷取其值
set(State, Key, Value) -> *NewState* 將設定值新增到 rebar 狀態。
lock(State) -> ListOfLocks 傳回鎖定相依性的列表
escript_path(State) -> Path 傳回 Rebar3 escript 位置
command_args(State) -> RawArgs 傳回傳遞給 rebar3 的參數
command_parsed_args(State) -> Args 傳回傳遞給 rebar3 的已解析參數。
deps_names(State) -> DepsNameList 傳回相依性名稱的列表
project_apps(State) -> AppList 傳回應用程式列表。可以使用 rebar_app_info 處理這些應用程式。
all_deps(State) -> DepsList 傳回相依性列表。可以使用 rebar_app_info 處理這些相依性。
add_provider(State, Provider) -> NewState 註冊一個新的提供者,其中 Provider 是調用 providers:create(Options) 的結果。為了生效,必須在提供者的 init/1 函式中調用此函式。可以多次調用它,允許插件註冊多個指令。
add_resource(State, {Key, Module}) -> NewState 使用用於處理它的模組註冊新的資源類型(例如 git、hg 等)。資源必須實作 rebar_resource 行為模式。為了生效,必須在提供者的 init/1 函式中調用此函式。

操作應用程式狀態

正在建置的每個應用程式(專案應用程式和相依性)。所有 AppInfo 記錄都可以在 State 中找到,並透過 project_apps/1all_deps/1 進行存取

函式 用法
get(AppInfo, Key, [DefaultValue]) -> Value 擷取為應用程式 AppInfo 定義的 Key 值
set(AppInfo, Key, Value) -> NewState 將一個設定值新增到應用程式的記錄中

命名空間

對於可能需要多個命令,且這些命令都適用於單一類型任務的插件(例如,為 Erlang 以外的 BEAM 語言實現一套工具),rebar3 引入了命名空間的支持,而不是讓多個命令污染命令空間或需要使用諸如 rebar3 mylang_compile 之類的前綴。

可以將插件聲明為屬於給定的命名空間。例如,ErlyDTL 編譯器插件erlydtl 命名空間下引入了 compile 命令。因此,可以通過 rebar3 erlydtl compile 來調用它。如果 erlydtl 命名空間還有其他命令,例如 clean,則可以將它們鏈接為 rebar3 erlydtl clean, compile

在其他方面,命名空間的作用類似於 dorebar3 do compile, edoc),但在非預設的命令集上操作。

要聲明命名空間,提供者只需在其配置列表中使用 {namespace, Namespace} 選項。提供者將自動註冊新的命名空間,並可在該術語下使用。

🚧

命名空間也適用於提供者依賴項和鉤子

如果提供者是給定命名空間的一部分,則將在同一命名空間內搜尋其依賴項。因此,如果 rebar3 mytool rebuild 依賴於 compile,則將在 mytool 命名空間中尋找 compile 命令。

要使用預設的 compile 命令,必須將依賴項聲明為 {default, compile},或者更通用的 {NameSpace, Command}

鉤子也適用相同的機制。

`

教學

第一個版本

在本教學中,我們將展示如何從頭開始,並編寫一個基本的插件。該插件非常簡單:它將在註釋中尋找 TODO: 行的實例,並將它們報告為警告。插件的最終程式碼可以在 bitbucket 上找到。

第一步是建立一個新的 OTP 應用程式,其中將包含該插件

→ rebar3 new plugin todo desc="example rebar3 plugin"
...
→ cd todo
→ git init
Initialized empty Git repository in /Users/ferd/code/self/todo/.git/

src/todo.erl 檔案將用於調用所有命令的初始化。目前,我們只有一個 todo 命令。打開包含命令實現的 src/todo_prv.erl 檔案,並確保您已準備好以下框架

-module(todo_prv).
-behaviour(provider).

-export([init/1, do/1, format_error/1]).

-define(PROVIDER, todo).
-define(DEPS, [app_discovery]).

%% ===================================================================
%% Public API
%% ===================================================================
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
    Provider = providers:create([
            {name, ?PROVIDER},          % The 'user friendly' name of the task
            {module, ?MODULE},          % The module implementation of the task
            {bare, true},               % The task can be run by the user, always true
            {deps, ?DEPS},              % The list of dependencies
            {example, "rebar provider_todo"}, % How to use the plugin
            {opts, []}                  % list of options understood by the plugin
            {short_desc, "example rebar3 plugin"},
            {desc, ""}
    ]),
    {ok, rebar_state:add_provider(State, Provider)}.


-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
    {ok, State}.

-spec format_error(any()) -> iolist().
format_error(Reason) ->
    io_lib:format("~p", [Reason]).

這顯示了所需的所有基本內容。請注意,我們將 DEPS 巨集保留為值 app_discovery,用於表示插件至少應找到項目的原始程式碼(不包括依賴項)。

在這種情況下,我們需要在 init/1 中進行很少的更改。以下是新的提供者描述

Provider = providers:create([
            {name, ?PROVIDER},       % The 'user friendly' name of the task
            {module, ?MODULE},       % The module implementation of the task
            {bare, true},            % The task can be run by the user, always true
            {deps, ?DEPS},           % The list of dependencies
            {example, "rebar todo"}, % How to use the plugin
            {opts, []},              % list of options understood by the plugin
            {short_desc, "Reports TODOs in source code"},
            {desc, "Scans top-level application source and find "
                   "instances of TODO: in commented out content "
                   "to report it to the user."}
    ]),

相反,大部分工作需要直接在 do/1 中完成。我們將使用 rebar_state 模組來取得我們需要的所有應用程式。這可以通過調用 project_apps/1 函數來完成,該函數返回項目的頂級應用程式列表。

do(State) ->
    lists:foreach(fun check_todo_app/1, rebar_state:project_apps(State)),
    {ok, State}.

這在高層次上意味著我們將一次檢查一個頂級應用程式(在使用版本時,通常可能有多個頂級應用程式)

其餘的是特定於插件的填充程式碼,負責讀取每個應用程式路徑,進入其中讀取程式碼,並在程式碼註釋中尋找「TODO:」的實例

check_todo_app(App) ->
    Paths = rebar_dir:src_dirs(rebar_app_info:opts(App)),
    Mods = find_source_files(Paths),
    case lists:foldl(fun check_todo_mod/2, [], Mods) of
        [] -> ok;
        Instances -> display_todos(rebar_app_info:name(App), Instances)
    end.
find_source_files(Paths) ->
    find_source_files(Paths, []).

find_source_files([], Files) ->
    Files;
find_source_files([Path | Rest], Files) ->
    find_source_files(Rest, [filename:join(Path, Mod) || Mod <- filelib:wildcard("*.erl", Path)] ++ Files).

check_todo_mod(ModPath, Matches) ->
    {ok, Bin} = file:read_file(ModPath),
    case find_todo_lines(Bin) of
        [] -> Matches;
        Lines -> [{ModPath, Lines} | Matches]
    end.

find_todo_lines(File) ->
    case re:run(File, "%+.*(TODO:.*)", [{capture, all_but_first, binary}, global, caseless]) of
        {match, DeepBins} -> lists:flatten(DeepBins);
        nomatch -> []
    end.

display_todos(_, []) -> ok;
display_todos(App, FileMatches) ->
    io:format("Application ~s~n",[App]),
    [begin
      io:format("\t~s~n",[Mod]),
      [io:format("\t  ~s~n",[TODO]) || TODO <- TODOs]
     end || {Mod, TODOs} <- FileMatches],
    ok.

只需使用 io:format/2 輸出即可。

要測試插件,請將其推送到某處的原始程式碼儲存庫。選擇您的其中一個項目,並在 rebar.config 中添加一些內容

{plugins, [
  {todo, {git, "git@bitbucket.org:ferd/rebar3-todo-plugin.git", {branch, "master"}}}
]}.

然後您可以直接調用它


→ rebar3 todo

===> Fetching todo

===> Compiling todo

Application merklet

    /Users/ferd/code/self/merklet/src/merklet.erl

      todo: consider endianness for absolute portability

Rebar3 將下載並安裝插件,並確定何時運行它。編譯後,可以隨時再次運行它。

選擇性搜尋相依性

讓我們稍微擴展一下。也許我們希望不時(在剪切版本時)確保我們的依賴項都不包含「TODO:」。

為此,我們需要解析一些命令列參數,並更改我們的執行模型。?DEPS 巨集現在需要指定 todo 提供者只能在安裝依賴項*之後*運行

-define(DEPS, [install_deps]).

我們可以將該選項添加到我們用於在 init/1 中配置提供者的列表中

{opts, [                 % list of options understood by the plugin
    {deps, $d, "deps", undefined, "also run against dependencies"}
]},

然後我們可以實現開關來確定要搜尋的內容

do(State) ->
    Apps = case discovery_type(State) of
        project -> rebar_state:project_apps(State);
        deps -> rebar_state:project_apps(State) ++ lists:usort(rebar_state:all_deps(State))
    end,
    lists:foreach(fun check_todo_app/1, Apps),
    {ok, State}.

[...]

discovery_type(State) ->
    {Args, _} = rebar_state:command_parsed_args(State),
    case proplists:get_value(deps, Args) of
        undefined -> project;
        _ -> deps
    end.

使用 rebar_state:command_parsed_args(State) 可以找到 deps 選項,該函數將返回「todo」之後命令列上的術語屬性列表,並將負責驗證是否接受標誌。其餘部分可以保持不變。

推送插件的新程式碼,然後在具有依賴項的項目上再次嘗試


===> Fetching todo

===> Compiling todo

===> Fetching bootstrap

===> Fetching file_monitor

===> Fetching recon

[...]

Application dirmon

    /Users/ferd/code/self/figsync/apps/dirmon/src/dirmon_tracker.erl

      TODO: Peeranha should expose the UUID from a node.

Application meck

    /Users/ferd/code/self/figsync/_deps/meck/src/meck_proc.erl

      TODO: What to do here?

      TODO: What to do here?

Rebar3 現在將在運行插件之前先選取依賴項。

您還可以 看到說明將會自動完成

→ rebar3 help todo

Scans top-level application source and find instances of TODO: in commented out content to report it to the user.

Usage: rebar todo [-d]

好了,todo 插件現在完成了!它已準備好發布並包含在其他儲存庫中。

新增更多指令

要將更多命令添加到同一個插件,只需將項目添加到主模組中 init 函數的項目即可

-module(todo).

-export([init/1]).

-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
    %% initialize all commands here
    {ok, State1} = todo_prv:init(State),
    {ok, State2} = todo_other_prv:init(State1),
    {ok, State2}.

Rebar3 將從那裡提取它。

上次修改時間:2024 年 3 月 10 日:shell hooks env vars (ba9462f)