因為 Erlang/OTP 釋出中要運送的每個元件都必須是 OTP App,理解這些元件是什麼以及如何運作對你將大有幫助。在本章中,我們將討論 OTP App 的基本架構,以及對專案的意義。
專案架構
我們從 Rebar3 範本開始,因為它們能讓我們建立符合 Erlang/OTP 預期目錄結構的全新專案。讓我們看看有哪些範本可用
$ rebar3 new
app (built-in): Complete OTP Application structure.
cmake (built-in): Standalone Makefile for building C/C++ in c_src
escript (built-in): Complete escriptized application structure
lib (built-in): Complete OTP Library application (no processes) structure
plugin (built-in): Rebar3 plugin project structure
release (built-in): OTP Release structure for executable programs
umbrella (built-in): OTP structure for executable programs
(alias of 'release' template)
以下表格顯示各種範本可能的用途
專案類型 | 使用的範本 | 註解 |
---|---|---|
指令碼或命令列工具 | escript | 需要使用者安裝 Erlang |
函式庫(模組集合) | lib | 可以當作相依性使用 |
函式庫(有狀態程序) | app | 可以當作相依性使用 |
完整的可執行程式 | umbrella 或 app | 可以轉換成完整版本,建議的部署機制 |
多個函式庫的集合 | umbrella | 雖然無法當作 git 相依性,但各個 app 還是可以發佈到 hex |
Rebar3 擴充 | plugin | |
編譯 C 程式碼 | cmake | 另外,也可以參閱「pc」外掛程式,以獲得一種編譯 C/C++ 的可攜式方法 |
你可以透過呼叫 rebar3 new help <template>
來查看特定範本的詳細資訊。例如
$ rebar3 new help lib
lib:
built-in template
Description: Complete OTP Library application (no processes) structure
Variables:
name="mylib" (Name of the OTP library application)
desc="An OTP library" (Short description of the app)
date="2019-03-15"
datetime="2019-03-15T19:52:31+00:00"
author_name="Fred Hebert"
author_email="[email protected]"
copyright_year="2019"
apps_dir="apps" (Directory where applications will be created if needed)
可以在命令列修改這些值,但這些是預設變數。讓我們看看自己撰寫程式碼會得到什麼
$ rebar3 new lib mylib desc="Checking out OTP libs"
===> Writing mylib/src/mylib.erl
===> Writing mylib/src/mylib.app.src
===> Writing mylib/rebar.config
===> Writing mylib/.gitignore
===> Writing mylib/LICENSE
===> Writing mylib/README.md
前往 mylib
目錄,並立即呼叫 rebar3 compile
$ rebar3 compile
===> Verifying dependencies...
===> Compiling mylib
查看目錄結構,專案中現在應該有類似以下內容
mylib/
├─ _build/
│ └─ default/
│ └─ lib/
│ └─ mylib/
│ ├─ ebin/
│ │ ├─ mylib.app
│ │ └─ mylib.beam
│ ├─ include/
│ ├─ priv/
│ └─ src/
│ └─ ...
├─ .gitignore
├─ LICENSE
├─ README.md
├─ rebar.config
├─ rebar.lock
└─ src/
├─ mylib.app.src
└─ mylib.erl
_build/
目錄是建置工具的遊戲場,可以在這裡儲存所有需要的工件。你永遠不應該手動觸碰裡面的內容,但可以隨時刪除。不過,這個目錄很有趣,因為它展示了 Rebar3 如何建構資料。
_build/
中的所有內容都依據 設定檔進行分割,讓 Rebar3 可以使用不同的設定(包含不同的相依性和編譯器選項)來建置不同內容,無論是用在 default
、test
或 prod
設定檔中,事實上你可以定義任意數量的設定檔,並將它們組合在一起。Rebar3 文件說明了這項功能的運作方式。
在每個剖面中,lib/
目錄包含專案可能會使用的所有 OTP 應用程式,位於標準版本的函式庫之外。您可以在那裡看到我們的 mylib
函式庫被複製,但其目錄結構與專案根目錄下的結構有些不同
- 已編譯的
.erl
檔案會移至ebin/
目錄,並已改為.beam
附檔名 - 已建立
mylib.app
檔案,而原始應用程式則有mylib.app.src
- 已新增兩個符號連結到
include/
和priv/
。如果專案根目錄中有符合的目錄,這些符號連結會連結到這些目錄。include/
目錄是針對 標頭檔案 (.hrl
) 用意,而priv/
目錄則針對必須複製並在生產環境中供應的任何檔案用意 - 專案根目錄中的所有其他檔案都已捨棄
如果我們有任何相依關係(請參閱 相依關係章節),它們也會置於 _build/<profile>/lib/
目錄中。
一般而言,您會完全忽略 _build/
目錄並避免在來源控制中追蹤它:如果您查看 .gitignore
檔案,您會看到它會自動為您忽略 _build/
。
依預設,Rebar3 會幫您選擇一個授權(因為您在計畫執行開源工作時,應該總是選擇一個授權),使用 Erlang 附帶的 Apache 2.0 授權。您可以根據需要加以替換。Rebar3 也會設定 README
檔案,您可能會想修正並更新此檔案,加入所有相關內容。別當個混蛋,寫下文件吧!
接著,我們會看到兩個有趣的檔案,rebar.config
和 rebar.lock
。Rebar3 會使用鎖定檔案來追蹤您專案中所使用相依關係的版本,因此應提交至來源控制。 相依關係章節 包含更多詳細資訊。
rebar.config
檔案是一個完整的宣告設定檔,它會揭露 Rebar3 與其整合的所有 Erlang 工具選項。 官方文件 說明所有可用的值,但預設值相當空。事實上,如果您只想要預設值且沒有相依關係,您可以刪除檔案即可。只要您的專案架構有如 OTP 應用程式,Rebar3 就能了解需要執行哪些工作。
我們來看看到底有那些標準讓此情況得以發生。
是什麼讓函式庫成為應用程式?
與其他任何架構一樣,您必須執行某些工作才能符合其預期。您可能已經猜到了,但目錄結構是 OTP 這樣架構的基本需求之一。只要您的函式庫在編譯後具有 ebin/
目錄,且其中有 <appname>.app
檔案,Erlang 執行時期系統就能載入您的模組並執行您的程式碼。
此基本需求引導了整個 Erlang 生態系統的專案結構。讓我們看看建立的 .app
檔案看起來像什麼
$ cat _build/default/lib/mylib/ebin/mylib.app
{application, mylib, [
{description, "Checking out OTP libs"},
{vsn, "0.1.0"}, % version number (string)
{registered, []}, % name of registered processes, if any
{applications, [ % List of OTP application names on which
kernel, stdlib % yours depends at run-time. kernel and
]}, % stdlib are ALWAYS needed
{env, []}, % default configuration values ({Key, Val} pairs)
{modules, [mylib]}, % list of all the modules in the application
%% content below is optional, and for package publication only
{licenses, ["Apache 2.0"]},
{links, []} % relevant URLs
]}.
這在基本上是一個描述應用程式所有事項的元資料檔案。我們已花時間為您加註解,因此您可以查看一下。許多內容以人手寫作都令人感到厭煩,因此如果您查看原始程式碼檔案 (src/mylib.app.src
),您會發現當您運用 Rebar3 範本時,這些欄位大多都是預先填寫的。您也可能會注意到 modules
是空的。這是故意的:Rebar3 會在編譯您的程式碼時幫您填入清單。
到目前為止,最關鍵的欄位是 applications
組合。它讓 Erlang 函式庫知悉 OTP 應用程式啟動工作的順序,也讓建置工具能夠在所有可用的 OTP 應用程式之間建立相依圖,以了解在建立發行版本時,哪些應用程式應予保留,哪些應用程式應予移除。
一個較難察覺的地方是,儘管這裡我們有一個 函式庫,因此不具有任何要執行的程序,但我們仍然有能力定義一些組態值(還沒有寫到組態章節中),而且必須遵守相依關係。以本範例來說,我們的函式庫可能是無狀態的,但使用了一個有狀態的 HTTP 客戶端,那麼 Erlang VM 就需要知道您的程式碼在什麼時候能呼叫、什麼時候不能呼叫。
現在讓我們先專注於無狀態與有狀態應用程式之間的具體差異。
使可執行應用程式成為應用程式的條件
為了建立一個可執行應用程式,我們將在 Rebar3 中使用「app」範本,並看看其與無狀態應用程式的差異為何。
讓我們取得您的指令列工具並執行下列事項
$ rebar3 new app myapp
===> Writing myapp/src/myapp_app.erl
===> Writing myapp/src/myapp_sup.erl
===> Writing myapp/src/myapp.app.src
===> Writing myapp/rebar.config
===> Writing myapp/.gitignore
===> Writing myapp/LICENSE
===> Writing myapp/README.md
$ cd myapp
如果您小心謹慎,您會看到現在我們有了兩個模組而非 <appname>.erl
:我們有 <appname>_app.erl
與 <appname>_sup.erl
。我們很快就會深入研究它們,但首先,我們先來關注應用程式的頂層元資料檔案:myapp.app.src
檔案
$ cat src/myapp.app.src
{application, myapp,
[{description, "An OTP application"},
{vsn, "0.1.0"},
{registered, []},
{mod, {myapp_app, []}}, % this is new!
{applications, [kernel, stdlib]},
{env,[]},
{modules, []},
{licenses, ["Apache 2.0"]},
{links, []}
]}.
這裡唯一新增的列為 {mod, {<appname>_app, []}}
組合。此組合會指定一個可以使用 (<appname>_app
) 搭配一些特定引數 ([]
) 來呼叫的特殊模組。預期這個模組在呼叫後會傳回 程序識別碼 (亦即 pid) 的 監督行程。
如果您前往查看 myapp_app
模組,您會看到這些回呼是什麼
%%%-------------------------------------------------------------------
%% @doc myapp public API
%% @end
%%%-------------------------------------------------------------------
-module(myapp_app).
-behaviour(application).
%% Application callbacks
-export([start/2, stop/1]).
%%====================================================================
%% API
%%====================================================================
start(_StartType, _StartArgs) ->
myapp_sup:start_link().
stop(_State) ->
ok.
在應用程式開機時,Erlang 執行時期系統會呼叫 start/2
回呼,這時它所有的相依性 (<tt>applications<\\tt> 屬性組中的 .app 檔案) 已經全部啟動完畢。你可以在此處執行一次初始化。在範本程式中,唯一執行的動作便是為這個應用程式啟動根層監控程式。
當所有監督樹在有人、某地在決定關閉 OTP 應用程式後被移除 stop/1
回呼將會被呼叫,時間會是在 之後。
但總之,app 檔案中這個額外的 mod
行和監督結構的存在,便是可執行應用程式與函式庫應用程式的差別。
提示
如果你不想關心監督樹,也只想要能夠像在其他大部分程式語言一樣得到一個 main()
函式,escript
可能會是個不錯的選擇。
escript
是個特殊的 C 程式,可包圍 Erlang 虛擬機器。在包圍它的同時,它也會引進一個小的界面層,透過呼叫你的程式至虛擬機器的根層 Erlang 程序,來改造 main()
函式的概念至發佈結構。
結果就是你可以執行直譯的程式,而不必煩惱發佈、OTP 應用程式或監督樹。你可以在 官方 Erlang 說明文件 中閱讀有關 escript 的更多資訊。Rebar3 也有 指令可建立複雜的 escript 軟體包。
現在你已經了解有關 Erlang/OTP 專案結構中的大部分奇異之處,以及與這些神秘的「OTP 應用程式」有關的一切。從下一章開始,我們將開始稍微討論一下監督樹,以便你瞭解如何設定有狀態可執行應用程式。