新的 opam 功能:更具表達力的依賴關係
這篇部落格將涵蓋 opam 2.0 相較於 opam 1.2 的另一個改進之處。由於它涵蓋的功能專為套件打包者和儲存庫維護者而設計,並且關於套件定義格式,因此我可能會比之前的主題更技術性一些。
在 opam 1.2 中指定依賴關係
Opam 1.2 已經有一種使用套件和版本公式指定套件依賴關係的高級方法,語法如下
depends: [
"foo" {>= "3.0" & < "4.0~"}
( "bar" | "baz" {>= "1.0"} )
]
表示正在定義的套件依賴於 3.x
系列中的套件 foo
,以及 bar
或 baz
其中之一,後者的版本至少為 1.0
。有關完整的文件,請參閱這裡。
然而,這僅允許對於給定套件是靜態的依賴關係。
Opam 1.2 引入了 build
、test
和 doc
「依賴標籤」,可以為依賴關係提供一些具體的資訊(例如,只有在要求測試套件時才需要 test
依賴關係)。這些標籤被限制在版本約束之前出現,例如 "foo" {build & doc & >= "3.0"}
。
opam 2.0 中的擴展
Opam 2.0 推廣了依賴標籤,並允許混合篩選器 (基於 opam 變數的公式) 與版本約束,使依賴關係的規範更具表達力。如果該公式成立,則強制執行依賴關係;否則,將捨棄該依賴關係。
在opam 2.0 手冊中對此進行了更詳細的說明。
另請注意,由於編譯器現在是套件,因此所需的 OCaml 版本現在也使用此機制來表示,透過依賴於(虛擬)套件 ocaml
,例如 depends: [ "ocaml" {>= "4.03.0"} ]
。這取代了 available:
欄位和 ocaml-version
切換變數的用法。
條件依賴關係
這使得可以很簡單地在給定的依賴關係上新增作業系統的條件,使用內建變數 os
depends: [ "foo" {>= "3.0" & < "4.0~" & os = "linux"} ]
在這裡,如果作業系統不是 Linux,則不需要 foo
。我們也可以使用更複雜的公式來更具體地說明其他作業系統
depends: [
"foo" { "1.0+linux" & os = "linux" |
"1.0+osx" & os = "darwin" }
"bar" { os != "osx" & os != "darwin" }
]
表示 Linux 和 OSX 分別需要 foo
,版本為 1.0+linux
和 1.0+osx
,而其他系統則需要 bar
,任何版本。
依賴標籤
不再需要 1.2 中使用的依賴標籤,它們被可以出現在版本規範中任何位置的變數所取代。以下變數通常在那裡很有用
with-test
、with-doc
:取代test
和doc
依賴標籤,當請求套件的測試或文件時為true
- 同樣地,
build
的行為與之前類似,將依賴關係限制為「建置依賴關係」,這表示如果依賴關係發生變更,則不需要重新建置套件 dev
:此布林變數在「開發」套件上為true
,也就是說,套件綁定到不穩定的來源(版本控制系統,或者如果套件被釘選到沒有已知檢查總和的封存檔)。dev
來源通常需要額外的初步步驟(例如autoconf
),這可能會有自己的依賴關係。
使用 opam config list
來取得預先定義變數的清單。請注意,with-test
、with-doc
和 build
變數並非在所有地方都可用:前兩個僅允許在 depends:
、depopts:
、build:
和 install:
欄位中使用,而後者則專用於 depends:
和 depopts:
欄位。
例如,datakit.0.9.0
套件具有
depends: [
...
"datakit-server" {>= "0.9.0"}
"datakit-client" {with-test & >= "0.9.0"}
"datakit-github" {with-test & >= "0.9.0"}
"alcotest" {with-test & >= "0.7.0"}
]
當執行 opam install datakit.0.9.0
時,with-test
變數設定為 false
,並且會篩選掉 datakit-client
、datakit-github
和 alcotest
依賴關係:它們將不是必需的。使用 opam install datakit.0.9.0 --with-test
,with-test
變數為 true(僅針對該套件,不會啟用命令列上未列出的套件的測試!)。在這種情況下,依賴關係解析為
depends: [
...
"datakit-server" {>= "0.9.0"}
"datakit-client" {>= "0.9.0"}
"datakit-github" {>= "0.9.0"}
"alcotest" {>= "0.7.0"}
]
這是正常處理的。
計算的版本
也可以使用變數,不僅作為條件,還可以計算版本值:允許 "foo" {= var}
,這將要求套件 foo
的版本對應於變數 var
的值。
這在定義一系列以相同版本號一起發布的套件時很有用:您不必在每次發布時更新每個套件的依賴關係以符合通用版本,而是可以利用 version
套件變數來表示「與目前套件版本相同的其他套件」。例如,foo-client
可以具有以下內容
depends: [ "foo-core" {= version} ]
甚至可以在版本中使用變數內插,例如,指定與上述不同的作業系統特定版本
depends: [ "foo" {= "1.0+%{os}%"} ]
這會展開 os
變數,解析為 1.0+linux
、1.0+darwin
等。
回到我們的 datakit
範例,我們可以利用這一點並將其重寫為更通用的
depends: [
...
"datakit-server" {>= version}
"datakit-client" {with-test & >= version}
"datakit-github" {with-test & >= version}
"alcotest" {with-test & >= "0.7.0"}
]
由於 datakit-*
套件遵循相同的版本控制,因此這避免了每次新版本都必須重新編寫 opam 檔案,而且每次都有出錯的風險。
附帶一提,這些變數與現在在build:
欄位中使用的內容一致,而build-test:
欄位現在已棄用。因此,同一個 datakit
opam 檔案的另一部分
build:
["ocaml" "pkg/pkg.ml" "build" "--pinned" "%{pinned}%" "--tests" "false"]
build-test: [
["ocaml" "pkg/pkg.ml" "build" "--pinned" "%{pinned}%" "--tests" "true"]
["ocaml" "pkg/pkg.ml" "test"]
]
現在最好寫成
build: ["ocaml" "pkg/pkg.ml" "build" "--pinned" "%{pinned}%" "--tests" "%{with-test}%"]
run-test: ["ocaml" "pkg/pkg.ml" "test"]
這避免了為了變更選項而建置兩次。
結論
希望這種對依賴關係表達力的擴展能讓套件打包者更容易;歡迎針對您的個人用例提供回饋。
請注意,官方儲存庫仍然是 1.2 格式(以 2.0 格式在 https://opam.ocaml.org/2.0
提供,透過自動轉換),並且只有在 opam 2.0 最終發布後才會進行遷移。歡迎您在自訂儲存庫或釘選套件上進行實驗,但是在您可以將使用上述內容的套件定義貢獻到官方儲存庫之前,還需要多一點耐心。
注意:本文已交叉發布在opam.ocaml.org和ocamlpro.com。請前往後者發表評論!