发布即真理

我已经可以执行了吗?

我们已经走了多远。所有这些工作,所有这些概念,我们还没有发布一个 Erlang 可执行文件。您可能同意我的观点,即启动和运行 Erlang 系统需要付出很多努力,尤其是与许多只需调用编译器即可运行的语言相比。

A slice of pizza

当然,这完全正确。我们可以编译文件,运行应用程序,检查一些依赖项,处理崩溃等等,但如果没有一个功能完备的 Erlang 系统可以轻松部署或与其一起交付,它就没有什么用。拥有美味的披萨有什么用,如果它只能冷着送达?(喜欢吃冷披萨的人可能会感到被排斥。我很抱歉。)

OTP 团队在确保真正的系统能够投入使用方面并没有让我们孤军奋战。OTP 版本是帮助将应用程序与最少的资源和依赖项打包在一起的系统的一部分。

修复泄漏的管道

对于我们的第一个版本,我们将重复使用我们之前的章节中的 ppoolerlcount 应用程序。但是,在我们这样做之前,我们需要在某些地方更改一些内容。如果您正在跟着这本书学习并编写自己的代码,您可能想将我们的两个应用程序复制到一个名为 release/ 的新目录中,对于本章的其余部分,我将假设您已经完成了此操作。

A leaky pipe with brown liquid dripping into a metal bucket

让我非常困扰的 erlcount 的第一个问题是,一旦它运行完毕,VM 就会一直运行,什么也不做。我们可能希望大多数应用程序永远运行,但这次并非如此。保持运行是有意义的,因为我们可能想在 shell 中玩一些东西,并且需要手动启动应用程序,但现在这不再需要了。

因此,我们将添加一个命令,该命令将以有序的方式关闭 BEAM 虚拟机。最好的地方是在 erlcount_dispatch.erl 自己的终止函数中,因为在获得结果后会调用它。完美的关闭一切的函数是 init:stop/0。这个函数非常复杂,但会负责以顺序的方式终止我们的应用程序,还会为我们清除文件描述符、套接字等。新的停止函数现在应该看起来像这样

terminate(_Reason, _State, _Data) ->
    init:stop().

这就是代码本身。我们还有更多工作要做。当我们在过去的两个章节中定义我们的应用程序文件时,我们只使用了启动它们所需的绝对最少信息量。还需要一些字段,这样 Erlang 才能不完全让我们生气。

首先,用于构建版本的 Erlang 工具要求我们在应用程序描述中更精确一些。您看,尽管版本工具不理解文档,但它们仍然对开发者在代码中没有留下任何关于应用程序作用的想法感到本能的恐惧。出于这个原因,我们需要在我们的 ppool.apperlcount.app 文件中添加一个 description 元组。

对于 ppool,添加以下内容

{description, "Run and enqueue different concurrent tasks"}

对于 erlcount

{description, "Run regular expressions on Erlang source files"}

现在,当我们检查不同的系统时,我们将能够更好地了解发生了什么。

最细心的读者还会记得,我在某个时候提到过,所有应用程序都依赖于 stdlibkernel。但是,我们的两个应用程序文件都没有提到其中任何一个。让我们将这两个应用程序添加到我们的每个应用程序文件中。这将需要在 ppool 应用程序文件 中添加以下元组

{applications, [stdlib, kernel]}

并将这两个应用程序添加到现有的 erlcount 应用程序文件中,得到 {applications, [stdlib, kernel, ppool]}

不要喝太多酷乐 aid
虽然这在手动启动版本时(甚至在使用 systools 生成版本时,我们很快就会看到)几乎没有影响,但将这两个库添加到列表中至关重要。

使用 reltool(我们将在本章中看到的另一个工具)生成版本的人员肯定需要这些应用程序,以便他们的版本能够正常运行,甚至能够以体面的方式关闭 VM。我不是在开玩笑,这是必须的。我在写本章时忘了做这件事,花了一晚上的时间试图找出到底哪里出了问题,结果发现是我一开始就没有做对。

有人可能会说,理想情况下,Erlang 的版本系统可以隐式添加这些应用程序,因为几乎所有应用程序(除了非常特殊的用例)都将依赖于它们。唉,他们没有。我们必须忍受这个。

我们已经实现了终止,并更新了应用程序文件等等。在开始使用版本之前,最后一步是编译所有应用程序。依次运行您的 Emakefile(使用 erl -make)在每个包含一个应用程序的目录中。否则,Erlang 的工具将不会为您执行此操作,您最终会得到一个没有代码可以运行的版本。哎哟。

使用 Systools 发布

systools 应用程序是构建 Erlang 版本的最简单方法。它是 Erlang 版本的轻松烤箱®。要从 systools 烤箱中获得美味的版本,您首先需要一个基本食谱和一份配料清单。如果我要手动描述为我们的 erlcount 应用程序创建一个成功的最小 Erlang 版本的配料,它看起来会有点像这样

erlcount 1.0.0 的配料
  • 您选择的 Erlang 运行时系统 (ERTS)。
  • 一个标准库
  • 一个内核库
  • ppool 应用程序,它不应该失败
  • erlcount 应用程序。

我有没有说过我是一个糟糕的厨师?我不确定我是否能煎薄饼,但我至少知道如何构建 OTP 版本。使用 systools 创建 OTP 版本的配料清单看起来像这个文件,名为 erlcount-1.0.rel,并放置在 release/ 目录的顶层

{release,
 {"erlcount", "1.0.0"},
 {erts, "5.8.4"},
 [{kernel, "2.14.4"},
  {stdlib, "1.17.4"},
  {ppool, "1.0.0", permanent},
  {erlcount, "1.0.0", transient}]}.

这只是告诉您与我的手动食谱相同的内容,尽管我们可以指定我们希望应用程序如何启动(temporarytransientpermanent)。我们还可以指定版本,以便根据我们的需要,我们可以混合和匹配来自不同 Erlang 版本的不同库。要获取所有版本号,您可以执行以下调用序列

$ erl
Erlang R14B03 (erts-5.8.4) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.4  (abort with ^G)
1> application:which_applications().
[{stdlib,"ERTS  CXC 138 10","1.17.4"},
 {kernel,"ERTS  CXC 138 10","2.14.4"}]

因此,对于那个,我运行的是 R14B03。您可以在发布号之后看到 ERTS 版本(版本是 5.8.4)。然后通过在运行的系统上调用 application:which_applications(),我可以看到 kernel(2.14.4)和 stdlib(1.17.4)所需的两个版本。这些数字会因 Erlang 版本而异。但是,明确指定您需要的版本很有帮助,因为这意味着如果您有多个不同的 Erlang 安装,您可能仍然只希望使用较旧版本的 stdlib,这样不会严重影响您正在做的事情。

A chocolate cupcake with pink creamy topping in a purplish paper, with a face, beard, legs and high heel shoes (pink)

您还会注意到,我选择将版本命名为 erlcount 并使其版本为 1.0.0。这与 ppoolerlcount 应用程序无关,这两个应用程序也都在运行 1.0.0 版本,如它们的应用程序文件中所指定。

所以现在我们已经编译了所有应用程序,我们有了配料清单和一个奇妙的隐喻的轻松烤箱®。我们需要的是实际的食谱。

现在将有一些概念登场。食谱会告诉你一些事情:以什么顺序添加配料,如何混合它们,如何烹饪它们等等。关于添加顺序的部分由我们每个应用程序文件中的依赖项列表涵盖。systools 应用程序将足够聪明,可以查看应用程序文件并确定需要在什么之前运行什么。

Erlang 的虚拟机可以从一个称为引导文件的内容中获取基本配置来启动自己。事实上,当您从 shell 中启动自己的 erl 应用程序时,它会隐式调用带有默认引导文件的 Erlang 运行时系统。该引导文件将提供基本的指令,例如“加载标准库”、“加载内核应用程序”、“运行给定的函数”等等。该引导文件是一个二进制文件,它是由一个称为 引导脚本 的内容创建的,其中包含表示这些指令的元组。我们将看到如何编写这样的引导脚本。

首先,我们从

{script, {Name, Vsn},
 [
  {progress, loading},
  {preLoaded, [Mod1, Mod2, ...]},
  {path, [Dir1,"$ROOT/Dir",...]}.
  {primLoad, [Mod1, Mod2, ...]},
  ...

开玩笑。没有人真正花时间这样做,我们也不会。引导脚本是可以通过 .rel 文件轻松生成的。只需从 release/ 目录中启动 Erlang VM 并调用以下行

$ erl -env ERL_LIBS .
...
1> systools:make_script("erlcount-1.0", [local]).
ok

现在,如果您查看您的目录,您将看到许多新文件,包括 erlcount-1.0.scripterlcount-1.0.boot 文件。这里,local 选项意味着我们希望版本能够从任何地方运行,而不仅仅是从当前安装位置运行。还有很多 其他选项 可以看到,但是因为 systools 并没有像 reltool(在接下来的部分中)那样强大,所以我们不会深入研究它们。

无论如何,我们有了引导脚本,但还不够用于分发我们的代码。回到您的 Erlang shell 并运行以下命令

2> systools:make_tar("erlcount-1.0", [{erts, "/usr/local/lib/erlang/"}]).
ok

或者,在 Windows 7 上

2> systools:make_tar("erlcount-1.0", [{erts, "C:/Program Files (x86)/erl5.8.4"}]).
ok

在这里,systools 将查找您的版本文件和 Erlang 运行时系统(由于 erts 选项)。如果您省略 erts 选项,则版本将不可执行,并且将依赖于系统上已经安装了 Erlang。

运行上面的函数调用将创建一个名为 erlcount-1.0.tar.gz 的存档文件。解压缩文件,您应该看到一个类似于这样的目录

erts-5.8.4/
lib/
releases/

erts-5.8.4/ 目录将包含运行时系统。lib/ 目录包含我们需要的全部应用程序,releases 包含引导文件等等。

移动到您解压缩这些文件的目录中。从那里,我们可以为 erl 构建一个命令行调用。首先,我们指定在哪里找到 erl 可执行文件和引导文件(不带 .boot 扩展名)。在 Linux 中,这给了我们

$ ./erts-5.8.4/bin/erl -boot releases/1.0.0/start

在 Windows 7 上,使用 Windows PowerShell,该命令对我来说是一样的。

不要喝太多酷乐 aid
不能保证版本在任何系统上都能工作。如果您使用的是纯 Erlang 代码,那么该代码将是可移植的。问题是,您与它一起提供的 ERTS 可能无法正常工作:您要么需要为许多不同的平台创建许多二进制包以便大规模定义,要么只提供 BEAM 文件而不提供关联的 ERTS,并要求人们使用他们自己的计算机上的 Erlang 系统来运行它们。

您可以选择使用绝对路径,如果希望命令在计算机上的任何位置都能运行。不过,现在不要运行它。它将毫无用处,因为当前目录中没有要分析的源文件。如果您使用绝对路径,则可以转到要分析的目录并从那里调用文件。如果您使用的是相对路径(就像我一样),并且您还记得我们的erlcount应用程序,我们使其可以配置代码将要扫描的目录。让我们将-erlcount directory "'<path to the directory>'"添加到命令中。然后,因为我们希望它不像 Erlang,所以让我们添加-noshell参数。这在我的电脑上给出了类似这样的东西

$ ./erts-5.8.4/bin/erl -boot releases/1.0.0/start -erlcount directory '"/home/ferd/code/otp_src_R14B03/"' -noshell
Regex if\s.+-> has 3846 results
Regex case\s.+\sof has 55894 results

使用绝对文件路径,我得到了这样的东西

$ /home/ferd/code/learn-you-some-erlang/release/rel/erts-5.8.4/bin/erl -boot /home/ferd/code/learn-you-some-erlang/release/rel/releases/1.0.0/start -noshell

无论我在哪里运行它,那都是将要扫描的目录。将它包装在一个 shell 脚本或批处理文件中,您应该可以开始使用了。

使用 Reltool 发布

systools 有很多令人讨厌的地方。我们对如何完成事情几乎没有控制权,坦率地说,按照目前的方式运行它们有点令人讨厌。手动指定引导文件路径等等很痛苦。此外,这些文件有点大。整个发布在磁盘上占用超过 20mb,如果我们要打包更多应用程序,情况会更糟。使用reltool可以做得更好,因为我们获得了更多的功能,尽管代价是增加了复杂性。

Reltool 从一个看起来像这样的配置文件中运行

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "erlcount", "1.0.0",
     [kernel,
      stdlib,
      {ppool, permanent},
      {erlcount, transient}
     ]},
    {boot_rel, "erlcount"},
    {relocatable, true},
    {profile, standalone},
    {app, ppool, [{vsn, "1.0.0"},
                  {app_file, all},
                  {debug_info, keep}]},
    {app, erlcount, [{vsn, "1.0.0"},
                     {incl_cond, include},
                     {app_file, strip},
                     {debug_info, strip}]}
]}.

Behold Erlang 的用户友好性!老实说,没有简单的方法可以让我们熟悉 Reltool。您需要一次性使用所有这些选项,否则什么也无法工作。这听起来可能很混乱,但其中有逻辑。

首先,Reltool 将采用不同级别的信息。第一级将包含发布范围的信息。第二级将是特定于应用程序的,然后允许在特定于模块的级别上进行细粒度控制

The levels of reltools: the release levels contains environment, applications and properties of the releases. The level under that, Applications, contains what to include, compression, debug_info, app files, etc. The last (and lowest) level, the Modules, contains what to include and debug_info

对于每个级别,就像在之前的图表中一样,将提供不同的选项。与其采用包含所有可能选项的百科全书式方法,不如访问一些基本选项,然后根据您在应用程序中可能正在寻找的内容访问一些可能的配置。

第一个选项帮助我们摆脱了需要坐在给定目录中或将正确的-env参数设置为 VM 的烦恼。该选项是lib_dirs,它接受一个应用程序所在的目录列表。因此,实际上,与其添加-env ERL_LIBS <list of directories>,不如放入{lib_dirs, [ListOfDirectories]},您将获得相同的结果。

Reltool 配置文件的另一个重要选项是rel。这个元组与我们为systools编写的.rel文件非常相似。在上面的演示文件中,我们有

{rel, "erlcount", "1.0.0",
 [kernel,
  stdlib,
  {ppool, permanent},
  {erlcount, transient}
 ]},

这就是我们需要告诉我们哪些应用程序需要正确启动的内容。在该元组之后,我们要添加一个形如

{boot_rel, "erlcount"}

这将告诉 Reltool,每当有人运行发布中包含的erl二进制文件时,我们希望从erlcount发布中启动应用程序。仅使用这 3 个选项(lib_dirsrelboot_rel),我们就可以获得有效的发布。

为此,我们将这些元组放入 Reltool 可以解析的格式中

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "erlcount", "1.0.0",
     [kernel,
      stdlib,
      {ppool, permanent},
      {erlcount, transient}
     ]},
    {boot_rel, "erlcount"}
]}.

是的,我们只是将它们包装在一个{sys, [Options]}元组中。在我的例子中,我将它保存在release/目录中的名为erlcount-1.0.config的文件中。您可以将其放在任何您想要的地方(除了/dev/null,即使它具有出色的写入速度!)。

然后,我们需要打开一个 Erlang shell

1> {ok, Conf} = file:consult("erlcount-1.0.config").
{ok,[{sys,[{lib_dirs,["/home/ferd/code/learn-you-some-erlang/release/"]},
           {rel,"erlcount","1.0.0",
                [kernel,stdlib,{ppool,permanent},{erlcount,transient}]},
           {boot_rel,"erlcount"}]}]}
2> {ok, Spec} = reltool:get_target_spec(Conf).
{ok,[{create_dir,"releases",
   ...
3> reltool:eval_target_spec(Spec, code:root_dir(), "rel").
ok

这里的第一步是读取配置并将它绑定到Conf变量。然后,我们将它发送到reltool:get_target_spec(Conf)。该函数将需要一段时间才能运行,并且会返回过多的信息,无法让我们继续。我们不关心,只是将结果保存在Spec中。

第三个命令获取规范并告诉 Reltool“我希望您使用我的 Erlang 安装所在的任何路径获取我的发布规范,并将它塞到“rel”目录中”。就这样。查看rel目录,您应该在其中看到很多子目录。

现在我们不关心,可以直接调用

$ ./bin/erl -noshell
Regex if\s.+-> has 0 results
Regex case\s.+\sof has 0 results

啊,运行起来简单多了。您可以将这些文件放在几乎任何地方,只要它们保持相同的文件树,并且可以从任何地方运行它们。

a squid's tentacle being cut off so it could free itself from a pair of handcuffs

您是否注意到有什么不同?我希望你有。我们不需要指定任何版本号。Reltool 在这方面比 Systools 更聪明。如果您没有指定版本,它将自动在您的路径(在code:root_dir()返回的目录中或您放入lib_dirs元组中的目录中)中查找可能的最新版本。

但是,如果我不时髦、不酷、不潮流,而且不关注最新的应用程序,而是复古爱好者呢?我仍然在这里穿着我的迪斯科裤,我想使用旧的 ERTS 版本和旧的库版本,您明白吗(我在 1977 年从未像现在这样活着!)

值得庆幸的是,Reltool 可以处理需要与旧版本 Erlang 一起工作的发布。尊重长辈是 Erlang 工具的重要理念。

如果您安装了旧版本的 Erlang,则可以在配置文件中添加一个{erts, [{vsn, Version}]}条目

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {erts, [{vsn, "5.8.3"}]},
    {rel, "erlcount", "1.0.0",
     [kernel,
      stdlib,
      {ppool, permanent},
      {erlcount, transient}
     ]},
    {boot_rel, "erlcount"}
]}.

现在,您需要清除rel/目录以删除较新的发布。然后,您再次运行相当难看的调用序列

4> f(),
4> {ok, Conf} = file:consult("erlcount-1.0.config"),
4> {ok, Spec} = reltool:get_target_spec(Conf),
4> reltool:eval_target_spec(Spec, code:root_dir(), "rel").
ok

这里提醒一下,f()用于解除 shell 中的变量绑定。现在,如果我转到rel目录并调用$ ./bin/erl,我将获得以下输出

Erlang R14B02 (erts-5.8.3) [source] ...

Eshell V5.8.3  (abort with ^G)
1> Regex if\s.+-> has 0 results
Regex case\s.+\sof has 0 results

很棒。这在版本 5.8.3 上运行,即使我还有更新的版本可用。啊哈,哈哈,哈哈,活着。

注意:如果您查看rel/目录,您会发现它与 Systools 中的内容非常相似,但其中一个区别将出现在lib/目录中。它现在将包含很多目录和.ez文件。这些目录将包含您在进行开发时所需的include/文件,以及在需要将文件保存在那里时的priv/目录。.ez文件只是压缩的 beam 文件。Erlang VM 将在运行时为您解压缩它们,这只是为了让事情更轻。

但是,我的其他模块呢?

啊,现在我们从发布范围的设置转向与应用程序相关的设置。还有很多发布范围的选项要看,但我们现在正处于热潮中,无法停止。我们将在一段时间后重新讨论它们。对于应用程序,我们可以通过添加更多元组来指定版本

{app, AppName, [{vsn, Version}]}

并在每个需要它的应用程序中添加一个。

现在我们有了更多选项。我们可以指定是否希望发布包含调试信息、将其剥离、尝试创建更紧凑的应用程序文件或相信我们的定义,要包含或排除的内容,在包含应用程序和您的应用程序可能依赖的模块时要多严格等等。此外,这些选项通常可以在发布范围和应用程序范围内定义,因此您可以指定默认值,然后覆盖值。

以下是快速概述。如果您觉得它很复杂,只需跳过它,您将看到一些要遵循的食谱。

仅发布选项

{lib_dirs, [ListOfDirs]}
要查找库的目录。
{excl_lib, otp_root}
在 R15B02 中添加,此选项允许您将 OTP 应用程序指定为发布的一部分,而无需在最终发布中包含来自标准 Erlang/OTP 路径的任何内容。这允许您创建本质上是从给定系统中安装的现有虚拟机启动的库的发布。使用此选项时,您现在必须以$ erl -boot_var RELTOOL_EXT_LIB <path to release directory>/lib -boot <path to the boot file>方式启动虚拟机。这将允许发布使用当前的 Erlang/OTP 安装,但使用您自己为自定义发布提供的库。
{app, AppName, [AppOptions]}
将允许您指定应用程序范围的选项,通常比发布范围的选项更具体。
{boot_rel, ReleaseName}
使用erl可执行文件启动的默认发布。这意味着我们不需要在调用erl时指定引导文件。
{rel, Name, Vsn, [Apps]}
要包含在发布中的应用程序。
{relocatable, true | false}
可以从任何地方运行发布,或者只能从系统中的硬编码路径运行发布。默认情况下,它设置为true,我倾向于保持这种状态,除非有充分的理由这样做。您会知道什么时候需要它。
{profile, development | embedded | standalone}
此选项将作为一种方法来指定基于发布类型默认的*_filters(下面描述)。默认情况下,使用development。它将从每个应用程序和 ERTS 中盲目地包含更多文件。standalone配置文件将更加严格,而embedded配置文件将更加严格,它会删除更多默认的 ERTS 应用程序和二进制文件。

发布和应用程序范围的选项

请注意,对于所有这些选项,在应用程序级别设置选项只会覆盖您在系统级别提供的 value。

{incl_sys_filters, [RegularExpressions]}
{excl_sys_filters, [RegularExpressions]}
检查文件是否与包含过滤器匹配,而与排除过滤器不匹配,然后再将其包含。您可能会以这种方式删除或包含特定文件。
{incl_app_filters, [RegularExpressions]}
{excl_app_filters, [RegularExpressions]}
类似于incl_sys_filtersexcl_sys_filters,但适用于特定于应用程序的文件
{incl_archive_filters, [RegularExpressions]}
{excl_archive_filters, [RegularExpressions]}
指定必须包含或排除到.ez存档文件(很快就会介绍更多内容)中的顶级目录。
{incl_cond, include | exclude | derived}
决定如何包含不一定在rel元组中指定的应用程序。选择include意味着 Reltool 将包含它可以找到的所有东西。选择derived意味着 Reltool 将只包含它检测到的可以由rel元组中的任何应用程序使用的应用程序。这是默认值。选择exclude意味着您默认情况下将完全不包含任何应用程序。您通常会在想要最小包含时将其设置为发布级别,然后在每个应用程序的基础上覆盖它,以便添加您想添加的内容。
{mod_cond, all | app | ebin | derived | none}
这控制着模块包含策略。选择`none`意味着不会保留任何模块。这不是很有用。`derived`选项意味着 Reltool 会尝试找出哪些模块被已经包含的其他模块使用,并在这种情况下添加它们。将选项设置为`app`意味着 Reltool 会保留应用程序文件中提到的所有模块,以及派生的模块。将其设置为`ebin`将保留`ebin/`目录中的模块和派生的模块。使用`all`选项将是使用`ebin`和`app`的混合。这是默认值。
{app_file, keep | strip | all}
此选项管理在包含应用程序时如何管理应用程序文件。选择`keep`将保证发布中使用的应用程序文件与您为应用程序编写的文件相同。这是默认选项。如果您选择`strip`,Reltool 将尝试生成一个新的应用程序文件,该文件将删除您不想在其中包含的模块(那些被过滤器和其他选项排除的模块)。选择`all`将保留原始文件,但也会在其中添加专门包含的模块。`all`的好处是它可以为您生成应用程序文件,即使没有可用文件。

模块特定选项

{incl_cond, include | exclude | derived}
这允许您覆盖在发布级别和应用程序级别定义的`mod_cond`选项。

所有级别选项

这些选项在所有级别上都有效。级别越低,优先级越高。

{debug_info, keep | strip}
假设您的文件是使用`debug_info`编译的(我建议您这样做),此选项允许您决定是保留它还是丢弃它。`debug_info`在您想要反编译文件、调试它们等时非常有用,但会占用一些空间。

太密集了

是的,信息很多。我甚至没有涵盖所有可能的选项,但这仍然是一个不错的参考。如果您想要完整的内容,请查看官方文档

A complex Rube Goldberg machine to represent the OTP Release process

食谱

现在,我们将提供一些根据您想要获得的内容而采取的操作的一般提示和技巧。

开发版本

获取开发内容应该相对容易。通常默认设置就足够了。坚持获取我们之前已经提到的基本项目,就足够了。

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "erlcount", "1.0.0", [kernel, stdlib, ppool, erlcount]},
    {boot_rel, "erlcount"}
]}.

Reltool 会负责导入足够的内容以确保正常工作。在某些情况下,您可能希望从常规 VM 中获取所有内容。您可能正在为一个团队分发整个 VM,其中包含一些库。在这种情况下,您想要做的是类似于以下内容

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "start_clean", "1.0.0", [kernel, stdlib]},
    {incl_cond, include},
    {debug_info, keep}
]}.

通过将`incl_cond`设置为`include`,在当前 ERTS 安装和`lib_dirs`中找到的所有应用程序都将成为您发布的一部分。

注意: 当没有指定`boot_rel`时,您必须有一个名为`start_clean`的发布,Reltool 才能正常工作。当您启动关联的`erl`文件时,该文件将被默认选择。

如果我们要排除一个特定的应用程序,比如`megaco`,因为我从来没有研究过它,我们可以获取一个类似于这样的文件

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "start_clean", "1.0.0", [kernel, stdlib]},
    {incl_cond, include},
    {debug_info, keep},
    {app, megaco, [{incl_cond, exclude}]}
]}.

在这里,我们可以指定一个或多个应用程序(每个应用程序都有自己的`app`元组),它们中的每一个都覆盖了在发布级别设置的`incl_cond`设置。因此,在本例中,我们将包含除`megaco`之外的所有内容。

仅导入或导出库的一部分

在我们的发布中,发生的一件令人讨厌的事情是,像`ppool`这样的应用程序以及其他应用程序,即使它们不需要这些文件,也会在发布中保留它们的测试文件。您可以通过进入`rel/lib/`并解压缩`ppool-1.0.0.ez`来查看它们(您可能需要先更改扩展名)。

要摆脱这些文件,最简单的方法是指定排除过滤器,例如

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "start_clean", "1.0.0", [kernel, stdlib, ppool, erlcount]},
    {excl_app_filters, ["_tests.beam$"]}
]}.

当您只想导入应用程序的特定文件时,比如我们的`erlcount_lib`是为了其功能,而不想从那里导入其他任何东西,事情会变得更加复杂

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {rel, "start_clean", "1.0.0", [kernel, stdlib]},
    {incl_cond, derived}, % exclude would also work, but not include
    {app, erlcount, [{incl_app_filters, ["^ebin/erlcount_lib.beam$"]},
                     {incl_cond, include}]}
]}.

在本例中,我们从`{incl_cond, include}`切换到更严格的`incl_cond`。这是因为如果您规模很大,并且使用耙子获取所有内容,那么包含单个库的唯一方法是使用`excl_app_filters`排除所有其他库。但是,当我们的选择更严格时(在本例中,我们是`derived`,并且不会包含`erlcount`,因为它不是`rel`元组的一部分),我们可以专门告诉发布仅包含与`erlcount_lib`相关的正则表达式匹配的文件的`erlcount`应用程序。这引发了一个问题:如何创建尽可能严格的应用程序?

为拥有伟大心灵的程序员提供更小的应用程序

这是 Reltool 变得更加复杂的地方,它使用了一个相当冗长的配置文件

{sys, [
    {lib_dirs, ["/home/ferd/code/learn-you-some-erlang/release/"]},
    {erts, [{mod_cond, derived},
            {app_file, strip}]},
    {rel, "erlcount", "1.0.0", [kernel, stdlib, ppool, erlcount]},
    {boot_rel, "erlcount"},
    {relocatable, true},
    {profile, embedded}, 
    {app_file, strip},
    {debug_info, strip},
    {incl_cond, exclude},
    {excl_app_filters, ["_tests.beam$"]},
    {app, stdlib, [{incl_cond, include}]},
    {app, kernel, [{incl_cond, include}]},
    {app, ppool, [{vsn, "1.0.0"}, {incl_cond, include}]},
    {app, erlcount, [{vsn, "1.0.0"}, {incl_cond, include}]}
]}.

哦,还有更多的事情要做。我们可以看到,在`erts`的情况下,我们要求 Reltool 只保留其中必要的东西。将`mod_cond`设置为`derived`,将`app_file`设置为`strip`将要求 Reltool 检查并只保留用于其他用途的内容。这就是为什么在发布级别也使用`{app_file, strip}`的原因。

a crate with a sign that sayz '.ez'

配置文件设置为`embedded`。如果您查看之前案例中的`.ez`档案,它们包含源文件、测试目录等。当切换到`embedded`时,只保留文件、二进制文件和`priv/`目录。我还在删除所有文件中的`debug_info`,即使它们是用它编译的。这意味着我们将失去一些调试能力,但会减小文件的大小。

我仍然在删除测试文件,并设置了一些东西,以便在明确告知包含之前不会包含任何应用程序(`{incl_cond, exclude}`)。然后,我在每个我想包含的应用程序中覆盖此设置。如果缺少任何东西,Reltool 会警告您,因此您可以尝试移动东西并玩弄设置,直到您获得想要的结果。这可能涉及到在某些应用程序设置中使用`{mod_cond, derived}`,以便保留一些应用程序的最小文件。

最终有什么区别?我们一些更通用的版本会超过 35MB。上面描述的那个被减少到不到 20MB。我们减少了很大一部分。但是,大小仍然相当大。这是因为 ERTS 本身就占用了 18.5MB。如果您愿意,您可以更深入地挖掘,并真正微观管理 ERTS 的构建方式以获得更小的东西。或者,您可以在 ERTS 中选择一些您知道应用程序不会使用的二进制文件:用于脚本的可执行文件、Erlang 的远程运行、测试框架中的二进制文件、不同的运行命令(有或没有 SMP 的 Erlang 等)。

最轻的版本是假设其他用户已经安装了 Erlang 的版本——当您选择此选项时,您需要将`rel/`目录的内容作为您ERL_LIBS环境变量的一部分添加,并自己调用启动文件(有点像使用 systools),但它会起作用。程序员可能希望将它包装在脚本中以使其运行。

注意: 如今,Erlang 程序员似乎非常喜欢由名为rebar3的工具为您处理所有这些发布的想法。Rebar3 将充当 Erlang 编译器的包装器,并处理发布。了解 Reltool 的工作原理不会有任何损失——Rebar3 使用更高级别的发布抽象,了解 Reltool 使得理解任何其他工具的工作原理变得容易。

从发布中发布

好吧,这就是处理发布的两种主要方式。这是一个复杂的话题,但它是一种处理事务的标准方式。应用程序可能足以满足许多读者的需求,并且在一段时间内坚持使用它们没有坏处,但偶尔发布可能会很有用,如果您想让您的运维人员更喜欢您,因为您知道(或至少对)如何部署 Erlang 应用程序时需要它们。

当然,还有什么比没有停机时间更能使您的运维人员高兴呢?下一个挑战是在发布运行时进行软件升级。

Parody of the poster of the Grease movie, where 'Grease' is replaced by 'Release', Olivia Newton-Jogn by Joe Armstrong and John Travolta by Bjarne Dacker