發布才是王道

我是一個可執行檔了嗎?

我們進展到哪裡了。這麼多工作,這麼多概念,而我們還沒有發布任何 Erlang 可執行檔。您可能會同意我的看法,要讓 Erlang 系統啟動並運行需要很多努力,特別是與許多只需調用編譯器即可開始的語言相比。

A slice of pizza

當然,這完全正確。我們可以編譯檔案、執行應用程式、檢查一些相依性、處理崩潰等等,但是如果沒有一個您可以輕鬆部署或隨附的功能性 Erlang 系統,就沒有什麼用處。如果美味的披薩只能冷著送來,那還有什麼用呢?(喜歡冷披薩的人可能會覺得被排除在外。我很抱歉。)

在確保實際系統實現方面,OTP 團隊並沒有讓我們孤軍奮戰。OTP 發布是旨在幫助將應用程式與最少的資源和相依性打包在一起的系統的一部分。

修復洩漏的管道

對於我們的第一個發布版本,我們將重複使用上一章的 ppoolerlcount 應用程式。但是,在我們這樣做之前,我們需要在這裡和那裡更改一些東西。如果您正在跟著本書撰寫自己的程式碼,您可能想將我們的兩個應用程式複製到一個名為 release/ 的新目錄中,我將假設您在本章的其餘部分都這樣做了。

A leaky pipe with brown liquid dripping into a metal bucket

erlcount 真正困擾我的第一件事是,一旦它運行完成,VM 就會保持開啟狀態,什麼也不做。我們可能希望大多數應用程式永遠保持運行,但這次並非如此。保持它運行是有道理的,因為我們可能想在 shell 中玩一些東西,並且需要手動啟動應用程式,但這應該不再必要了。

因此,我們將新增一個命令,以有序的方式關閉 BEAM 虛擬機。最好的方法是在 erlcount_dispatch.erl 的 terminate 函數中進行,因為它是在我們取得結果後被調用的。關閉一切的完美函數是 init:stop/0。此函數相當複雜,但會負責按順序終止我們的應用程式,並為我們處理檔案描述符、socket 等。新的停止函數現在應該看起來像這樣

terminate(_Reason, _State, _Data) ->
    init:stop().

這就是程式碼本身的全部內容。我們還有一些工作要做。當我們在最後兩章定義應用程式檔案時,我們在做的同時使用了讓它們運行的絕對最少量資訊。需要更多欄位,這樣 Erlang 才不會對我們完全發瘋。

首先,用於建置發布版本的 Erlang 工具要求我們在應用程式描述中更加精確。您看,雖然發布版本的工具不了解文件,但它們仍然對開發人員太粗魯而至少沒有留下應用程式作用的想法的程式碼,有著這種直觀的恐懼。因此,我們需要在 ppool.apperlcount.app 檔案中都新增一個 description 元組。

對於 ppool,新增以下一個

{description, "Run and enqueue different concurrent tasks"}

對於 erlcount

{description, "Run regular expressions on Erlang source files"}

現在,當我們檢查不同的系統時,我們將能夠更好地了解正在發生的事情。

最專心的讀者也會記得我曾經提到過所有應用程式都依賴於 stdlibkernel。但是,我們的兩個應用程式檔案都沒有提及任何這些。讓我們將這兩個應用程式新增到我們的每個應用程式檔案中。這將需要在 ppool 應用程式檔案中新增以下元組

{applications, [stdlib, kernel]}

並將這兩個應用程式新增到現有的 erlcount 應用程式檔案中,給我們 {applications, [stdlib, kernel, ppool]}

別喝太多酷愛飲料
雖然當我們手動啟動發布版本時(甚至當我們使用 systools 生成它們時,我們很快就會看到),這可能幾乎沒有任何影響,但將這兩個程式庫新增到清單中絕對至關重要。

使用 reltool(我們將在本章中看到的另一個工具)生成發布版本的人絕對需要這些應用程式,才能讓他們的發布版本順利運行,甚至能夠以體面的方式關閉 VM。我不是在開玩笑,這是必須的。當我寫這一章時,我忘記這樣做,並浪費了一個晚上的工作時間,試圖找出當我一開始就沒有做對事情時,到底出了什麼問題。

可以說,理想情況下,Erlang 的發布系統可以隱式新增這些應用程式,因為幾乎所有應用程式(非常特殊的情況除外)都將依賴它們。唉,它們不會。我們必須這樣做。

我們已經有了終止機制,並且更新了應用程式檔案等等。在我們開始使用發布版本之前,最後一步是編譯所有應用程式。在每個包含應用程式的目錄中,依次執行您的 Emakefile(使用 erl -make)。否則,Erlang 的工具不會為您執行此操作,您最終會得到一個沒有程式碼可執行的發布版本。哎呦。

使用 Systools 發布版本

systools 應用程式是用來建置 Erlang 發布版本的最簡單的應用程式。它是 Erlang 發布版本的簡易烤箱®。要從 systools 烤箱中取出美味的發布版本,您首先需要一個基本的食譜和配料清單。如果我要手動描述我們 erlcount 應用程式成功的最簡 Erlang 發布版本的配料,它看起來會有點像這樣

erlcount 1.0.0 的配料
  • 您選擇的 Erlang 執行時系統 (ERTS)。
  • 標準程式庫
  • 核心程式庫
  • 不應該失敗的 ppool 應用程式
  • erlcount 應用程式。

我有沒有說我是一個糟糕的廚師?我不確定我是否能煎鬆餅,但至少我知道如何建置 OTP 發布版本。使用 systools 的 OTP 發布版本的配料清單看起來像這個檔案,名為 erlcount-1.0.rel 並放置在 release/ 目錄的最上層

{release,
 {"erlcount", "1.0.0"},
 {erts, "5.8.4"},
 [{kernel, "2.14.4"},
  {stdlib, "1.17.4"},
  {ppool, "1.0.0", permanent},
  {erlcount, "1.0.0", transient}]}.

這只是告訴您與我的手動食譜相同的內容,儘管我們可以指定我們希望應用程式如何啟動(temporarytransientpermanent)。我們還可以指定版本,以便我們可以根據我們的需要混合和匹配來自不同 Erlang 版本的不同程式庫。要在其中取得所有版本號,您只需執行以下一系列調用

$ erl
Erlang R14B03 (erts-5.8.4) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.4  (abort with ^G)
1> application:which_applications().
[{stdlib,"ERTS  CXC 138 10","1.17.4"},
 {kernel,"ERTS  CXC 138 10","2.14.4"}]

因此,對於那一個,我正在運行 R14B03。您可以在發布版本號之後看到其中的 ERTS 版本(版本為 5.8.4)。然後,通過在運行的系統上調用 application:which_applications(),我可以看到我從 kernel (2.14.4) 和 stdlib (1.17.4) 需要的兩個版本。這些數字會因 Erlang 版本而異。但是,明確指出您需要的版本會很有幫助,因為這表示如果您安裝了許多不同的 Erlang,您可能仍然只想要一個較舊版本的 stdlib,它不會對您正在執行的任何操作產生不良影響。

A chocolate cupcake with pink creamy topping in a purplish paper, with a face, beard, legs and high heel shoes (pink)

您還會注意到我選擇將發布版本命名為 erlcount 並使其版本為 1.0.0。這與 ppoolerlcount 應用程式無關,後者也都在執行版本 1.0.0,如其應用程式檔案中所指定。

所以現在我們已經編譯了所有應用程式、配料清單和簡易烤箱® 的美妙概念。我們需要的是實際的食譜。

現在將會出現一些概念。食譜會告訴您一些事情:以什麼順序新增配料、如何混合它們、如何烹飪它們等等。新增配料的順序部分由每個應用程式檔案中的相依性清單涵蓋。systools 應用程式將足夠聰明地查看應用程式檔案,並找出需要在什麼之前運行什麼。

Erlang 的虛擬機器可以從稱為啟動檔案的內容中取得基本配置來啟動自身。事實上,當您從 shell 啟動自己的 erl 應用程式時,它會隱式地調用具有預設啟動檔案的 Erlang 執行時系統。該啟動檔案將提供基本指令,例如「載入標準程式庫」、「載入核心應用程式」、「運行給定的函數」等等。該啟動檔案是一個二進位檔案,由稱為 啟動腳本 的內容建立,其中包含代表這些指令的元組。我們將了解如何編寫這樣的啟動腳本。

首先我們從

{script, {Name, Vsn},
 [
  {progress, loading},
  {preLoaded, [Mod1, Mod2, ...]},
  {path, [Dir1,"$ROOT/Dir",...]}.
  {primLoad, [Mod1, Mod2, ...]},
  ...

開玩笑的。沒有人會真的花時間這樣做,我們也不會。啟動腳本是很容易從 .rel 檔案生成的東西。只需從 release/ 目錄啟動 Erlang VM 並調用以下行

$ erl -env ERL_LIBS .
...
1> systools:make_script("erlcount-1.0", [local]).
ok

現在,如果您查看您的目錄,您將擁有一堆新檔案,包括 erlcount-1.0.scripterlcount-1.0.boot 檔案。在這裡,local 選項表示我們希望發布版本可以在任何地方運行,而不僅僅是目前的安裝。還有更多選項要查看,但是因為 systools 不像 reltool(在下一節中)那樣強大,我們不會過多地研究它們。

無論如何,我們有了啟動腳本,但還不足以分發我們的程式碼。回到您的 Erlang shell 並執行以下命令

2> systools:make_tar("erlcount-1.0", [{erts, "/usr/local/lib/erlang/"}]).
ok

或者,在 Windows 7 上

2> systools:make_tar("erlcount-1.0", [{erts, "C:/Program Files (x86)/erl5.8.4"}]).
ok

在這裡,systools 會尋找您的發布檔案和 Erlang 執行時系統(因為有 erts 選項)。如果您省略 erts 選項,發布版本將無法自行執行,並且將取決於系統中是否已安裝 Erlang。

執行上述函數調用會建立一個名為 erlcount-1.0.tar.gz 的封存檔。解壓縮其中的檔案,您應該會看到一個像這樣的目錄

erts-5.8.4/
lib/
releases/

erts-5.8.4/ 目錄將包含執行時系統。lib/ 目錄包含我們需要的所有應用程式,而 releases 具有啟動檔案等等。

移動到您提取這些檔案的目錄中。從那裡,我們可以為 erl 建置命令列調用。首先,我們指定在哪裡找到 erl 可執行檔和啟動檔案(沒有 .boot 副檔名)。在 Linux 中,這會給我們

$ ./erts-5.8.4/bin/erl -boot releases/1.0.0/start

在 Windows 7 上,使用 Windows PowerShell 的命令與我相同。

別喝太多酷愛飲料
無法保證發佈版本在任何系統上都能正常運作。如果您使用的是純 Erlang 程式碼,那麼該程式碼將具有可移植性。問題在於,您隨附的 ERTS 本身可能無法運作:您要么需要為許多不同平台建立許多二進制套件,以便大規模定義,要么直接發佈 BEAM 檔案,而不包含相關的 ERTS,並請使用者使用他們自己電腦上的 Erlang 系統來執行它們。

如果您希望命令在電腦上的任何位置都能運作,您可以選擇使用絕對路徑。不過,現在先不要執行它。這將毫無用處,因為目前目錄中沒有要分析的原始程式檔。如果您使用絕對路徑,您可以前往要分析的目錄,然後從那裡呼叫該檔案。如果您使用相對路徑(就像我一樣),並且如果您還記得我們的 erlcount 應用程式,我們使其可以設定程式碼將掃描的目錄。讓我們將 -erlcount directory "'<目錄路徑>'" 新增至命令中。然後,因為我們不希望它看起來像 Erlang,讓我們新增 -noshell 參數。這讓我在我的電腦上得到類似這樣的結果

$ ./erts-5.8.4/bin/erl -boot releases/1.0.0/start -erlcount directory '"/home/ferd/code/otp_src_R14B03/"' -noshell
Regex if\s.+-> has 3846 results
Regex case\s.+\sof has 55894 results

使用絕對檔案路徑,我會得到類似這樣的結果

$ /home/ferd/code/learn-you-some-erlang/release/rel/erts-5.8.4/bin/erl -boot /home/ferd/code/learn-you-some-erlang/release/rel/releases/1.0.0/start -noshell

無論我從哪裡執行它,那都是將要掃描的目錄。將其包裝在 Shell 指令碼或批次檔案中,您就應該可以開始使用了。

使用 Reltool 的發佈版本

systools 有很多惱人的地方。我們對事情的完成方式幾乎沒有控制權,坦白說,像那樣執行事情有點煩人。手動指定啟動檔案的路徑等等有點痛苦。此外,檔案有點大。整個發佈版本在磁碟上佔用超過 20MB 的空間,如果我們打包更多應用程式,情況會更糟。使用 reltool 可以做得更好,因為我們可以獲得更多的權力,儘管代價是複雜性增加。

Reltool 從一個看起來像這樣的設定檔運作

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "erlcount", "1.0.0",
     [kernel,
      stdlib,
      {ppool, permanent},
      {erlcount, transient}
     ]},
    {boot_rel, "erlcount"},
    {relocatable, true},
    {profile, standalone},
    {app, ppool, [{vsn, "1.0.0"},
                  {app_file, all},
                  {debug_info, keep}]},
    {app, erlcount, [{vsn, "1.0.0"},
                     {incl_cond, include},
                     {app_file, strip},
                     {debug_info, strip}]}
]}.

瞧,Erlang 的使用者友善性!老實說,沒有簡單的方法可以介紹 Reltool 給自己。您需要同時使用一堆選項,否則任何東西都無法運作。這聽起來可能令人困惑,但背後是有邏輯的。

首先,Reltool 將採用不同層級的資訊。第一層將包含發佈版本範圍的資訊。第二層將是特定於應用程式的,然後允許在模組特定層級進行細微的控制

The levels of reltools: the release levels contains environment, applications and properties of the releases. The level under that, Applications, contains what to include, compression, debug_info, app files, etc. The last (and lowest) level, the Modules, contains what to include and debug_info

對於每個層級,如上圖所示,將可以使用不同的選項。我們不會採用百科全書式的方法介紹所有可能的選項,而是會介紹一些基本選項,然後根據您在應用程式中尋找的內容介紹一些可能的設定。

第一個選項可以幫助我們擺脫有點煩人的需求,即必須位於給定的目錄中或將正確的 -env 參數設定給 VM。該選項是 lib_dirs,它採用應用程式所在目錄的列表。因此,實際上,您不需要新增 -env ERL_LIBS <目錄列表>,而是放入 {lib_dirs, [目錄列表]},您會得到相同的結果。

Reltool 設定檔的另一個重要選項是 rel。這個元組與我們為 systools 撰寫的 .rel 檔案非常相似。在上面的範例檔案中,我們有

{rel, "erlcount", "1.0.0",
 [kernel,
  stdlib,
  {ppool, permanent},
  {erlcount, transient}
 ]},

這就是我們需要告訴我們哪些應用程式需要正確啟動的資訊。在該元組之後,我們要新增一個形式為

{boot_rel, "erlcount"}

這將告訴 Reltool,無論何時有人執行發佈版本中包含的 erl 二進制檔案,我們都希望啟動 erlcount 發佈版本中的應用程式。僅使用這 3 個選項 (lib_dirsrelboot_rel),我們就可以獲得有效的發佈版本。

為此,我們將這些元組放入 Reltool 可以剖析的格式中

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "erlcount", "1.0.0",
     [kernel,
      stdlib,
      {ppool, permanent},
      {erlcount, transient}
     ]},
    {boot_rel, "erlcount"}
]}.

是的,我們只是將它們包裝在 {sys, [選項]} 元組中。在我的情況下,我將其儲存在 release/ 目錄中名為 erlcount-1.0.config 的檔案中。您可以將其放在您想要的任何地方(/dev/null 除外,即使它具有出色的寫入速度!)

然後,我們需要開啟一個 Erlang Shell

1> {ok, Conf} = file:consult("erlcount-1.0.config").
{ok,[{sys,[{lib_dirs,["/home/ferd/code/learn-you-some-erlang/release/"]},
           {rel,"erlcount","1.0.0",
                [kernel,stdlib,{ppool,permanent},{erlcount,transient}]},
           {boot_rel,"erlcount"}]}]}
2> {ok, Spec} = reltool:get_target_spec(Conf).
{ok,[{create_dir,"releases",
   ...
3> reltool:eval_target_spec(Spec, code:root_dir(), "rel").
ok

這裡的第一步是讀取設定並將其繫結到 Conf 變數。然後,我們將其傳送到 reltool:get_target_spec(Conf)。該函式將執行一段時間,並傳回過多的資訊讓我們無法繼續。我們不在乎,只是將結果儲存在 Spec 中。

第三個命令會採用規格,並告訴 Reltool「我希望您採用我的發佈版本規格,使用我的 Erlang 安裝所在的任何路徑,並將其放入「rel」目錄中」。就是這樣。查看 rel 目錄,您應該會看到那裡有一些子目錄。

現在我們不在乎,可以直接呼叫

$ ./bin/erl -noshell
Regex if\s.+-> has 0 results
Regex case\s.+\sof has 0 results

啊,執行起來簡單一點。您可以將這些檔案放在任何地方,只要它們保持相同檔案樹,並從您想要的任何地方執行它們即可。

a squid's tentacle being cut off so it could free itself from a pair of handcuffs

您有沒有注意到有什麼不同?我希望您有。我們不需要指定任何版本號碼。Reltool 在這方面比 Systools 更聰明一點。如果您沒有指定版本,它會自動尋找您路徑中可能最新的版本(在 code:root_dir() 傳回的目錄中,或在您放入 lib_dirs 元組中的內容中)。

但是,如果我不時髦、不酷炫、不趕潮流,而且不喜歡最新的應用程式,而是喜歡復古呢?我還穿著我的迪斯可舞褲在這裡,而且我想使用舊版的 ERTS 版本和舊版的程式庫版本,您看(我在 1977 年從未比現在更有活力!)

幸運的是,Reltool 可以處理需要與舊版 Erlang 搭配使用的發佈版本。尊重長者是 Erlang 工具的重要概念。

如果您安裝了舊版的 Erlang,您可以將 {erts, [{vsn, 版本}]} 項目新增至設定檔中

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {erts, [{vsn, "5.8.3"}]},
    {rel, "erlcount", "1.0.0",
     [kernel,
      stdlib,
      {ppool, permanent},
      {erlcount, transient}
     ]},
    {boot_rel, "erlcount"}
]}.

現在,您需要清除 rel/ 目錄,以擺脫較新的發佈版本。然後,您再次執行相當醜陋的呼叫序列

4> f(),
4> {ok, Conf} = file:consult("erlcount-1.0.config"),
4> {ok, Spec} = reltool:get_target_spec(Conf),
4> reltool:eval_target_spec(Spec, code:root_dir(), "rel").
ok

這裡快速提醒一下,f() 用於解除繫結 Shell 中的變數。現在,如果我前往 rel 目錄並呼叫 $ ./bin/erl,我會得到以下輸出

Erlang R14B02 (erts-5.8.3) [source] ...

Eshell V5.8.3  (abort with ^G)
1> Regex if\s.+-> has 0 results
Regex case\s.+\sof has 0 results

太棒了。這會在 5.8.3 版上執行,即使我有更新的版本可用。啊哈,哈哈,哈,保持活力。

注意:如果您查看 rel/ 目錄,您會發現它們與 Systools 的情況有點類似,但其中一個差異會在 lib/ 目錄中。該目錄現在將包含一堆目錄和 .ez 檔案。這些目錄將包含您想要開發時所需的 include/ 檔案,以及需要保留在那裡的檔案的 priv/ 目錄。.ez 檔案只是壓縮的 beam 檔案。Erlang VM 會在執行階段為您解壓縮它們,這只是為了讓事情更輕鬆。

但是,我的其他模組呢?

啊,現在我們從發佈版本範圍的設定移開,必須輸入與應用程式相關的設定。仍然有很多發佈版本範圍的選項需要查看,但我們正處於順風順水的狀態,我們不能要求現在停止。我們稍後會再回到這些選項。對於應用程式,我們可以透過新增更多元組來指定版本

{app, AppName, [{vsn, Version}]}

並為每個需要的應用程式放入一個。

現在,我們對所有內容都有更多選項。我們可以指定是否希望發佈版本包含偵錯資訊、將其去除、嘗試製作更精簡的應用程式檔案,或信任我們的定義、要包含或排除的內容、在包含您的應用程式可能依賴的應用程式和模組時的嚴格程度等等。此外,這些選項通常可以在發佈版本範圍和應用程式範圍內定義,因此您可以指定預設值,然後指定要覆寫的值。

以下是快速概述。如果您覺得它很複雜,請直接跳過它,您會看到一些可以遵循的食譜

僅限發佈版本的選項

{lib_dirs, [目錄列表]}
要在其中尋找程式庫的目錄。
{excl_lib, otp_root}
在 R15B02 中新增,此選項可讓您指定 OTP 應用程式作為發佈版本的一部分,而無需在最終發佈版本中包含來自標準 Erlang/OTP 路徑的任何內容。這可讓您建立基本上是可從給定系統中已安裝的現有虛擬機啟動的程式庫的發佈版本。使用此選項時,您現在必須將虛擬機啟動為 $ erl -boot_var RELTOOL_EXT_LIB <發佈版本目錄路徑>/lib -boot <啟動檔案路徑>。這將允許發佈版本使用目前的 Erlang/OTP 安裝,但使用您自己的自訂發佈版本的程式庫。
{app, AppName, [AppOptions]}
可讓您指定應用程式範圍的選項,通常比發佈版本範圍的選項更具體。
{boot_rel, ReleaseName}
要使用 erl 可執行檔啟動的預設發佈版本。這表示我們在呼叫 erl 時不需要指定啟動檔案。
{rel, Name, Vsn, [Apps]}
要包含在發佈版本中的應用程式。
{relocatable, true | false}
可以從任何地方執行發佈版本,也可以僅從系統中的硬式編碼路徑執行。預設情況下,它會設定為 true,而且我傾向於這樣保留,除非有充分的理由需要變更。當您需要它時,您就會知道。
{profile, development | embedded | standalone}
此選項將作為指定基於您的發佈版本類型的預設 *_filters(如下所述)的方法。預設情況下,會使用 development。它會盲目地包含每個應用程式和 ERTS 中的更多檔案。standalone 設定檔會更嚴格,而 embedded 設定檔會更加嚴格,會捨棄更多預設的 ERTS 應用程式和二進制檔案。

發佈版本和應用程式範圍的選項

請注意,對於所有這些選項,在應用程式層級設定選項只會覆寫您在系統層級給定的值。

{incl_sys_filters, [RegularExpressions]}
{excl_sys_filters, [RegularExpressions]}
檢查檔案是否符合包含篩選器,而不會符合排除篩選器,然後才將其包含在內。您可以透過這種方式捨棄或包含特定檔案。
{incl_app_filters, [RegularExpressions]}
{excl_app_filters, [RegularExpressions]}
incl_sys_filtersexcl_sys_filters 類似,但適用於特定於應用程式的檔案
{incl_archive_filters, [RegularExpressions]}
{excl_archive_filters, [RegularExpressions]}
指定必須將哪些頂層目錄包含或排除到 .ez 封存檔案中(稍後會詳細說明)。
{incl_cond, include | exclude | derived}
決定如何包含不一定在 rel 元組中指定的應用程式。選擇 include 表示 Reltool 將包含它可以找到的幾乎所有內容。選擇 derived 表示 Reltool 將只包含它偵測到可以在您的 rel 元組中的任何應用程式使用的應用程式。這是預設值。選擇 exclude 表示您預設將完全不包含任何應用程式。您通常會在想要最少包含時在發佈版本層級設定此選項,然後在應用程式的基礎上,覆寫要新增的內容的值。
{mod_cond, all | app | ebin | derived | none}
這控制了模組的包含策略。選擇 none 代表不保留任何模組。這沒什麼用。derived 選項代表 Reltool 會嘗試找出其他已包含的模組使用了哪些模組,並在這種情況下將它們加入。將選項設定為 app 將表示 Reltool 保留應用程式檔案中提到的所有模組以及衍生的模組。設定為 ebin 將保留 ebin/ 目錄中的模組以及衍生的模組。使用 all 選項將是使用 ebinapp 的混合。這是預設值。
{app_file, keep | strip | all}
當您包含應用程式時,此選項會管理應用程式檔案的管理方式。選擇 keep 將保證發行版本中使用的應用程式檔案與您為應用程式編寫的檔案相同。這是預設選項。如果您選擇 strip,Reltool 會嘗試產生一個新的應用程式檔案,該檔案會移除您不需要的模組(那些被過濾器和其他選項排除的模組)。選擇 all 將保留原始檔案,但也會將特別包含的模組加入其中。all 的好處是,如果沒有可用的應用程式檔案,它可以為您產生應用程式檔案。

模組特定的選項

{incl_cond, include | exclude | derived}
這讓您可以覆寫在發行版本層級和應用程式層級定義的 mod_cond 選項。

所有層級的選項

這些選項在所有層級都有效。層級越低,優先權越高。

{debug_info, keep | strip}
假設您的檔案在編譯時啟用了 debug_info(我建議您這樣做),此選項可讓您決定是否保留它或捨棄它。當您想要反編譯檔案、除錯它們等等時,debug_info 很有用,但會佔用一些空間。

資訊量很大

是的,這有很多資訊。我甚至沒有涵蓋所有可能的選項,但這仍然是一個不錯的參考。如果您想要完整的內容,請查看官方文件

A complex Rube Goldberg machine to represent the OTP Release process

訣竅

現在,我們將根據您想要獲得的內容提供一些一般性的提示和訣竅。

開發版本

獲取開發用的東西必須相對容易。通常預設值就足夠了。堅持使用我們之前看到的那些基本項目,應該就夠了。

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "erlcount", "1.0.0", [kernel, stdlib, ppool, erlcount]},
    {boot_rel, "erlcount"}
]}.

Reltool 會負責導入足夠的東西以確保正常運作。在某些情況下,您可能希望擁有來自一般虛擬機器的一切。您可能正在為團隊分發整個虛擬機器,其中包含一些程式庫。在這種情況下,您想要做的事情更像是這樣:

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "start_clean", "1.0.0", [kernel, stdlib]},
    {incl_cond, include},
    {debug_info, keep}
]}.

透過將 incl_cond 設定為 include,目前 ERTS 安裝和 lib_dirs 中找到的所有應用程式都將成為您的發行版本的一部分。

注意:當未指定 boot_rel 時,您必須有一個名為 start_clean 的發行版本,Reltool 才會正常運作。當您啟動相關的 erl 檔案時,預設會選擇該檔案。

如果我們想要排除特定的應用程式,例如 megaco,因為我從未研究過它,我們可以改為取得這樣的檔案:

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "start_clean", "1.0.0", [kernel, stdlib]},
    {incl_cond, include},
    {debug_info, keep},
    {app, megaco, [{incl_cond, exclude}]}
]}.

在這裡,我們可以指定一個或多個應用程式(每個應用程式都有自己的 app 元組),並且它們中的每一個都會覆寫在發行版本層級設定的 incl_cond。因此,在這種情況下,我們將包含除 megaco 以外的所有內容。

僅導入或匯出程式庫的一部分

在我們的發行版本中,發生了一個令人煩惱的事情,即使像 ppool 和其他應用程式不需要它們,它們仍然在發行版本中保留了測試檔案。您可以透過進入 rel/lib/ 並解壓縮 ppool-1.0.0.ez 來看到它們(您可能需要先更改副檔名)。

要擺脫這些檔案,最簡單的方法是指定排除篩選器,例如:

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "start_clean", "1.0.0", [kernel, stdlib, ppool, erlcount]},
    {excl_app_filters, ["_tests.beam$"]}
]}.

當您只想導入應用程式的特定檔案時,例如我們的 erlcount_lib 用於其功能,但除此之外不導入其他任何內容時,事情會變得稍微複雜一些:

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "start_clean", "1.0.0", [kernel, stdlib]},
    {incl_cond, derived}, % exclude would also work, but not include
    {app, erlcount, [{incl_app_filters, ["^ebin/erlcount_lib.beam$"]},
                     {incl_cond, include}]}
]}.

在這種情況下,我們從 {incl_cond, include} 切換到更嚴格的 incl_cond。這是因為如果您將所有內容都納入考量,那麼包含單個程式庫的唯一方法就是使用 excl_app_filters 排除所有其他程式庫。但是,當我們的選擇更具限制性時(在這種情況下,我們是 derived,並且不會包含 erlcount,因為它不是 rel 元組的一部分),我們可以明確告訴發行版本僅包含與 erlcount_lib 相關的正規表示式相符的檔案的 erlcount 應用程式。這引發了一個問題,即如何製作最嚴格的應用程式,對吧?

為有遠大理想的程式設計師設計的較小應用程式

這是 Reltool 變得更加複雜的地方,有一個相當詳細的設定檔

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {erts, [{mod_cond, derived},
            {app_file, strip}]},
    {rel, "erlcount", "1.0.0", [kernel, stdlib, ppool, erlcount]},
    {boot_rel, "erlcount"},
    {relocatable, true},
    {profile, embedded}, 
    {app_file, strip},
    {debug_info, strip},
    {incl_cond, exclude},
    {excl_app_filters, ["_tests.beam$"]},
    {app, stdlib, [{incl_cond, include}]},
    {app, kernel, [{incl_cond, include}]},
    {app, ppool, [{vsn, "1.0.0"}, {incl_cond, include}]},
    {app, erlcount, [{vsn, "1.0.0"}, {incl_cond, include}]}
]}.

哦,發生了很多事情。我們可以看到,在 erts 的情況下,我們要求 Reltool 僅保留其中必要的內容。將 mod_cond 設定為 derived,將 app_file 設定為 strip 將要求 Reltool 檢查並僅保留用於其他用途的內容。這就是為什麼 {app_file, strip} 也用於發行版本層級的原因。

a crate with a sign that sayz '.ez'

設定檔設定為 embedded。如果您查看先前案例中的 .ez 封存檔,它們包含了原始碼檔案、測試目錄等。當切換到 embedded 時,僅會保留包含檔案、二進位檔和 priv/ 目錄。我也從所有檔案中移除了 debug_info,即使它們在編譯時啟用了它。這表示我們將失去一些除錯能力,但會減小檔案的大小。

我仍然在移除測試檔案,並設定任何應用程式都不會被包含,直到明確告知為止({incl_cond, exclude})。然後,我在我想要包含的每個應用程式中覆寫此設定。如果缺少某些內容,Reltool 會警告您,因此您可以嘗試移動內容並使用設定,直到獲得想要的結果。它可能涉及使用 {mod_cond, derived} 的一些應用程式設定,以便保留某些應用程式的最小檔案。

最後的差異是什麼?我們一些較一般的發行版本會超過 35MB。上面描述的版本縮減到不到 20MB。我們縮減了很大一部分。不過,這個大小仍然相當大。這是因為 ERTS 本身佔用了 18.5MB。如果願意,您可以深入挖掘並真正微管理 ERTS 的建置方式,以獲得更小的版本。或者,您可以選擇 ERTS 中您知道應用程式不會使用的某些二進位檔案:用於腳本的可執行檔、Erlang 的遠端執行、來自測試框架的二進位檔、不同的執行命令(帶或不帶 SMP 的 Erlang 等)。

最輕量的發行版本將假設其他使用者已經安裝了 Erlang — 當您選擇此選項時,您需要將 rel/ 目錄的內容新增為您的 ERL_LIBS 環境變數的一部分,並自行呼叫啟動檔案(有點像使用 systools),但它會正常運作。程式設計師可能希望將其包裝在腳本中以使事情順利進行。

注意:現在,Erlang 程式設計師似乎非常喜歡使用一個名為 rebar3 的工具為您處理所有這些發行版本的想法。Rebar3 將充當 Erlang 編譯器的封裝器並處理發行版本。理解 Reltool 的運作方式沒有任何損失 — Rebar3 對於發行版本使用更高層次的抽象,而理解 reltool 可以輕鬆理解任何其他工具的運作方式。

從發行版本中釋放

好了,這就是處理發行版本的兩種主要方法。這是一個複雜的主題,但也是處理事情的標準方法。應用程式可能對許多讀者來說已經足夠了,並且在很長一段時間內堅持使用它們並沒有什麼不好,但是如果您希望您的營運和維護人員更喜歡您一點,因為您知道(或至少有一些概念)在需要時如何部署 Erlang 應用程式,那麼發行版本有時可能會很有用。

當然,什麼能讓您的營運人員比沒有停機時間更快樂?下一個挑戰是在發行版本執行時進行軟體升級。

Parody of the poster of the Grease movie, where 'Grease' is replaced by 'Release', Olivia Newton-Jogn by Joe Armstrong and John Travolta by Bjarne Dacker