问题是
我发现大多数关于Erlang的文章和书籍都集中在创建长时间运行的类似服务器的应用程序上,而没有涉及命令行工具的创建过程。
我有一个包含3个应用程序的多应用程序rebar3项目:
myweb
-基于Cowboy的网络服务;mycli
-一个命令行工具,用于为myweb
准备资产;mylib
-myweb
和mycli
使用的库,依赖于NIF。
作为构建的结果,我希望得到这样的工件:
1.将要服务HTTP请求的Web部件的可执行文件;
1.用于资产准备的可执行命令行工具;
1.一组由上述使用的库。
要求
- CLI应该像一个正常的非交互式命令行工具一样工作:处理参数、处理stdin/stdout、出错时返回非零退出代码等;
- 服务器和CLI都应该能够使用NIF;
- 将工件打包为一组
deb
/rpm
包应该很容易,因此服务器和CLI都应该重用公共依赖项。
事情尝试至今
构建一个escript
我见过的一种方法是创建一个自包含的escript
文件,至少rebar3和relx
是这样做的,所以我尝试了一下。
优点:
- 支持命令行参数;
- 如果出现错误,它将返回非零退出代码。
缺点: - 将所有依赖项嵌入到单个文件中,使得不可能重用
mylib
; - 由于
*.so
文件被嵌入到生成的escript
文件中,因此它们不能在运行时加载,因此NIF不起作用(参见erlang rebar escriptize & nifs); rebar3 escriptize
不能很好地处理依赖关系(请参见bug 1139)。
未知:- CLI应用程序是否应成为正确的OTP应用程序?
- 它是否应该有一个监督树?
- 到底该不该开始?
- 如果是,如何在处理完资产后停止它?
构建发布版本
Fred Hebert的How I start: Erlang文章中描述了另一种构建命令行工具的方法。
优点:
- 每个依赖项应用程序都进入自己的目录,从而可以轻松地共享和打包它们。
缺点: - 不存在像
escript
的main/1
那样的已定义入口点; - 因此,命令行参数和退出代码都必须手动处理。
未知: - 如何以非交互方式对CLI OTP应用程序进行建模?
- 如何在处理完资产后停止应用程序?
以上两种方法似乎都不适合我。
如果能两全其美就好了:获得escript
提供的基础设施,如main/1
入口点、命令行参数和退出代码处理,同时仍然具有易于打包且不妨碍使用NIF的良好目录结构。
2条答案
按热度按时间cmssoen21#
无论您是在Erlang中还是在CLI命令中启动一个长时间运行的类似守护进程的应用程序,您总是需要以下内容:
erts
应用程序-特定版本中的虚拟机和内核1.应用程序的依赖项
然后,在任何一种情况下,CLI入口点都必须启动Erlang VM并执行它在给定情况下应该执行的代码,然后它将退出或继续运行-对于长时间运行的应用程序,后者是后者。
CLI入口点可以是启动Erlang VM的任何内容,例如
escript
脚本、sh
、bash
等。escript
相对于通用shell的明显优势在于,escript
已经在Erlang VM的上下文中执行,因此无需处理VM的启动/停止。您可以通过两种方式启动Erlang VM:
1.使用系统范围的Erlang VM
1.使用embedded Erlang版本
在第一种情况下,您不提供
erts
,也不提供任何OTP应用程序,您只需要为您的应用程序创建一个特定的Erlang版本依赖项;在第二种情况下,您需要在您的软件包中提供erts
和所有必需的OTP应用程序沿着应用程序的依赖项。在第二种情况下,您还需要在启动VM时正确设置代码根。但这很容易,请参阅Erlang用于启动系统范围VM的
erl
脚本:这可以通过脚本来处理,例如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
。这反过来又会使用bld_load:boot/3
加载所有剩余模块。请注意,我可以使用标准OTP应用程序而无需指定它们的位置-builderl
由escript
执行,因此所有应用程序都从它们的安装位置加载(/usr/local/lib/erlang/lib/
on my system)。如果您的应用程序使用的库(例如mylib
)安装在其他位置,您只需将该位置添加到Erlang路径(例如code:add_path
)。Erlang将自动从添加到代码路径列表的文件夹中加载代码中使用的模块。嵌入式Erlang语言
但是,如果应用程序是独立于系统范围的Erlang安装而安装的正确的OTP版本,则情况也是如此,因为在这种情况下,脚本是由属于该嵌入式Erlang版本而不是系统范围版本的
escript
执行的(即使它已经安装)。因此它知道属于该版本的所有应用程序的位置例如riak
就是这样做的--在他们的软件包中他们提供了一个embedded Erlang release,它包含了自己的erts
和所有依赖的Erlang应用程序。这样riak
就可以在主机操作系统上没有安装Erlang的情况下启动。这是FreeBSD上一个riak
软件包的摘录:一米三十至一米/一米三十一至一米
除了必须显式调用您在启动Erlang VM时要执行的函数(入口点或您所调用的
main
函数)之外,这在原则上与上面的方法没有太大区别。考虑
builderl
为启动Erlang应用程序而生成的脚本,该脚本只是为了执行指定的任务(生成RELEASES
文件),之后节点关闭:这是一个类似的脚本,但不启动任何特定的代码或应用程序,而是启动一个正确的OTP版本,因此启动哪些应用程序以及启动顺序取决于版本(由
-boot
选项指定)。在
vm.args
文件中,您可以根据需要提供应用程序的其他路径,例如:在此示例中,这些路径是相对的,但如果应用程序安装在标准的已知位置,则可以是绝对的。此外,只有在使用系统范围的Erlang安装并需要添加其他路径来定位Erlang应用程序时,或者Erlang应用程序位于非标准位置时,才需要执行此操作(例如,不在
lib
文件夹中,因为Erlang OTP需要)。在适当的嵌入式Erlang版本中,应用程序位于代码根/lib
文件夹中,Erlang能够加载这些应用程序,而无需指定任何附加路径。总结和其他考虑
Erlang应用程序的部署与其他用脚本语言编写的项目(如ruby或python项目)没有太大区别,所有这些项目都必须处理类似的问题,我相信每个操作系统的包管理都以这样或那样的方式处理它们:
1.了解操作系统如何处理具有运行时依赖项的打包项目。
1.看看其他Erlang应用程序是如何为您的操作系统打包的,有很多Erlang应用程序通常由所有主要系统分发:RabbitMQ,Ejabberd,Riak等等。只需下载包并将其解包到一个文件夹中,然后你就会看到所有文件的位置。
编辑-参考要求
回到您的要求,您有以下选择:
1.将Erlang安装为系统范围内的OTP版本、嵌入式Erlang或随机文件夹中的应用程序包(对不起,Rebar)
1.您可以使用
sh
或escript
脚本形式的多个入口点来执行已安装版本中的选定应用程序。只要您正确配置了这些应用程序的代码根和路径(如上所述),这两个入口点都可以工作。然后,您的每个应用程序:X1 M41 N1 X和X1 M42 N1 X,将需要在其自己的新上下文中执行,例如,启动新的虚拟机示例并执行所需的应用程序(来自相同的Erlang发行版)。对于
myweb
,入口点可以是sh
脚本,该脚本根据发行版启动新节点在mycli
的情况下,入口点可以是escript
,它在任务完成时结束执行。但是,即使虚拟机是从
sh
启动的,也完全可以创建一个退出虚拟机的短期运行任务-参见上面的示例。在这种情况下,mycli
将需要单独的发布文件-script
和boot
来 Boot 虚拟机。当然,也可以从escript
启动一个长期运行的Erlang虚拟机。我提供了一个同时使用所有这些方法的示例项目humbundee,编译后它提供了三个访问点:
cmd
发行版。humbundee
发行版。1.一米五十四奈一米五十五奈一米。
第一个用于启动节点进行安装,然后关闭它;第二个用于启动一个长时间运行的Erlang应用程序;第三个是一个构建工具,用于安装/配置节点。下面是创建发布版本后的项目外观:
cmd
入口点将使用应用程序deploy-0.0.1
和builderl-0.2.7
以及发布文件cmd.boot
、cmd.script
标准的humbundee
入口点将使用除builderl
和deploy
之外的所有应用程序。然后,builderl.esh
脚本将使用应用程序deploy-0.0.1
和builderl-0.2.7
。所有这些应用程序都来自同一个嵌入式Erlang OTP安装。q0qdq0h22#
一个小的脚本,然后进入代码从'常规'模块可能是一个解决方案。
例如,Concuerror预期用作命令行工具,并使用escript作为其入口点。它通过getopt处理命令行参数。所有主要代码都在常规Erlang模块中,这些模块包含在escript的简单参数路径中。
据我所知,可以使用常规的
-onload
属性加载NIF(Concuerror不使用NIF)。