OTP App

OTP App

2019 年 8 月 8 日

因為 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 可以使用不同的設定(包含不同的相依性和編譯器選項)來建置不同內容,無論是用在 defaulttestprod 設定檔中,事實上你可以定義任意數量的設定檔,並將它們組合在一起。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.configrebar.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 應用程式」有關的一切。從下一章開始,我們將開始稍微討論一下監督樹,以便你瞭解如何設定有狀態可執行應用程式。