相依性

相依性

2019 年 10 月 26 日

在過去 20 年中,人們已大量轉變其編寫程式的方式。過往的資料多半專注於撰寫每個專案內可重複使用且可延伸的元件,但目前的趨勢是建立小型獨立專案,每個專案可個別輕鬆捨棄與替換。目前的微服務實作與 Javascript 相依性樹狀結構,或許讓我們誤以為沒有任何專案太小到可以捨棄。

具有諷刺意味的是,朝向強隔離與明確定義界線的轉變,不論是以介面、網路 API 或通訊協定方式實現,這是小型單一用途元件所必需的,它已引發出有史以來數量最多的程式碼重複使用。重複使用係由具備相同需求的不同人員與專案而產生,而非來自提供完美可延伸類別階層的神奇能力。現今有非常多的重複使用,以致我們已到達人們開始提出疑問的程度,也就是我們是否在不必要的情況下重複使用過多的程式碼:每個程式庫都有其風險與負債,而為了快速建置,我們暴露了自己於大量風險之中。

Erlang 本身一直仰賴強隔離與明確定義的訊息傳遞通訊協定,以涵蓋在函式介面中,並一直賦予其生命力。其社群規模小,通常都是有經驗的開發人員,這讓其在程式庫與封裝方面落後於其他社群。儘管之前做過努力,ProcessOne 與 Faxien 都推出了封裝管理員,例如 CEAN,而這些管理員會全球安裝 OTP 應用程式,一直到 2009 年,才真的可以輕鬆安裝他人的程式庫。這要歸功於第一版 rebar,它贊同依專案安裝相依性,且是最早以 escript 形式出現的,這是一種可攜式指令碼,開發人員無需安裝即可使用。

Erlang 程式庫隨意地在 GitHub 與類似的版本控制服務中大量散布,在某個時間點,有可能同時找到超過十二個版本相同 PostgreSQL 驅動程式,它們的名稱與版本號碼類似,但功能都有些許不同。

在 Elixir 的大力推進下,引入了諸如 mix 和 hex.pm 等新觀點和工具,促使 Erlang 社群重新調整自己,建構出一個更容易理解的生態系統。時至今日,Erlang 的社群規模仍然不大,但已經採用了更好的實務範例,並且現在與 Elixir,以及在同一虛擬機器上執行的小型語言(約六種),共享其套件和程式庫基礎架構。

在本章中,我們將藉由回答以下問題,了解在 Erlang 世界中如何實踐現代化程式庫使用與社群整合

  • 什麼是程式庫?
  • 如何將其當作相依性套件使用?
  • 相依性套件有哪些生命週期?
  • 如何使用 Elixir 相依性套件?
  • 如果我的程式使用單一存放庫該怎麼辦?

使用開源程式庫

Erlang 的開源相依性套件,只不過是 OTP 應用程式,就像版本中的任何其他程式庫。因此,使用開源程式庫需要的條件,就是讓 Erlang 工具鏈能夠看見該 OTP 應用程式。這個概念很簡單,但是每種語言和社群在這一點上的做法略有不同。我們必須在電腦上全球安裝程式庫,共享環境中安裝程式庫,或是並在每個專案中執行本機安裝程式庫中做選擇。然後,還有關於版本控制和發表的規範,也必須遵守這些規範。本節將說明該如何執行這些動作,不過首先我們會繞道了解 Rebar3 對於專案生命週期所預期的內容。

Rebar3 預期

Rebar3 已規範了有關開源作品的一些棘手決策,與其抗拒,通常比較容易選擇順其自然,特別是初學者。Rebar3 最初建構於規模中等的公司中,以半公開模式撰寫服務:既使用又發布開源套件,但是部分程式碼必須永遠保持私有權。同時開發多個不相關的服務,而這些服務不一定要使用相同的 Erlang 版本和相同的程式庫。有些程式只會透過持續更新來部署(就像雲端所有地方),但有些系統絕對需要熱程式碼載入。Rebar3 也是在社群的版本控制實務極其混亂的那段時間開發的:許多程式庫的 .app 檔案版本與 GitHub 上的 git 標籤不同,而文件則又記載了另一個版本。那時,hex.pm 上大約有 5 分之 4 的程式庫,甚至沒有達到 1.0.0 版本。

因此,Rebar3 擁有以下特性

  • Rebar3 是一種宣告性建構工具。您可以提供設定檔,而 Rebar3 會執行程式碼以滿足該設定檔的要求。如果您想執行自訂指令碼並擴充建構,它特別提供了 掛鉤外掛 介面來執行。還有其他方法可以取得 動態設定,能夠更彈性地填滿設定檔。
  • 作為宣告方式的一部分,Rebar3 中的指令會透過相依性順序進行定義。例如,rebar3 compile 工作仰賴於 rebar3 get-deps(或更明確地來說,是其一種會鎖定相依性項目的私人形式),並會替您執行该工作。rebar3 tar 指令會隱含呼叫包含 get-deps -> compile -> release -> tar 順序的相關項目。因此,Rebar3 得以了解在編譯專案時,需要其相依性。
  • Rebar3 在 _build 目錄中定義它自己的工作區域;它預期可以控制其中內容,而不用在源碼管理中追蹤該目錄或仰賴其內部結構。當建立使用者會想要直接使用的製成品時,它會在終端機中輸出該製成品的儲存路徑。例如,rebar3 escriptize 會在每次執行後列印產生的 escript 儲存路徑至 _build/default/bin/rebar3 ct 會在測試失敗時列印 Common Test HTML 輸出的儲存路徑,而 rebar3 tar 會列印發布 tar 檔案的儲存路徑。
  • 所有專案都會在其專屬目錄中,基於您環境中目前載入的 Erlang 執行時期,也就是 $PATH 內找到的 erl,進行本機建置。
  • 所有相依性項目會擷取到專案 _build 目錄。
  • 所有相依性項目都可定義其自己的相依性,而 Rebar3 會辨識此事,並將之撷取至根目錄專案的 _build 目錄。
  • 由於版本號碼並不可靠(即使是語意化版本),因此我們將版本視為提供給使用者的資訊,而非建置工具。針對最終重複宣告的函式庫,選擇最接近專案根目錄的函式庫,同時在第一次建置過程中傳送 警告(由於相依性沒有鎖定,因此它知道是「第一次」),讓使用者知道未使用哪些版本。假設宣告在較接近專案根目錄的函式庫是用得最徹底的。
  • 禁止循環相依性。
  • Rebar3 支援可組合的每個專案 設定檔,讓您能夠區隔或組合組態設定,例如只用於測試或只在特定環境中(例如,在特定的目標作業系統上)使用的相依性。組態設定檔的數量或名稱並無限制,但有四個組態設定檔(分別為 defaulttestdocsprod)會在特定工作中自動由 Rebar3 所使用。
  • 建置意在可重複執行。相依性項目經過鎖定,只會在特別要求的狀況下才更新。rebar.lock 中的版本優先於 rebar.config 中宣告的版本,直至呼叫 rebar3 upgrade 來更新鎖定檔後。
  • 依賴關係總是會套用 prod 輪廓來建置。Rebar3 會持續確認它們是否符合鎖定檔案,並且建置人工產品是否已存在,但不會偵測你在一個依賴關係中手動執行的程式碼變更。
  • Rebar3 假設你偶爾會需要調整你無法控制的函式庫,並支援 覆寫 以供這類用途。
  • 此工具假設你會使用版本控制機制(例如 githg,Mercurial)進行開發,這表示變更分支可能會變更鎖定檔案中的依賴關係版本。由於 Rebar3 在每次建置之前都會驗證依賴關係,因此如果你在變更分支時出現變更,它會自動重新取得函式庫來取得目前分支的鎖定版本。
  • 為了簡化貢獻及發布,Rebar3 在本機不支援使用相對路徑宣告函式庫的方式,因為這樣可能會讓建置變得脆弱不可重覆且在發布程式碼時不可移植。
  • 在專案的依賴關係中進行變更時,相對路徑很常見。 _checkouts 允許暫時覆寫依賴關係並使用本地副本的方式自動化。對於其他使用案例,外掛程式 能夠建立 自訂資源型別
  • Rebar3 不是最終使用者應用的安裝程式或執行程式,也不支援任何相關事物;它旨在產生建置人工製品,讓你可以透過正確的專用管道進行安裝。Rebar3 沒有預期或需求會出現在生產裝置或伺服器上。
  • Rebar3 不是沙盒。雖然它會確認你下載的所有依賴關係都符合正確簽章,並提供可重覆的建置,但它無法保證建置期間引用的腳本檔或編譯期間執行的 解析轉換 永遠都是安全的,也無意承擔此責任。外掛程式也不會自動鎖定,函式庫作者應自行決定是否要鎖定會影響編譯的版本。

以上資訊很多,但在深入瞭解依賴關係前,我們認為知道這些資訊很有用。如果你假設 Rebar3 的運作方式就像 Javascript 的 npm、Elixir 的 mix、Go 的工具鏈,甚或是原始 rebar,你可能會對某些行為感到困惑。在此情況下,我們來使用依賴關係。

宣告依賴關係

因為這些相依性都侷限在專案裡,因此必須要宣告在專案的 `rebar.config` 檔案中。這樣才能讓 Rebar3 知道需要擷取相依性、建置它們,並在專案中提供它們。所有相依性都必須是獨立的 OTP 應用程式,這樣才能各自設定版本並個別處理。

下列格式都是有效的

{deps, [
    %% git dependencies
    {AppName, {git, "https://host.tld/path/to/app", {tag, "1.2.0"}}},
    {AppName, {git, "https://host.tld/path/to/app", {branch, "master"}}},
    {AppName, {git, "https://host.tld/path/to/app", {ref, "aed12f..."}}},
    %% similar format for mercurial deps
    {AppName, {hg, "https://host.tld/path/to/app", {RefType, Ref}}}
    %% hex packages
    AppName, % latest known version (as per `rebar3 update`)
    {AppName, "1.2.0"},
    {AppName, "~> 1.2.0"}, % latest version at 1.2.0 or above, and below 1.3.0
    {AppName, "1.2.0", {pkg, PkgName}}, % when application AppName is published with package name PkgName
]}.

此外,外掛程式允許定義 自訂資源定義,讓您能夠在專案中新增新的相依性類型。

讓我們看看這在本專書中特別為此建置的專案 `service_discovery` 中如何運作。開啟 `rebar.config` 檔案,您會看到

...

{deps, [
    {erldns,
     {git, "https://github.com/tsloughter/erldns.git",
     {branch, "revamp"}}},
    {dns,
     {git, "https://github.com/tsloughter/dns_erlang.git",
     {branch, "hex-deps"}}},

    recon,
    eql,
    jsx,
    {uuid, "1.7.5", {pkg, uuid_erl}},
    {elli, "~> 3.2.0"},
    {grpcbox, "~> 0.11.0"},
    {pgo,
     {git, "https://github.com/tsloughter/pgo.git",
     {branch, "master"}}}
]}.

...

git 和 hex 相依性可以適用於大部分的專案。唯一的例外是 hex 套件,只能相依於其他 hex 套件。讓我們編譯整個專案,逐步了解其中發生了什麼。

$ rebar3 compile
===> Fetching covertool v2.0.1
===> Downloaded package, caching at /Users/ferd/.cache/rebar3/hex/hexpm/packages/covertool-2.0.1.tar
===> Compiling covertool
...
===> Verifying dependencies...
===> Fetching dns (from {git,"https://github.com/tsloughter/dns_erlang.git",
               {ref,"abc562548e8a232289eec06cf96ce7066261cc9d"}})
===> Fetching provider_asn1 v0.2.3
===> Downloaded package, caching at /Users/ferd/.cache/rebar3/hex/hexpm/packages/provider_asn1-0.2.3.tar
===> Compiling provider_asn1
===> Fetching elli v3.2.0
...
===> Fetching rfc3339 v0.9.0
===> Version cached at /Users/ferd/.cache/rebar3/hex/hexpm/packages/rfc3339-0.9.0.tar is up to date, reusing it
===> Compiling quickrand
===> Compiling uuid
===> Compiling recon
...
===> Compiling service_discovery_storage
===> Compiling service_discovery
===> Compiling service_discovery_http
===> Compiling service_discovery_grpc
===> Compiling service_discovery_postgres

執行這項建置時,您會看到同時發生多件事

  1. 外掛程式(例如 `covertool`)會在其他任何事情開始之前先擷取並編譯
  2. 擷取與專案實際相關的相依性(例如 `dns` 和 `elli`)
  3. 編譯相依性(`quickrand` 和其他)
  4. 編譯主應用程式

如果您要重試並從頭執行所有項目,同時刪除 `rebar.lock` 檔案,則事情會有點不同。您可能會看到類似於此項內容作為輸出的一部分

...
===> Fetching dns (from {git,"https://github.com/tsloughter/dns_erlang.git",
               {branch,"hex-deps"}})
...
===> Skipping dns (from {git,"git://github.com/dnsimple/dns_erlang.git",
               {ref,"b9ee5b306acca34b3d866d183c475d5f12b313a5"}}) as an app of the same name has already been fetched
...
===> Skipping jsx v2.9.0 as an app of the same name has already been fetched
===> Skipping recon v2.4.0 as an app of the same name has already been fetched
...

這些是在相依性解析期間發生的輕微通知和警告,當有 `rebar.lock` 檔案時就不需要執行。它們會告知函式庫維護人員已偵測到衝突,並略過某個函式庫版本。若要完成建置稽核,您可以呼叫 `rebar3 tree` 來檢查最後的相依性解析。

$ rebar3 tree
===> Verifying dependencies...
├─ service_discovery─e4b7061 (project app)
├─ service_discovery_grpc─e4b7061 (project app)
├─ service_discovery_http─e4b7061 (project app)
├─ service_discovery_postgres─e4b7061 (project app)
└─ service_discovery_storage─e4b7061 (project app)
   ├─ dns─0.1.0 (git repo)
   │  └─ base32─0.1.0 (hex package)
   ├─ elli─3.2.0 (hex package)
   ├─ eql─0.2.0 (hex package)
   ├─ erldns─1.0.0 (git repo)
   │  ├─ iso8601─1.3.1 (hex package)
   │  ├─ opencensus─0.9.2 (hex package)
   │  │  ├─ counters─0.2.1 (hex package)
   │  │  └─ wts─0.3.0 (hex package)
   │  │     └─ rfc3339─0.9.0 (hex package)
   │  └─ telemetry─0.4.0 (hex package)
   ├─ grpcbox─0.11.0 (hex package)
   │  ├─ acceptor_pool─1.0.0 (hex package)
   │  ├─ chatterbox─0.9.1 (hex package)
   │  │  └─ hpack─0.2.3 (hex package)
   │  ├─ ctx─0.5.0 (hex package)
   │  └─ gproc─0.8.0 (hex package)
   ├─ jsx─2.10.0 (hex package)
   ├─ pgo─0.8.0+build.91.refaf02392 (git repo)
   │  ├─ backoff─1.1.6 (hex package)
   │  └─ pg_types─0.0.0+build.24.ref32ed140 (git repo)
   ├─ recon─2.4.0 (hex package)
   └─ uuid─1.7.5 (hex package)
      └─ quickrand─1.7.5 (hex package)

這列清單以 `Verifying dependencies ...` 列開始,這是 Rebar3 驗證所有相依性皆已解析才會列印這棵樹。接下來是所有頂層應用程式(我們在儲存庫中撰寫的應用程式),而我們剛剛擷取的相依性則全都在它們下方。這樣您便可以看到完整的解析樹,找出哪些版本已擷取,以及哪些應用程式引進這些相依性。這對於瞭解當兩個以上應用程式包含傳遞性相依性時,為什麼會選取某個版本非常有用。

如果您查看 `_build/default/lib` 中的內容,您會在各自分錄中看到所有這些應用程式

$ ls _build/default/lib
acceptor_pool  gproc       rfc3339
backoff        grpcbox     service_discovery
base32         hpack       service_discovery_grpc
chatterbox     iso8601     service_discovery_http
counters       jsx         service_discovery_postgres
ctx            opencensus  service_discovery_storage
dns            pgo         telemetry
elli           pg_types    uuid
eql            quickrand   wts
erldns         recon

這些都是具有類似目錄結構的 OTP 應用程式。這種配置與 深入淺出 OTP 中所述發行版的專案結構相當類似,但這仍然只是個暫存區。

建置具有相依性的專案

在專案中建置 OTP 應用程式,需要的步驟不只是取得相依套件與編譯它們而已。如同 讓程式庫成為應用程式 中所提,Erlang 執行時期系統預期在 .app 檔案中找到相依套件的執行時期定義。如果沒有將它們放在該處,會轉告 Rebar3 它們是建置時期的相依套件,而不是執行時期的相依套件。這表示它們不會包含在某些工作或環境中:例如版本會忽略它們,而 rebar3 dialyzer 會避免將它們納入分析中。

開啟 apps/service_discovery/src/service_discovery.app.src,再查看 applications 元組中的值

{application, service_discovery,
 [{description, "Core functionality for service discovery service"},
  {vsn, {git, short}},
  {registered, []},
  {mod, {service_discovery_app, []}},
  {applications,
   [kernel,
    stdlib,
    erldns,
    service_discovery_storage
   ]},
  {env,[]},
  {modules, []},

  {licenses, ["Apache 2.0"]},
  {links, []}
 ]}.

您會看到已加入 erldnsservice_discovery_storage。指定這些相依套件可確保它們在執行時期和版本中提供。如果沒有將它們放在該處,可能會導致建置失敗。

如果您曾經使用 Erlang 生態系中的其他建置工具,您可能從來不必執行這個步驟。這些工具 (Erlang.mk 或 Mix in Elixir) 最後會將專案配置中的相依套件複製到 applications 元組中。聽起來,直接手動執行這項作業會造成很大的阻礙,但最後會根據 OTP 標準來支援建置時期的相依套件。其他工具會透過配置檔案中的額外選項達成類似的結果。讓我們看 Rebar3 的做法如何在幾個情況中提供關鍵的控制。

這是重要的第一個狀況,是在版本中下載您想要包含的相依套件,以協助您除錯,但您的 OTP 應用程式沒有任何相依套件。例如 reconredbug,或自訂 記錄器 處理常式。您希望這些應用程式在版本中提供,但由於 applications 元組能讓版本知道有哪些應用程式必須引導或啟動,您不見得希望它們成為您相依鏈的一部分。為何必須啟動並執行一個只有在必要時才會安裝的除錯工具,才能讓網站運作?這完全沒有必要。您不希望一個功能失常的除錯工具阻止實際應用程式啟動。

在這種情況下,您會希望專案配置看起來與 service_discovery 的這一段類似

{relx, [
    {release, {service_discovery, {git, long}},
     [service_discovery_postgres,
      service_discovery,
      service_discovery_http,
      service_discovery_grpc,
      recon]},
    ...
]}.

您可以看到,除了我們的應用程式之外,除錯工具 recon 也明確包含在版本的應用程式清單中。所有它們的遞移相依套件都會包含在內 (根據 .app 檔案中的 applications 元組),但各種 OTP 應用程式會以不連接的方式處理。

讓我們稍微聚焦在這些 4 個 service_discovery 應用程式上。這些代表我們要將 rebar.config 中建置相依性的宣告與 .app.src 檔案中執行時期相依性的宣告分開的第二種類型狀況。

您會看到在頂層,所有函式庫的所有相依性都在單一 rebar.config 檔案中宣告。這讓開發人員可以輕易處理並更新所有需要的版本。然而,如果您檢視 service_discoveryservice_discovery_http.app.src 檔案,您會發現這一點

%% service_discovery.app.src
...
  {applications,
   [kernel,
    stdlib,
    erldns,
    service_discovery_storage
   ]},
...
%% service_discovery_http.app.src
...
  {applications,
   [kernel,
    stdlib,
    service_discovery,
    jsx,
    elli
   ]},
...

這裡,service_discovery_http 相依於一個網路伺服器 (elli),但 service_discovery 沒有。這允許更清晰的開機和關機指令碼,其中您實際上不需要網路伺服器開機和執行就可以啟動系統後端。

對於第三個指令碼,您可能也會想像一個小應用程式 service_discovery_mgmt,它僅用於產生一個 escript,讓您可以執行系統管理工作,與系統互動並傳送指令。

如果執行時期相依性是跨所有應用程式分享,這些應用程式相依於相同的 rebar.config 檔案,那麼即使 service_discovery_mgmt 沒有包含在發行版本中 (它只會是側邊的指令碼),它的相依性仍然有風險透過其他自動將其插入到其中的應用程式而進入生產環境。更糟的是,service_discovery 發行版本的相依性可能會一併與指令碼打包!我們可能會得到一個包含網路伺服器和資料庫驅動程式的的小管理工具,這是因為建置工具試著變得更好。

因此,Rebar3 維護人員決定在需要擷取才能建置或執行專案的應用程式 (在 rebar.config 中) 和每個 OTP 應用程式的執行時期相依性 (在 .app 檔案中) 之間維持清楚的區別,後者可能是 OTP 預設安裝的一部分,因此不會包含在 rebar.config。生態系統中的其他建置工具可讓您達成類似的結果,但它們預設包含執行時期的所有內容,而 Rebar3 要求開發人員始終具體說明他們的意圖。

應用所有這些後,我們唯一要做的就是整理和清除我們的相依性組。

相依性生命週期

在初始化專案時,相依性解決和擷取是最重的工作。結果儲存在鎖定檔案中,隨後的結果全部透過 Rebar3 進行較小的部分變更來處理。這個第一階段很重要,即使您不太常執行也是如此。

Rebar3 鎖定檔案建立於專案的根目錄,也就是您呼叫 Rebar3 的目錄。它會儲存為rebar.lock,您應該在您選擇的版本控制中追蹤它。您可以開啟檔案查看內容,但通常不應該手動編輯它。您會發現它大多包含版本號、應用程式名稱及各種雜湊。不時稽核它可能很有趣,但當您維護依存樹時,最後會間接執行這個動作。

鎖定檔案表示在建置時所希望的全部依賴關係的扁平化樹狀結構。除非您要求變更,或是刪除它讓新的解析從頭開始進行,否則它不會被修改。這個嚴格性是故意的,也是 Rebar3 如何在任何情況下保證可重現建置的一部分。

檔案中的雜湊表示,即使依存關係由多層鏡像擷取,以及某個惡意人士變更您可能使用的各種 hex 索引或 git 來源中的封包,Rebar3 都能夠發現資訊不如預期,並因此產生錯誤。

因此,您應僅在需要時更新鎖定檔案。您可以使用下列操作執行這個動作

  • rebar3 unlock <appname>從鎖定檔中移除未使用的依存關係。您通常會希望在您已從rebar.config檔案中移除後呼叫這個,以告訴 Rebar3 它已確實消失或已降級為一個傳遞性依存關係。
  • rebar3 upgrade <appname>告訴 Rebar3 忽略該應用程式的鎖定版本,並根據(如果有)rebar.config中目前指定的版本,重新建置依存關係樹。這將產生一個新的鎖定檔,並重新解析可能已變更的全部傳遞性依存關係。
  • rebar3 update並不完全與鎖定檔有關,它會更新遠端 hex 封包的本地快照,這是一種快取,可防止每個建置都 ping 封包伺服器。如果您發現自己對應用程式呼叫rebar3 upgrade,但它並未升級到您知道在 Hex 中可用的最新版本,您會希望先update。這是因為 Rebar3 嘗試使用本地索引快取解析依存關係,因此限制使用網路。rebar3 update會擷取索引中已存在每個封包的最新索引項目,然後再執行upgrade會看到最新版本。請注意,如果您指定要升級到特定版本,Rebar3 會自動擷取更新的索引,因為它無法在本地滿足依存關係。
  • rebar3 tree印出已建置並由鎖定檔案表示的依存關係樹狀結構。
  • rebar3 deps 列出相依套件並加註那些可以更新的套件。請注意,它對於值得更新的套件有嚴格的限制:git 中的分支、參考標籤已移轉或未指定 hex 版本。但是,如果你指定一個套件版本為 "1.2.3",而 1.2.4 可用,它不會告訴你任何事情。

還有其他指令會稍微徹底一點,即 rebar3 unlockrebar3 upgrade(沒有任何引數)。這些指令將移除專案的下一次建置的鎖定檔。但總的來說,所有這些指令會執行你管理大部分相依套件所需要的所有工作。

一般來說,你的工作流程可能如下:

  1. 設定初始專案、編譯一次並追蹤原始程式碼管理中的鎖定檔
  2. 找出你想要變更的頂層相依套件
  3. 在 rebar.config 檔中變更相依套件定義(或者,如果你選擇使用非版本 hex 套件,請呼叫 rebar3 update
  4. 呼叫 rebar3 upgrade <app> 來更新應用程式及其傳遞相依套件

而已,你完成了。

簽出相依套件

Rebar3 希望讓開發人員的生活更輕鬆,同時保持安全且可重複執行。_checkouts 是一個特點,其與鎖定檔等概念和重複執行相違背,但它提供了快速的回饋和圍繞相依套件局部變更的更好體驗。

如果你剛開始採用 Erlang,無論是為了好玩還是工作,那麼很有可能你有一些相依套件存在於不同儲存庫中的專案。使用這些相依套件並不會太難。但是,遲早,如果你必須開始修補相依套件,或者,如果你在有數十個儲存庫的公司環境中工作,使用 Rebar3 可能會令人感到沮喪。

問題在於,每次你想要嘗試修改相依套件時,你都必須提交變更並將變更發布到 Rebar3 可擷取變更的位置,因為如果模組已經有對應的 .beam 檔,它不會建置對 _build 下原始檔所做的變更。當你必須跨過儲存庫邊界,並只想測試變更時,這可能會很快讓你感到不耐煩。

因此,有一個稱為簽出相依套件的特點小技巧。簽出相依套件的工作原理如下:

  • 你的主要專案在你的檔案系統中的某個位置
  • 相依套件已在主要專案的 rebar.config 檔中宣告
  • 你也將該相依套件放在你的檔案系統中的某個位置,做為一個獨立專案
  • 你將一個 _checkouts/ 目錄加入主要專案
  • 你將相依套件的目錄複製或建立符號連結到 _checkouts/ 目錄中

從此以後,只要建置主要應用程式,它就會在精選集裡增加一個 ebin/ 目錄到依賴目錄中,並重新編譯它,就像它是在主要專案中的一級應用程式一樣。

然後,您可以在主要專案中測試依賴項的變更,直到它們就緒為止。完成之後,從依賴項中移除 ebin/ 目錄,提交並在依賴項中發布您的程式碼,從 _checkouts 目錄中移除它,並執行 rebar3 upgrade

這讓您可以在主要專案的內容中針對依賴項執行許多小型的反覆變更,無需推送依賴項的變更或變更組態檔,將依賴項指向某個本機目錄。它大幅降低了每個應用程式儲存庫當作整體開發策略的負擔。

使用 Elixir 依賴項

多年來,Elixir 和 Erlang 道路是一條單行道。您可以在 Elixir 專案中包含 Erlang 依賴項,但反之則不行。從那時起,並感謝 Erlang Ecosystem Foundation 的支援,已對 Rebar3 做出變更,以提供全新的編譯器管理結構。這個結構已經讓寫出允許 Erlang 使用者使用 Elixir 程式碼的編譯器外掛程式變為可能。

這樣做的方式是先安裝 Elixir。为此,您可能需要依循 官方 Elixir 網站 上的步驟。大多數 Elixir 程式設計師會使用 asdf 來管理版本。如 安裝 所述,Erlang 的 kerl 選項可與 asdf 的 Erlang 外掛程式搭配使用,因此它可以提供給您完整的安裝。

放置 Elixir 後,將 rebar_mix 外掛程式加入您的程式庫或專案。

{plugins, [rebar_mix]}.
{provider_hooks, [{post, [{compile, {mix, consolidate_protocols}}]}]}.

{relx, [
    ...
    {overlay, [
        {copy, "{{base_dir}}/consolidated", "releases/{{release_version}}/consolidated"}
    ]
}.

並將以下列加入您的 vm.args.src 檔(如果您有這個檔)

-pa releases/${REL_VSN}/consolidated

從此開始,您就可以毫無問題地安裝任何包含 Elixir 程式碼的 hex 依賴項。目前,這個外掛程式僅支援只依賴其他 hex 依賴項的 hex 依賴項;不過,使用 git 的傳遞依賴項支援應該很快就會推出。

請注意,由於需要在 Rebar3 和 Elixir 方面都執行更多工作才能實現這一點,目前並不支援在同一個程式庫中同時使用 Erlang 和 Elixir 的混合 Rebar3 專案。另一方面,如果您需要,Mix 確實支援這種模式。

企業環境

企業環境往往對能做和不能做的事情有各種奇怪的限制,而且開發工具可能非常獨特。Rebar3 主要開發的目的是要符合開放原始碼世界,並主要專注於強制執行專案結構、擷取依賴項,並將兩者結合在一起,作為將許多標準工具包裝在一起的絕佳藉口。

因此,或許毫不令人意外的是,在企業環境中安裝該工具會證明有一些挑戰。在此部分,我們將介紹一些在企業環境中常見的標準工具,它們在採用 Erlang 時可能會讓你的生活更輕鬆。

代理支援

許多工作場所實施非常嚴格的防火牆規則,所有進出資料都必須攔截和監控。一般來說,這些地方不會完全與公共網路隔離,但需要使用代理伺服器才能建立外連線。

程式尊重 HTTP_PROXYHTTPS_PROXY 環境變數,這是相當標準的作法。當這些變數在你的開發環境中設定時,Rebar3 將會確保其與外界的所有通訊都使用這些代理伺服器。

這應該讓你能夠妥善配合你的資訊科技部門的政策。在某些情況下,這樣還不夠。

私人 Hex 鏡像

某些公司更進一步,將其內部網路從公共網路中區隔開來。所有來到網站上的資料都必須經過檢查和獨立 hosting,沒有機會連線到如 github、gitlab 或 hex 等常見程式碼存放庫。另一個有趣的案例是建置伺服器,在此情況下你可能想要阻擋與外界的全部連線,出於安全和保持可重複性的理由。

對於此類設定,兩種手法最常被使用:採用單一存放庫,這將在下一篇幅中介紹,以及透過私人 hosting 的套件索引,我們將在此介紹。

私人 hosting 索引的理念是,在專案中使用的所有套件和相依項目的必須經過全面審查。你可能會針對程式碼品質進行技術審查、安全評估,或請公司法律顧問查看程式碼,了解其授權或專利問題。然後,才能使用正確版本的套件。這種索引通常可接受封閉式建置,假設它是在每個建置伺服器上本地執行,或者在一個與建置伺服器一樣受到嚴格監控的私人網路中執行。

Rebar3 支援此用例。如果你想要啟用它,你需要先設定一個私人 hex 實體。這可以透過 minirepo 專案完成。透過遵循 專案頁面 上的說明,最終你會執行自己的私人 hex 伺服器,使用本機檔案系統或基於 S3 的儲存空間,並具備鏡像其他索引和私下發佈自己套件的功能。

你需要 調整你的全域 Rebar3 組態 才能使用它,但一旦完成,你就可以開始使用了。

關於單一存放庫

如果您在具有單一儲存庫的公司環境中工作,其中所有私人程式庫、一次性密碼應用程式和相依項都一視同仁(它不只是個整體版本),那麼 Rebar3 並不是最適合的工作工具。這主要歸因於使用單一儲存庫的大多數公司都具備大量具有非常自訂工作流程的自訂工具、大型程式碼庫,以及非常強烈的傾向不分享存取權給 Rebar3 維護者。在維護者可以存取之前,在那個範疇內幾乎不可能完成任何事。

儘管使用單一儲存庫,但依賴 Rebar3 的各種商業使用者回報指出,他們以結合使用 _checkouts 相依項與重新設定 _build 目錄的方式取得成功的建置。然而,我們目前無法建議此方法,而且不對單一儲存庫提供官方支援。

另一個選項是提供相依項,這可透過類似 rebar3_path_deps 這類外掛程式來完成。

在所有這些都就定位後,您應該已設定好管理專案相依項的生命週期。