新的 opam 功能:更具表达力的依赖项
这篇博客将介绍 opam 2.0 相较于 opam 1.2 的另一个改进方面。我可能比之前的文章更具技术性,因为它涵盖了专门针对打包人员和仓库维护人员的功能,以及关于包定义格式的方面。
在 opam 1.2 中指定依赖项
Opam 1.2 已经提供了一种高级的方式来指定包依赖项,使用包和版本的公式,语法如下:
depends: [
"foo" {>= "3.0" & < "4.0~"}
( "bar" | "baz" {>= "1.0"} )
]
这意味着正在定义的包依赖于 foo
包(位于 3.x
系列中)以及 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}
是允许的,并将要求与变量 var
的值相对应的包 foo
的版本。
例如,这对于定义一系列一起发布并具有相同版本号的包非常有用:无需在每次发布时更新每个包的依赖项以匹配公共版本,你可以利用 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 格式(通过自动转换,在 https://opam.ocaml.org/2.0
中作为 2.0 提供服务),并且仅在 opam 2.0 最终发布后才会迁移。欢迎您在自定义仓库或固定包上进行实验,但需要稍等一段时间才能将利用上述内容的包定义贡献给 官方仓库。
注意:这篇文章同时发布在 opam.ocaml.org 和 ocamlpro.com 上。请前往后者查看评论!