建置插件
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
是一個類型(atom
、binary
、boolean
、float
、integer
或string
)、具有預設值的類型 ({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/1
和 all_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
。
在其他方面,命名空間的作用類似於 do
(rebar3 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 將從那裡提取它。