OTP 应用

OTP 应用

2019 年 8 月 8 日

由于 Erlang/OTP 发布版中每个要发布的组件都需要是一个 OTP 应用,因此了解它们是什么以及它们如何工作将对您大有裨益。在本章中,我们将介绍 OTP 应用的基本结构,以及这对您的项目意味着什么。

项目结构

我们将首先使用 Rebar3 模板,因为它们允许我们创建全新的项目,这些项目能够正确遵循 Erlang/OTP 预期的目录结构。让我们看看哪些模板可用

$ rebar3 new
app (built-in): Complete OTP Application structure.
cmake (built-in): Standalone Makefile for building C/C++ in c_src
escript (built-in): Complete escriptized application structure
lib (built-in): Complete OTP Library application (no processes) structure
plugin (built-in): Rebar3 plugin project structure
release (built-in): OTP Release structure for executable programs
umbrella (built-in): OTP structure for executable programs
                     (alias of 'release' template)

这是一个表格,显示了它们可能在何时使用

项目类型要使用的模板注释
脚本或命令行工具escript要求用户安装 Erlang
库(模块集合)lib可用作依赖项
库(有状态进程)app可用作依赖项
完整的可执行程序umbrella 或 app可以转换为完整发布版,这是推荐的部署机制
多个库的集合umbrella不能用作 git 依赖项,但每个单独的应用都可以发布到 hex
Rebar3 扩展plugin
编译 C 代码cmake另请参阅“pc”插件,以获取编译 C/C++ 的便携式方法

您可以通过调用 rebar3 new help <template> 查看给定模板的详细信息。例如,请参见

$ rebar3 new help lib
lib:
  built-in template
  Description: Complete OTP Library application (no processes) structure
  Variables:
    name="mylib" (Name of the OTP library application)
    desc="An OTP library" (Short description of the app)
    date="2019-03-15"
    datetime="2019-03-15T19:52:31+00:00"
    author_name="Fred Hebert"
    author_email="[email protected]"
    copyright_year="2019"
    apps_dir="apps" (Directory where applications will be created if needed)

这些值可以在命令行中根据需要修改,但这些是默认变量。让我们看看通过编写我们自己的代码会得到什么

$ rebar3 new lib mylib desc="Checking out OTP libs"
===> Writing mylib/src/mylib.erl
===> Writing mylib/src/mylib.app.src
===> Writing mylib/rebar.config
===> Writing mylib/.gitignore
===> Writing mylib/LICENSE
===> Writing mylib/README.md

转到 mylib 目录,并立即调用 rebar3 compile

$ rebar3 compile
===> Verifying dependencies...
===> Compiling mylib

如果您查看您的目录结构,您现在应该在您的项目中看到如下内容

mylib/
├─ _build/
│  └─ default/
│     └─ lib/
│        └─ mylib/
│           ├─ ebin/
│           │  ├─ mylib.app
│           │  └─ mylib.beam
│           ├─ include/
│           ├─ priv/
│           └─ src/
│              └─ ...
├─ .gitignore
├─ LICENSE
├─ README.md
├─ rebar.config
├─ rebar.lock
└─ src/
   ├─ mylib.app.src
   └─ mylib.erl

_build/ 目录是构建工具的游乐场,它可以在其中存储所有需要的工件。您永远不应该手动触摸其中的内容,但如果您愿意,可以随时将其删除。但是,此目录很有趣,因为它显示了 Rebar3 如何构建事物。

_build/ 中的所有内容都按 配置文件 分割,这允许 Rebar3 以不同的方式构建内容(使用不同的依赖项集和编译器选项),无论它们是在 defaulttest 还是 prod 配置文件中构建——事实上,您可以根据需要定义任意数量的配置文件,并将它们组合在一起。Rebar3 文档解释了这是如何工作的。

在每个配置文件中,lib/ 目录包含项目可能使用的所有 OTP 应用,标准发行版的库除外。您可以在那里看到我们的 mylib 库的副本,但其目录结构与项目根目录下的结构略有不同

  • 已编译的 .erl 文件被移动到 ebin/ 目录,现在具有 .beam 扩展名
  • 创建了一个 mylib.app 文件,而源应用具有 mylib.app.src
  • 已向 include/priv/ 添加了两个符号链接。如果存在,它们将引用项目根目录下的匹配目录。include/ 目录用于 头文件.hrl),priv/ 目录用于必须复制并可在生产环境中使用的任何文件
  • 项目根目录下的所有其他文件都已被丢弃

如果我们有任何依赖项(请参阅 依赖项章节),它们也将放置在 _build/<profile>/lib/ 目录中。

通常,您希望完全忽略 _build/ 目录,并避免在源代码控制中跟踪它:如果您查看 .gitignore 文件,您会发现它会自动为您忽略 _build/

Rebar3 默认为您选择许可证(因为如果您计划进行开源工作,则应始终选择许可证),使用 Erlang 附带的 Apache 2.0 许可证。请根据需要随意替换它。Rebar3 还会设置一个 README 文件,您可能需要修复它并更新所有相关内容。不要做混蛋,编写文档!

然后我们看到两个有趣的文件,rebar.configrebar.lock。锁定文件由 Rebar3 用于跟踪项目中任何依赖项使用的版本,因此应将其检入源代码控制。 依赖项章节 包含更多详细信息。

rebar.config 文件是一个完整的声明性配置文件,它公开了 Rebar3 集成的所有 Erlang 工具的选项。 官方文档 解释了所有可能的值,但默认情况下它为空。实际上,如果您只需要默认值而没有依赖项,则可以简单地删除该文件。只要您的项目结构像 OTP 应用一样,Rebar3 就会弄清楚需要做什么。

让我们看看为此发生需要遵守哪些标准。

是什么使库成为应用

与任何其他框架一样,您必须做一些事情才能符合其预期。您可能已经猜到了,但目录结构是像 OTP 这样的框架的基本要求之一。只要您的库在编译后具有 ebin/ 目录,其中包含 <appname>.app 文件,Erlang 运行时系统将能够加载您的模块并运行您的代码。

此基本要求指导了整个 Erlang 生态系统的项目结构。让我们看看构建的 .app 文件是什么样子

$ cat _build/default/lib/mylib/ebin/mylib.app
{application, mylib, [
  {description, "Checking out OTP libs"},
  {vsn, "0.1.0"},     % version number (string)
  {registered, []},   % name of registered processes, if any
  {applications, [    % List of OTP application names on which
    kernel, stdlib    % yours depends at run-time. kernel and
  ]},                 % stdlib are ALWAYS needed
  {env, []},          % default configuration values ({Key, Val} pairs)
  {modules, [mylib]}, % list of all the modules in the application
  %% content below is optional, and for package publication only
  {licenses, ["Apache 2.0"]},
  {links, []}         % relevant URLs
]}.

这本质上是一个元数据文件,描述了应用的全部内容。我们已经花时间为您注释了它,所以请查看一下。其中的许多内容手动编写起来很麻烦,因此如果您查看源文件(src/mylib.app.src),您会发现当您应用 Rebar3 模板时,这些字段大多已预先填充。您可能还会注意到 modules 为空。这是故意的:Rebar3 将在编译代码时为您填充此列表。

到目前为止,那里最关键的字段是 applications 元组。它允许 Erlang 库知道必须按什么顺序启动 OTP 应用才能工作,并且还允许构建工具在所有可用的 OTP 应用之间构建依赖关系图,以了解在构建发布版时要保留哪些应用以及要从分发版中删除哪些应用。

需要注意的一个更微妙的事情是,即使我们这里的是一个 ,并且因此它没有要运行的进程,我们仍然能够定义一些配置值(将在尚未编写的配置章节中介绍),并且必须尊重依赖项。例如,我们的库可能是无状态的,但使用有状态的 HTTP 客户端:然后 Erlang VM 需要知道您的代码何时可以安全调用,何时不可以安全调用。

现在,让我们专注于无状态应用和有状态应用之间到底有什么区别。

是什么使可运行的应用成为应用

为了创建一个可运行的应用,我们将使用 Rebar3 中的“app”模板,并查看它与无状态应用有什么区别。

所以让我们拿起您的命令行工具并运行以下命令

$ rebar3 new app myapp
===> Writing myapp/src/myapp_app.erl
===> Writing myapp/src/myapp_sup.erl
===> Writing myapp/src/myapp.app.src
===> Writing myapp/rebar.config
===> Writing myapp/.gitignore
===> Writing myapp/LICENSE
===> Writing myapp/README.md
$ cd myapp

如果您小心,您会看到我们现在有两个模块而不是 <appname>.erl:我们有 <appname>_app.erl<appname>_sup.erl。我们很快就会学习它们,但首先,让我们关注应用的顶级元数据文件,即 myapp.app.src 文件

$ cat src/myapp.app.src
{application, myapp,
 [{description, "An OTP application"},
  {vsn, "0.1.0"},
  {registered, []},
  {mod, {myapp_app, []}},               % this is new!
  {applications, [kernel, stdlib]},
  {env,[]},
  {modules, []},

  {licenses, ["Apache 2.0"]},
  {links, []}
 ]}.

这里唯一的新行是 {mod, {<appname>_app, []}} 元组。此元组指定了一个特殊的模块(<appname>_app),它可以被调用,并带有一些特定的参数([])。当被调用时,预期此模块将返回 监督树进程标识符pid)。

如果您访问 myapp_app 模块,您将看到这些回调是什么

%%%-------------------------------------------------------------------
%% @doc myapp public API
%% @end
%%%-------------------------------------------------------------------

-module(myapp_app).
-behaviour(application).
%% Application callbacks
-export([start/2, stop/1]).

%%====================================================================
%% API
%%====================================================================

start(_StartType, _StartArgs) ->
    myapp_sup:start_link().

stop(_State) ->
    ok.

当 Erlang 运行时系统启动应用时,将调用 start/2 回调,此时其所有依赖项(如 .app 文件中的 applications 元组中定义的那样)都已启动。您可以在此处执行一次性初始化操作。在模板应用中,唯一执行的操作是启动应用的根监督程序。

当某个地方的某个人决定关闭 OTP 应用时,整个监督树被关闭后,将调用 stop/1 回调。

但总而言之,app 文件中这个小型的额外 mod 行和监督结构的存在是区分可运行应用和库应用的关键。

提示

如果您不想关心监督树,并且只对获得一个 main() 函数来启动(就像在大多数其他编程语言中一样),escript 可能是您的一个好选择。

escript 是一个特殊的 C 程序,它包装了 Erlang 虚拟机。在包装它的过程中,它还引入了一个小的 shim,将 main() 函数的概念改造到发布结构中,通过调用您的代码进入虚拟机的根 Erlang 进程。

最终结果是,您可以运行解释型代码,而不必担心发布版、OTP 应用或监督树。您可以在 Erlang 官方文档 中阅读有关 escript 的更多信息。Rebar3 也有 一个命令来创建复杂的 escript 包

现在,您已经了解了 Erlang/OTP 项目结构中大多数奇怪的东西以及与这些神秘的“OTP 应用”有关的所有内容。从下一章开始,我们将开始深入研究监督树,以便您知道如何在有状态的可运行应用中设置事物。