关于Erlang的大多数文章和书籍我都可以找到专注于创建长期运行的类似服务器的应用程序,而不会覆盖命令行工具创建过程。
我有一个包含3个应用程序的多应用程序rebar3项目:
myweb
- 基于cowboy
的网络服务; mycli
- 为myweb
; mylib
- myweb
和mycli
使用的库取决于NIF。由于构建我希望得到这样的工件:
我在野外看到的一种方法是创建一个自包含的escript文件。至少rebar
和relx
会这样做。所以我试了一下。
优点:
缺点:
mylib
; *.so
文件嵌入到生成的escript文件中,因此无法在运行时加载,因此NIF无法正常工作(请参阅erlang rebar escriptize & nifs); rebar3 escriptize
无法很好地处理依赖关系(请参阅bug 1139)。未知
另一种构建命令行工具的方法在Fred Hebert撰写的How I start: Erlang文章中有所描述。
优点:
缺点:
main/1
; 未知
上述两种方法似乎都不适合我。
它将从两个世界中获得最佳效果:获取escript提供的基础结构,例如main/1
入口点,命令行参数和退出代码处理,同时仍具有易于打包的漂亮目录结构并且不妨碍使用NIF。
答案 0 :(得分:5)
无论您是在Erlang中启动长期运行的守护程序类应用程序,还是CLI命令,您始终需要以下内容:
erts
应用程序 - 特定版本的VM和内核然后在任何一种情况下,CLI入口点都必须启动Erlang VM并执行它应该在给定情况下执行的代码。然后它将退出或继续运行 - 后者用于长时间运行的应用程序。
CLI入口点可以是启动Erlang VM的任何内容,例如:一个escript
脚本,sh
,bash
等。escript
优于通用shell的明显优势是escript
已经在一个上下文中执行Erlang VM,因此无需处理启动/停止VM。
您可以通过两种方式启动Erlang VM:
在第一种情况下,您不会向您的软件包提供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+"$@"}
这可以通过脚本来处理,例如Basho用于为所有主要操作系统打包Riak数据库的node_package
工具。我正在使用我自己的名为my own fork的构建工具来维护它builderl
。我只是说,所以你知道如果我设法定制它你也能够做到这一点:)
启动Erlang VM后,您的应用程序应该能够加载和启动任何应用程序,无论是Erlang提供还是应用程序(包括您提到的mylib
库)。以下是一些如何实现这一目标的例子:
escript
示例
请参阅this builderl.esh
example我如何处理来自builderl
的其他Erlang应用程序。 escript
脚本假定Erlang安装与其执行的文件夹相关。如果它是另一个应用程序的一部分,例如humbundee
,则load_builderl.hrl
包含文件会编译并加载bld_load
,而builderl
会加载bld_load:boot/3
所有剩余的模块。请注意我如何使用标准OTP应用程序而不指定它们的位置 - escript
正在执行/usr/local/lib/erlang/lib/
,因此所有应用程序都从它们的安装位置加载(mylib
在我的系统上) 。如果您的应用程序使用的库,例如escript
,安装在其他地方,您需要做的就是将该位置添加到Erlang路径,例如:与code:add_path
。 Erlang将自动从添加到代码路径列表的文件夹中加载代码中使用的模块。
嵌入式Erlang
但是,如果应用程序是独立于系统范围的Erlang安装而安装的正确OTP版本,则同样适用。这是因为在这种情况下,脚本由属于该嵌入式Erlang版本的riak
执行而不是系统版本(即使它已安装)。因此,它知道属于该版本的所有应用程序的位置(包括您的应用程序)。例如,erts
就是这样 - 在他们的包中,它们提供了一个embedded Erlang release,其中包含自己的riak
和所有依赖的Erlang应用程序。这样就可以在没有Erlang甚至安装在主机操作系统上的情况下启动riak
。这是FreeBSD上% 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
/ main
强>
除了必须显式调用启动Erlang VM时要执行的函数(入口点或builderl
函数时,原则上与上面的内容完全不同之处)。
考虑RELEASES
生成的脚本,以启动Erlang应用程序只是为了执行指定的任务(生成#!/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()\""
文件),之后节点关闭:
-boot
这是一个类似的脚本,但不会启动任何特定的代码或应用程序。相反,它会启动正确的OTP版本,因此启动哪些应用程序以及依赖于版本的顺序(由#!/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
文件中,您可以根据需要提供应用程序的其他路径,例如:
lib
在此示例中,这些是相对的,但如果您的应用程序安装在标准的已知位置,则可能是绝对的。此外,只有在使用系统范围的Erlang安装并且需要添加其他路径来定位Erlang应用程序时,或者如果您的Erlang应用程序位于非标准位置(例如,不在{{1}中)时,才需要这样做}文件夹,正如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.esh
escript
。第一个用于启动节点进行安装,然后将其关闭。第二个用于启动长期运行的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安装。
答案 1 :(得分:0)
然后从“传统”模块进入代码的小escript可能是一种解决方案。
例如,Concuerror应该用作命令行工具,并使用escript作为其入口点。它通过getopt处理命令行参数。所有主要代码都在常规的Erlang模块中,这些模块包含在带有escript的简单参数的路径中。
据我了解,NIF可以加载常规-onload
属性(Concuerror不使用NIF)。