依赖项
声明依赖项
依赖项可以在顶级 rebar.config
文件中声明,并使用 rebar3 tree
命令进行检查。
通常,Rebar3 支持两种类型的依赖项
- 源依赖项(Git、Mercurial)
- 包依赖项
这两种类型的依赖项的工作方式大致相同。Rebar3 使用 hex.pm 提供一组受管理的包及其依赖项。它们通常会更快(位于 CDN 后面),可以镜像,并且会在本地缓存到 ~/.cache/rebar3/
中。
所有依赖项都应为项目本地。这通常是一个不错的选择,以避免全局库出现版本冲突的常见问题。它还有助于 Erlang 的通用机制 发布,它构建独立系统。
依赖项符合以下任何一种格式
{deps,[
%% Packages
rebar,
{rebar,"1.0.0"},
{rebar, {pkg, rebar_fork}}, % rebar app under a different pkg name
{rebar, "1.0.0", {pkg, rebar_fork}},
%% Source Dependencies
{rebar, {git, "git://github.com/erlang/rebar3.git"}},
{rebar, {git, "http://github.com/erlang/rebar3.git"}},
{rebar, {git, "https://github.com/erlang/rebar3.git"}},
{rebar, {git, "[email protected]:erlang/rebar3.git"}},
{rebar, {hg, "https://othersite.com/erlang/rebar3"}},
{rebar, {git, "git://github.com/erlang/rebar3.git", {ref, "aef728"}}},
{rebar, {git, "git://github.com/erlang/rebar3.git", {branch, "master"}}},
{rebar, {git, "git://github.com/erlang/rebar3.git", {tag, "3.0.0"}}},
%% Source dependencies (git only) in subdirectories, from version 3.14 onwards
{rebar, {git_subdir, "git://github.com/erlang/rebar3.git", {branch, "main"}, "subdir"}},
{rebar, {git_subdir, "git://github.com/erlang/rebar3.git", {tag, "3.14"}, "sub/dir"},
{rebar, {git_subdir, "git://github.com/erlang/rebar3.git", {ref, "aeaefd"}, "dir"}
]}.
如上例所示,对于当前版本,仅支持包、git 源和 mercurial 源。自定义依赖项源可以通过 实现资源行为 并将其像插件一样包含进来进行添加。
运行时依赖项
但是,Erlang/OTP 为启动和关闭应用程序所做的依赖项处理以及构建发布和脚本的工具(甚至是 Rebar3 的一部分)依赖于更细粒度的依赖项声明,指定项目中每个应用程序的哪些依赖于其他应用程序。
您应该将每个依赖项添加到您的 app
或 app.src
文件中
{application, <APPNAME>,
[{description, ""},
{vsn, "<APPVSN>"},
{registered, []},
{modules, []},
{applications, [kernel
,stdlib
,cowboy
]},
{mod, {<APPNAME>_app, []}},
{env, []}
]}.
这将允许灵活地编写和生成软件,其中各种分离的应用程序可以共存于虚拟机中,而无需将它们的依赖项完全纠缠在一起。例如,您可能希望您的 Web 服务器能够独立于管理和调试工具运行,即使它们应该在生产环境中可用。
如果需要更多格式的支持,可以通过 rebar_resource
行为 扩展 Rebar3,并通过 拉取请求 发送给维护者。
🚧
依赖项和配置文件
依赖项将始终使用应用于其配置的
prod
配置文件进行编译。任何依赖项都不会使用其他配置文件(当然,除了default
之外)。即使它们配置为prod
,依赖项仍将被获取到其声明下的配置文件目录中。例如,顶级deps
中的依赖项将在_build/default/lib
下,而配置文件test
下的依赖项将被获取到_build/test/lib
,并且两者都将使用应用的prod
配置文件配置进行编译。
依赖项版本处理
Rebar3 认为依赖项版本仅供参考。鉴于 Rebar3 添加时 Erlang 社区中现有的开源环境,尝试强加 语义版本控制 或任何其他类似方案被认为是不切实际的
- 人们更新某些版本,但并非所有版本(Git 标签与分支名称与 OTP 应用程序版本),并且它们可能相互矛盾;
- 有些人从未更新过他们的版本,并在这些版本下发布了很多次;
- 并非所有人都遵循相同的版本方案;
- 人们在订阅语义版本控制时会出错;
- 许多应用程序的版本低于
1.0.0
,因此永远被认为是不稳定的; - 源依赖项经常使用:因此,确定版本冲突需要从所有依赖项下载所有传递依赖项,以每次都确定它们是否冲突,并且成本很高;
- 严格遵守语义版本控制最终会导致错误的冲突,其中使用跨主要版本未更改的 API 的子集仍然需要手动冲突解决和依赖项审核。
相反,Rebar3 将以 层序遍历 的方式获取和下载依赖项。这意味着最靠近依赖项树根部的依赖项是将被选择的依赖项,无论其版本如何。项目 rebar.config
中声明的任何依赖项都不会被传递依赖项覆盖,并且传递依赖项永远不会被稍后遇到的冲突传递依赖项覆盖。
这也意味着,如果您希望优先使用某个版本,则只需将其添加到您的 rebar.config
文件中并选择要保留的内容;有时语义版本控制所需的相同冲突解决机制。
在实践中,这已被证明是一种绝对足够的机制。
在每次依赖项获取和解析运行之后,最终依赖项列表将写入 rebar.lock
。
📘
将冲突视为错误
如果您希望 Rebar3 在检测到依赖项冲突时立即中止,而不是跳过文件并照常继续,请将行
{deps_error_on_conflict, true}.
添加到您的 rebar 配置文件中。
出于方便考虑(以及因为 hex.pm 规定了 semver),可以使用类似 semver 的语法指定 hex 依赖项
{deps,[
rebar, % fetches latest known version, ignoring pre-releases
{rebar, "~> 2.0.0"}, % >= 2.0.0 and < 2.1.0`
{rebar, "~> 2.1.2"}, % >= 2.1.2 and < 2.2.0`
{rebar, "~> 2.1.3-dev"}` % >= 2.1.3-dev and < 2.2.0`
{rebar, "~> 2.0"}` % >= 2.0.0 and < 3.0.0`
{rebar, "~> 2.1"}` % >= 2.1.0 and < 3.0.0`
]}.
要获取可用的最新版本的包,请调用
$ rebar3 update
===> Updating package index...
要使用除默认 CDN 之外的其他 CDN,例如 官方镜像 之一,请将其添加到项目的 rebar.config
或 ~/.config/rebar3/rebar.config
中
{rebar_packages_cdn, "https://s3-eu-west-1.amazonaws.com/s3-eu.hex.pm"}.
检出依赖项
为了处理您希望在本地处理的依赖项的情况,而无需不断发布新版本,可以使用 _checkouts
目录。只需将您的依赖项符号链接或复制到项目顶级的 _checkouts
中即可
_checkouts
└── depA
└── src
_checkouts
中的任何应用程序或插件都将优先于同一应用程序,如果它还列在 rebar.config
的 deps
、plugins
或 project_plugins
中。这也会覆盖已获取到 _build
的任何内容。
请注意,_checkouts
是一个覆盖,这意味着为了使其工作,必须存在 rebar.config
中的 dep
或 plugin
条目。
获取顺序
对于常规的依赖项树,例如
A
/ \
B C
将获取依赖项 A
、B
和 C
。
但是,对于更复杂的树,例如
A
/ \
B C1
|
C2
将获取依赖项 A
、B
和 C1
。当 Rebar3 遇到对 C2
的需求时,它将改为显示警告:跳过 C2(来自 $SOURCE),因为已获取了同名的应用程序
。
此类消息应让用户知道跳过了哪个依赖项。
如果两个传递依赖项具有相同的名称并且在同一级别上会怎样?
A
/ \
B C
| |
D1 D2
在这种情况下,D1
将接管 D2
,因为 B
在字典排序中位于 C
之前。这是一个完全任意的规则,但至少它是一个确保可重复获取的规则。
如果用户不同意结果,他们可以将 D2
提升到顶级并确保尽早选择它
A D2
/ \
B C
| |
D1 D2
这将产生 A
、B
、C
和 D2
。
Rebar3 将对包执行相同的算法,并且还将检测循环依赖项并在这些依赖项上出错。
_checkouts
目录中的依赖项将保持不变,并被视为顶级 OTP 应用程序。
锁定文件
锁定文件 (rebar.lock
) 是 Rebar3 将生成的唯一工件之一,它将位于 _build/
之外。它们应始终签入源代码控制。锁定文件包含有关代码依赖项的信息,包括源依赖项(如 git 中的依赖项)的不可变引用,以及它们的版本以及预期包哈希值(可用于防止镜像被劫持)。
目标是使用有关找到的依赖项的更准确信息,而不是仅通过配置文件获得的信息,例如,允许按需配置依赖项以从 main
更新,但同时锁定到稳定的测试版本。只有解锁或升级依赖项才能将其移动到更新或不同的版本。其想法是允许可重复构建,即使例如 Git 标签或分支被某人破坏性地修改。
Rebar3 还将在切换分支或获取传递依赖项时将锁定文件用作依赖项的真正权威来源(如果可用,它将从锁定文件中选择数据),而不是 rebar.config
文件。这样,当在 rebar.config
文件中使用松散的引用或版本时,我们可以在其他应用程序中携带安全测试状态。
预期格式向前和向后兼容。Rebar3 将根据存储的元数据格式注释锁定文件版本,并在使用旧版本的 Rebar3 读取新版本的锁定文件时发出警告。这可以告诉用户,在以后某个日期被认为重要的某些元数据将因使用旧版本的工具而丢失。
升级依赖项
每当获取和锁定依赖项时,Rebar3 将从源文件提取引用以将其固定到特定时间版本。依赖项应强制在后续构建中遵循该版本。
Rebar3 可以通过两种方式将先前安装的依赖项升级到更新的版本:将分支的引用转发到其最新版本(例如,将旧的 main
分支更新到新的 main
的 HEAD
用于源文件,或未指定版本的包的最新版本),或使用 rebar.config
文件中的新版本覆盖现有锁定依赖项。
在以下依赖项树中
A B
| |
C D
用户可以升级任一依赖项 (rebar3 upgrade A
和 rebar3 upgrade B
) 或同时升级两者 (rebar3 upgrade A,B
或 rebar3 upgrade
,这将升级所有依赖项)。
仅升级A
意味着A
和C
可能会被升级。对B
和D
的升级将被忽略。
升级依赖项可能会产生意想不到的效果和一些有趣的特殊情况。请考虑以下依赖项树
A B C1
/ \ / \ / \
D E F G H I2
| |
J K
|
I1
在获取上述依赖项树后,I2
将优先于I1
被选择,因为它更靠近项目根目录。但是,在从C1
升级到C2
之后,C2
不再需要依赖于I2
,Rebar3 将自动在A
树下获取I1
(即使不需要对A
进行升级)以提供正确的新的树。升级后的树将如下所示
A B C2
/ \ / \ |
D E F G H
| |
J K
|
I1
其中I2
不再存在于项目中,而I1
现在存在。
调用rebar3 unlock
将完全清空锁定文件。
您可以使用rebar3 tree
手动检查内容,它将显示当前的依赖项树。
$ rebar3 tree
...
├─ bootstrap-0.0.2 (git repo)
├─ dirmon-0.1.0 (project app)
├─ file_monitor-0.1 (git repo)
├─ peeranha-0.1.0 (git repo)
│ ├─ gproc-git (git repo)
│ ├─ interclock-0.1.2 (git repo)
│ │ ├─ bitcask-1.7.0 (git repo)
│ │ │ └─ lager-2.1.1 (hex package)
│ │ │ └─ goldrush-0.1.6 (hex package)
│ │ └─ itc-1.0.0 (git repo)
│ └─ merklet-1.0.0 (git repo)
├─ recon-2.2.2 (git repo)
└─ uuid-1.5.0 (git repo)
└─ quickrand-1.5.0 (git repo)
Elixir 依赖项
从 Rebar3 版本 3.7.0 和 Elixir 版本 1.7.4 开始,支持 Elixir 依赖项,这通过使用插件来实现。有关详细信息,请参阅相关的插件部分。