无论您是在 Erlang 中启动一个长时间运行的类似守护进程的应用程序,还是使用 CLI 命令,您始终需要以下内容:
-
erts 应用程序 - 特定版本中的虚拟机和内核
- Erlang OTP 应用程序
- 您的应用程序的依赖项
- CLI 入口点
然后在任何一种情况下,CLI 入口点都必须启动 Erlang VM 并执行它应该在给定情况下执行的代码。然后它将退出或继续运行 - 后者用于长时间运行的应用程序。
CLI 入口点可以是启动 Erlang VM 的任何东西,例如escript 脚本、sh、bash 等。escript 相对于通用 shell 的明显优势是 escript 已经在 Erlang VM 的上下文中执行,因此无需处理启动/停止虚拟机。
您可以通过两种方式启动 Erlang VM:
- 使用系统范围的 Erlang VM
- 使用embedded Erlang 发布
在第一种情况下,您的包中没有提供 erts 或任何 OTP 应用程序,您只需将特定的 Erlang 版本作为您的应用程序的依赖项。在第二种情况下,您提供 erts 和所有必需的 OTP 应用程序以及应用程序在包中的依赖项。
在第二种情况下,您还需要在启动 VM 时正确设置code root。但这很容易,请参阅 Erlang 用于启动系统范围 VM 的 erl 脚本:
# location: /usr/local/lib/erlang/bin/erl
ROOTDIR="/usr/local/lib/erlang"
BINDIR=$ROOTDIR/erts-7.2.1/bin
EMU=beam
PROGNAME=`echo $0 | sed 's/.*\///'`
export EMU
export ROOTDIR
export BINDIR
export PROGNAME
exec "$BINDIR/erlexec" ${1+"$@"}
这可以通过脚本来处理,例如 node_package 工具,Basho 使用它来为所有主要操作系统打包他们的 Riak 数据库。我正在维护其中的my own fork,我正在使用我自己的构建工具builderl。我只是这么说,所以你知道,如果我设法定制它,你也将能够做到这一点:)
一旦 Erlang VM 启动,您的应用程序应该能够加载和启动任何应用程序,无论是 Erlang 或您的应用程序提供的(包括您提到的 mylib 库)。以下是一些如何实现这一目标的示例:
escript 示例
请参阅 this builderl.esh example 我如何处理从 builderl 加载其他 Erlang 应用程序。 escript 脚本假定 Erlang 安装与执行它的文件夹相关。当它是另一个应用程序的一部分时,例如humbundee,load_builderl.hrl 包含文件编译并加载bld_load,这反过来又用bld_load:boot/3 加载所有剩余的模块。请注意我如何在不指定位置的情况下使用标准 OTP 应用程序 - builderl 正在由 escript 执行,因此所有应用程序都从它们的安装位置加载(/usr/local/lib/erlang/lib/ 在我的系统上)。如果您的应用程序使用了库,例如mylib,安装在其他地方,您需要做的就是将该位置添加到 Erlang 路径,例如与code:add_path。 Erlang 会自动从添加到代码路径列表的文件夹中加载代码中使用的模块。
嵌入式 Erlang
但是,如果应用程序是独立于系统范围的 Erlang 安装的正确 OTP 版本,则同样适用。这是因为在这种情况下,脚本由属于该嵌入式 Erlang 版本的escript 执行,而不是系统范围的版本(即使它已安装)。因此它知道属于该版本的所有应用程序(包括您的应用程序)的位置。例如riak 就是这样做的——在他们的包中,他们提供了一个embedded Erlang release,其中包含它自己的erts 和所有依赖的Erlang 应用程序。这样riak 就可以在没有安装 Erlang 的主机操作系统上启动。这是 FreeBSD 上 riak 软件包的摘录:
% tar -tf riak2-2.1.1_1.txz
/usr/local/sbin/riak
/usr/local/lib/riak/releases/start_erl.data
/usr/local/lib/riak/releases/2.1.0/riak.rel
/usr/local/lib/riak/releases/RELEASES
/usr/local/lib/riak/erts-5.10.3/bin/erl
/usr/local/lib/riak/erts-5.10.3/bin/beam
/usr/local/lib/riak/erts-5.10.3/bin/erlc
/usr/local/lib/riak/lib/stdlib-1.19.3/ebin/re.beam
/usr/local/lib/riak/lib/ssl-5.3.1/ebin/tls_v1.beam
/usr/local/lib/riak/lib/crypto-3.1/ebin/crypto.beam
/usr/local/lib/riak/lib/inets-5.9.6/ebin/inets.beam
/usr/local/lib/riak/lib/bitcask-1.7.0/ebin/bitcask.app
/usr/local/lib/riak/lib/bitcask-1.7.0/ebin/bitcask.beam
(...)
sh/bash
除了在启动 Erlang VM 时必须显式调用要执行的函数(入口点或您调用的 main 函数)之外,这在原则上与上述没有太大区别。
考虑builderl 生成的这个脚本来启动一个Erlang 应用程序只是为了执行一个指定的任务(生成RELEASES 文件),然后节点关闭:
#!/bin/sh
START_ERL=`cat releases/start_erl.data`
APP_VSN=${START_ERL#* }
run_erl -daemon ../hbd/shell/ ../hbd/log "exec erl ../hbd releases releases/start_erl.data -config releases/$APP_VSN/hbd.config -args_file ../hbd/etc/vm.args -boot releases/$APP_VSN/humbundee -noshell -noinput -eval \"{ok, Cwd} = file:get_cwd(), release_handler:create_RELEASES(Cwd, \\\"releases\\\", \\\"releases/$APP_VSN/humbundee.rel\\\", []), init:stop()\""
这是一个类似的脚本,但不会启动任何特定的代码或应用程序。相反,它会启动适当的 OTP 版本,因此启动哪些应用程序以及启动顺序取决于版本(由 -boot 选项指定)。
#!/bin/sh
START_ERL=`cat releases/start_erl.data`
APP_VSN=${START_ERL#* }
run_erl -daemon ../hbd/shell/ ../hbd/log "exec erl ../hbd releases releases/start_erl.data -config releases/$APP_VSN/hbd.config -args_file ../hbd/etc/vm.args -boot releases/$APP_VSN/humbundee"
如果需要,您可以在 vm.args 文件中提供应用程序的其他路径,例如:
-pa lib/humbundee/ebin lib/yolf/ebin deps/goldrush/ebin deps/lager/ebin deps/yajler/ebin
在此示例中,这些是相对的,但如果您的应用程序安装到标准的知名位置,则可能是绝对的。此外,仅当您使用系统范围的 Erlang 安装并且需要添加其他路径来定位您的 Erlang 应用程序时,或者如果您的 Erlang 应用程序位于非标准位置(例如,不在 lib 文件夹中,正如 Erlang OTP 要求的那样)。在适当的嵌入式 Erlang 版本中,应用程序位于 code root/lib 文件夹中,Erlang 能够加载这些应用程序而无需指定任何其他路径。
总结及其他注意事项
Erlang 应用程序的部署与其他用脚本语言编写的项目没有太大区别,例如ruby 或 python 项目。所有这些项目都必须处理类似的问题,我相信每个操作系统的包管理都以一种或另一种方式处理它们:
了解您的操作系统如何处理具有运行时依赖项的打包项目。
查看其他 Erlang 应用程序是如何为您的操作系统打包的,其中有很多通常由所有主要系统分发:RabbitMQ、Ejabberd、Riak 等。只需下载包并解压到一个文件夹,然后你就会看到所有文件的放置位置。
编辑 - 参考要求
回到您的要求,您有以下选择:
将 Erlang 安装为系统范围内的 OTP 版本、嵌入式 Erlang 或一些随机文件夹中的应用程序包(抱歉 Rebar)
您可以有多个入口点,以sh 或escript 脚本的形式执行从已安装版本中选择的应用程序。只要您正确配置了这些应用程序的代码根目录和路径(如上所述),两者都可以工作。
那么您的每个应用程序:myweb 和 mycli,都需要在其自己的新上下文中执行,例如启动一个新的 VM 实例并执行所需的应用程序(来自同一个 Erlang 版本)。在myweb 的情况下,入口点可以是sh 脚本,它根据版本启动一个新节点(类似于Riak)。在mycli 的情况下,入口点可以是escript,一旦任务完成就完成执行。
但是,即使它是从sh 启动的,也完全有可能创建一个退出 VM 的短期运行任务 - 请参见上面的示例。在这种情况下,mycli 将需要单独的发布文件 - script 和 boot 来引导 VM。当然,也可以从escript 启动一个长时间运行的 Erlang VM。
我提供了一个同时使用所有这些方法的示例项目humbundee。编译完成后,它会提供三个访问点:
-
cmd 发布。
-
humbundee 发布。
-
builder.eshescript.
第一个用于启动节点进行安装,然后将其关闭。第二个用于启动长时间运行的 Erlang 应用程序。第三个是安装/配置节点的构建工具。这是创建发布后项目的外观:
$:~/work/humbundee/tmp/rel % ls | tr " " "\n"
bin
erts-7.3
etc
lib
releases
$:~/work/humbundee/tmp/rel % ls bin | tr " " "\n"
builderl.esh
cmd.boot
humbundee.boot
epmd
erl
escript
run_erl
to_erl
(...)
$:~/work/humbundee/tmp/rel % ls lib | tr " " "\n"
builderl-0.2.7
compiler-6.0.3
deploy-0.0.1
goldrush-0.1.7
humbundee-0.0.1
kernel-4.2
lager-3.0.1
mnesia-4.13.3
sasl-2.7
stdlib-2.8
syntax_tools-1.7
yajler-0.0.1
yolf-0.1.1
$:~/work/humbundee/tmp/rel % ls releases/hbd-0.0.1 | tr " " "\n"
builderl.config
cmd.boot
cmd.rel
cmd.script
humbundee.boot
humbundee.rel
humbundee.script
sys.config.src
cmd 入口点将使用应用程序deploy-0.0.1 和builderl-0.2.7 以及发布文件cmd.boot、cmd.script 和一些OTP 应用程序。标准的humbundee 入口点将使用除builderl 和deploy 之外的所有应用程序。然后builderl.esh escript 将使用应用程序deploy-0.0.1 和builderl-0.2.7。全部来自同一个嵌入式 Erlang OTP 安装。