gpt4 book ai didi

erlang - 用 Erlang 编写的命令行工具的惯用方式

转载 作者:行者123 更新时间:2023-12-04 02:20:45 27 4
gpt4 key购买 nike

问题

我发现大多数关于 Erlang 的文章和书籍都专注于创建长时间运行的类似服务器的应用程序,而没有涵盖命令行工具的创建过程。

我有一个包含 3 个应用程序的多应用程序 rebar3 项目:

  • myweb - 一个 cowboy基于网络服务;
  • mycli - 为 myweb 准备 Assets 的命令行工具;
  • mylib - 两者都使用的库 mywebmycli , 取决于 NIF。

  • 作为构建的结果,我想获得这样的工件:
  • 将服务于 http 请求的 Web 部件的可执行文件;
  • 用于 Assets 准备的可执行命令行工具;
  • 上面使用的一组库。

  • 要求
  • cli 应该表现得像一个理智的非交互式命令行工具:处理参数,处理 stdin/stdout,在错误时返回非零退出代码等;
  • 服务器和 cli 都应该能够使用 NIF;
  • 将工件打包为一组 deb/rpm 包应该很容易,因此服务器和 cli 都应该重用公共(public)依赖项。

  • 到目前为止尝试过的事情

    建立一个脚本

    我在野外看到的一种方法是创建一个独立的 escript 文件。至少 rebarrelx这样做。所以我试了一下。

    优点:
  • 支持命令行参数;
  • 如果出现错误,则返回非零退出代码。

  • 缺点:
  • 将所有依赖项嵌入到单个文件中,从而无法重用 mylib ;
  • *.so文件被嵌入到生成的 escript 文件中,它们无法在运行时加载,因此 NIF 不起作用(参见 erlang rebar escriptize & nifs);
  • rebar3 escriptize不能很好地处理依赖关系(请参阅 bug 1139 )。

  • 未知数:
  • cli 应用程序是否应该成为合适的 OTP 应用程序;
  • 它是否应该有一个监督树;
  • 是否应该开始?
  • 如果是这样,我如何在 Assets 处理完毕后停止它?

  • 构建发布

    How I start: Erlang 中描述了另一种构建命令行工具的方法。弗雷德·赫伯特 (Fred Hebert) 的文章。

    优点:
  • 每个依赖应用程序都进入自己的目录,以便于共享和打包它们。

  • 缺点:
  • 没有像 escript 那样定义的入口点 main/1 ;
  • 因此,必须手动处理命令行参数和退出代码。

  • 未知数:
  • 如何以非交互式方式为 cli OTP 应用程序建模;
  • Assets 处理完毕后如何停止应用程序?


  • 以上两种方法似乎都不适合我。

    这将是两全其美:获得 escript 提供的基础设施,例如 main/1入口点、命令行参数和退出代码处理,同时仍然具有易于打包且不妨碍使用 NIF 的良好目录结构。

    最佳答案

    无论您是在 Erlang 中启动一个长时间运行的类似守护程序的应用程序,还是 CLI 命令,您始终需要以下内容:

  • erts应用程序 - 特定版本中的 VM 和内核
  • Erlang OTP 应用
  • 您的应用程序的依赖项
  • CLI 入口点

  • 然后在任何一种情况下,CLI 入口点都必须启动 Erlang VM 并执行它应该在给定情况下执行的代码。然后它将退出或继续运行——后者用于长时间运行的应用程序。

    CLI 入口点可以是启动 Erlang VM 的任何东西,例如 escript脚本, sh , bashescript的明显优势在通用 shell 上是 escript已经在 Erlang VM 的上下文中执行,因此无需处理启动/停止 VM。

    您可以通过两种方式启动 Erlang VM:
  • 使用系统范围的 Erlang VM
  • 使用 embedded Erlang发布

  • 在第一种情况下,您不提供 erts也没有任何带有您的包的 OTP 应用程序,您只需将特定的 Erlang 版本作为您的应用程序的依赖项。在第二种情况下,您提供 erts以及所有必需的 OTP 应用程序以及应用程序包中的依赖项。

    在第二种情况下,您还需要处理设置 code root启动VM时正确。但这很容易,见 erl Erlang 用来启动系统范围的 VM 的脚本:
    # 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 版本,则同样适用。那是因为在这种情况下,脚本由 escript 执行。属于那个嵌入式 Erlang 版本而不是系统范围的版本(即使它已安装)。因此它知道属于该版本的所有应用程序(包括您的应用程序)的位置。例如 riak正是这样做的 - 在他们的包装中,他们提供了一个 embedded Erlang release包含它自己的 erts以及所有依赖的 Erlang 应用程序。那样 riak甚至可以在主机操作系统上不安装 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

    除了必须在启动 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脚本执行从已安装的版本中选择的应用程序。只要您正确配置了代码根目录和这些应用程序的路径(如上所述),两者都可以工作。

  • 然后你的每个应用程序: mywebmycli , 需要在它自己的新上下文中执行,例如启动一个新的 VM 实例并执行所需的应用程序(来自同一个 Erlang 版本)。如果是 myweb入口点可以是 sh根据版本启动新节点的脚本(类似于 Riak)。如果是 mycli入口点可以是 escript , 任务完成后即完成执行。

    但是,即使它是从 sh 启动的,也完全有可能创建一个退出 VM 的短期运行任务。 - 见上面的例子。在那种情况下 mycli需要单独的发布文件 - scriptboot启动虚拟机。当然,也可以从 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.1builderl-0.2.7以及发布文件 cmd.boot , cmd.script ,以及一些 OTP 应用程序。标准 humbundee入口点将使用除 builderl 之外的所有应用程序和 deploy .然后是 builderl.esh escript 将使用应用程序 deploy-0.0.1builderl-0.2.7 .全部来自相同的嵌入式 Erlang OTP 安装。

    关于erlang - 用 Erlang 编写的命令行工具的惯用方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36359040/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com