配置文件
在任何项目中,总会有一组根据运行的任务或运行任务的人员角色而期望的选项。
例如,在 Erlang 项目中,最常见的例子是仅在测试运行时所需的依赖项,例如模拟库或特定的测试工具或框架。
Rebar3 通过 *配置文件* 的概念解决了这些需求。配置文件是一组仅在一个特定上下文中使用的配置设置,覆盖或补充常规配置。它们的目的是能够支持多种开发用例,同时保持操作的可重复性,并且无需使用外部工具或环境变量来完成这些工作。
可以通过三种不同的方式指定运行的配置文件
- 调用 rebar 为
rebar3 as <profile> <command>
或rebar3 as <profile1>,<profile2> <command>
- 通过给定的 Rebar3 命令。例如,
eunit
和ct
命令 *始终* 会将test
配置文件添加到运行中。 REBAR_PROFILE
环境变量
任何这些形式(甚至同时使用所有形式)都会让 Rebar3 知道它应该以其中一个特殊配置文件运行,并相应地修改其配置。
配置文件配置可以在主 rebar.config
文件中这样指定
{profiles, [{ProfileName1, [Options, ...]},
{ProfileName2, [Options, ...]}]}.
例如,一个仅在测试运行时添加 meck
依赖项的测试配置文件可以定义为
{profiles, [{test, [{deps, [meck]}]}]}.
任何配置值都可以放入配置文件中,包括插件、编译器选项、发布选项等等。
示例
一个更完整的示例可能如下所示
{deps, [...]}.
{relx, [
...
]}.
{profiles, [
{prod, [
{erl_opts, [no_debug_info, warnings_as_errors]},
{relx, [{dev_mode, false}]}
]},
{native, [
{erl_opts, [{native, {hipe, o3}}]}
]},
{test, [
{deps, [meck]},
{erl_opts, [debug_info]}
]}
]}.
因此,这样的项目有 *四个* 不同的配置文件
-
default
,所有运行的事实上的配置文件,对应于整体rebar.config
文件 -
prod
,在这种情况下,可能用于生成没有符号链接的完整版本,并使用更严格的编译器选项 -
native
,强制使用 HiPE 编译,以加快数学代码的执行速度 -
test
,加载模拟库并在测试运行期间将调试信息保留在文件中。
这些可以在多种方式中组合。以下是一些运行示例
-
rebar3 ct
:将运行项目的通用测试套件。应用的配置文件顺序为default
,然后是test
,因为ct
要求使用test
配置文件。 -
rebar3 as test ct
:将与之前相同。配置文件仅应用一次。 -
rebar3 as native ct
:将以原生模式运行测试。配置文件的顺序将是default
,然后是native
,最后是test
(由运行的命令最后指定)。 -
rebar3 as test,native ct
:将与上述类似,但略有不同。应用配置文件时,Rebar3 首先扩展所有配置文件,并按正确的顺序应用它们。因此,此处的顺序将是default
,然后是test
,然后是native
。最后一个test
配置文件(因为ct
命令)被省略,因为它已经应用过了。这与调用rebar3 as native ct
并不完全等效,因为如果test
和native
配置文件都设置了冲突的选项,则配置文件顺序变得很重要。 -
rebar3 release
将仅以default
配置文件构建版本。 -
rebar3 as prod release
将构建不带开发模式的版本,并使用更严格的编译器选项。 -
rebar3 as prod, native release
将与上一个命令一样构建版本,但同时也将模块编译为原生模式。 -
rebar3 as prod release
以及环境中的REBAR_PROFILE=native
,将与上一个命令一样构建版本,但native
将在prod
之前应用。
因此,配置文件的应用顺序为
default
REBAR_PROFILE
值(如果有)- 命令行
as
部分中指定的配置文件 - 每个单独命令指定的配置文件
因此,配置文件是一种以上下文方式指定配置子集的可组合方法。
📘
锁定依赖项
仅
rebar.config
最顶层(default
配置文件)列出的依赖项将保存到rebar.lock
中。其他依赖项不会被锁定。如果有人想要“锁定生产环境”(意味着使用与生产相关的配置文件),答案是保留默认配置文件并使用 版本发布,它会生成可随时重用的已编译工件。
选项合并算法
通常很难尝试自动合并所有配置选项。不同的工具或命令会以不同的方式期望它们,作为元组列表、proplist 或键值对,以转换为某种字典。
为了尽可能支持最通用的形式,Rebar3 将它们视为 proplist 和元组列表的松散组合。这意味着以下所有选项都被视为具有键 native
native
{native, {hipe, o3}}
{native, still, supported}
即使其中一些可能不受工具支持。例如,Erlang 编译器支持将宏定义为 {d, 'MACRONAME'}
或 {d, 'MACRONAME', MacroValue}
,但不支持 d
单独使用,但它确实支持 native
和 {native, {hipe, o3}}
。
Rebar3 正确支持所有这些形式并以函数方式合并它们。让我们以以下配置文件为例
{profiles, [
{prod, [
{erl_opts, [no_debug_info, warnings_as_errors]},
]},
{native, [
{erl_opts, [{native, {hipe, o3}}, {d, 'NATIVE'}]}
]},
{test, [
{erl_opts, [debug_info]}
]}
]}.
以不同的顺序应用配置文件将产生不同的 erl_opts
选项列表
rebar3 as prod,native,test <command>
:[debug_info, {d, 'NATIVE'}, {native, {hipe, o3}}, no_debug_info, warnings_as_errors]
rebar3 as test,prod,native <command>
:[{d, 'NATIVE'}, {native, {hipe, o3}}, no_debug_info, warnings_as_errors, debug_info]
rebar3 as native,test,prod <command>
:[no_debug_info, warnings_as_errors, debug_info, {d, 'NATIVE'}, {native, {hipe, o3}}]
rebar3 as native,prod,test <command>
:[debug_info, no_debug_info, warnings_as_errors, {d, 'NATIVE'}, {native, {hipe, o3}}]
请注意,最后应用的配置文件产生列表中的第一个元素,并且每个配置文件列表中的元素将根据其键进行排序。
这将允许 Rebar3 命令按正确的顺序获取元素,同时仍然支持需要许多元素共享相同键的多值列表(例如 [{d, 'ABC'}, {d, 'DEF'}]
,它们是两个独立的宏!)。不支持重复元素的命令可以在处理第一个元素后停止处理它们,而那些从它们构建字典(或映射)的命令可以选择按原样插入它们,或者可以先安全地反转列表(如果处理的最后一个元素成为映射中的最终元素)。
所有配置文件合并规则都是以这种安全的方式处理的。插件编写者应该了解这些规则并相应地进行规划。
请注意,在实践中,Erlang 编译器与 debug_info
和 no_debug_info
并不兼容(这甚至不是一个真正的选项,而是由 Rebar3 添加的)。Rebar3 做了一些魔法来消除这些特定值以满足编译器的要求,但不会将此礼貌扩展到所有工具。设计插件以使用 {OptionName, true|false}
通常是一个好主意。
🚧
依赖项和配置文件
依赖项将始终使用应用于其配置的
prod
配置文件进行编译。任何依赖项都不会使用其他(当然除了default
之外)配置文件。即使它们配置为prod
,依赖项仍将被获取到其声明下配置文件的配置文件目录中。例如,顶层deps
中的依赖项将在_build/default/lib
下,而test
配置文件下的依赖项将被获取到_build/test/lib
。两者都将使用应用的prod
配置文件配置进行编译。