Mix 是 elixir 的工程管理工具,是时候总结一下 mix 工程的运行方式了。
我们有两种方式来创建 mix 工程:
- 第一种是
mix new xxx
创建一个 elixir 库。 - 第二种是
mix new --sup xxx
创建一个带监督树的应用。
我们有多种方式来运行 Mix 工程。
iex -S mix
在开发阶段我们最常用的方式是用 iex -S mix
在交互式 shell iex 中运行代码。这个命令会在启动 iex 之前先运行 mix 编译工程,并加载进虚拟机。然后我们就可以在 iex 中调用我们编写的函数了,这在开发调试阶段非常实用。
这个命令实际上运行的还是 mix run
,只是它还给我们启动了一个交互式 shell。
mix run
当我们使用 mix new --sup xxx
来创建一个工程时,就可以使用 mix run
或则 mix
来运行工程, mix
默认执行的就是 mix run
。
无论是 mix run
还是 mix compile
,mix 工程都会被编译成一个 erlang 应用,如果熟悉 erlang 的话,一定不会对此感到陌生。
一个 erlang 应用肯定不止一个文件,但通常由一个 .app
文件来描述。 .app
文件中描述了应用的名称,版本,依赖以及启动入口等信息。如果我们使用 mix new --sup example_sup
来创建一个应用,然后运行 mix compile
,就可以在 _build/dev/lib/example_sup/ebin/
目录下发现一个叫做 example_sup.app
的文件,内容大致如下:
{application,example_sup,
[{config_mtime,1749558441},
{optional_applications,[]},
{applications,[kernel,stdlib,elixir,logger]},
{description,"example_sup"},
{modules,['Elixir.ExampleSup','Elixir.ExampleSup.Application']},
{registered,[]},
{vsn,"0.1.0"},
{mod,{'Elixir.ExampleSup.Application',[]}}]}.
这是一个 erlang 元组,其中的 mod
字段就是应用的启动模块,这个模块必须实现 elixir 的 Application
行为,其中的 start
函数就相当于其他语言的 main
函数。Erlang 虚拟机的应用控制器会读取这个 .app
文件并启动应用。
Mix 会根据 mix.exs
文件来生成这个 .app
文件,我们可以在 mix.exs
文件的 application
函数中通过 :mod
来配置启动模块,这个函数需要返回一个 keyword 列表。
def application do
[
extra_applications: [:logger],
mod: {ExampleSup.Application, []}
]
end
如果我们仔细对比 mix new xxx
和 mix new xxx --sup
生成的工程,就会发现它俩唯一的区别就是后者多了一个 application.ex
文件,并且 mix.exs
文件中的 application
函数多了 :mod
的配置。前者也能通过 mix compile
或 mix run
来编译运行,但是运行不会有任何结果,因为生成的 .app
文件中没有 mod
字段,因此也不会有任何代码运行。
Mix 极大的简化了工程管理,运行,测试,打包,发布的流程和工作量,但是其最终还是会落回 erlang 上,因此要深入学习 elixir 还是需要有一定的 erlang 基础。
自定义 mix 任务
我们也可以通过自定义 mix 任务来运行代码,这样我们就可以方便的在命令行直接输入 mix xxx
来运行代码。一个经典的例子就是使用 mix phx.server
来启动 phoenix 应用。
自定义 mix 任务我们需要将源码文件放到 lib/mix/tasks/
目录下,然后创建一个 .ex
文件,名字任意。比如我们创建一个 start.ex
文件,然后输入以下内容:
defmodule Mix.Tasks.Start do
use Mix.Task
def run(args) do
IO.inspect(args, label: "args")
end
end
我们需要 use Mix.Task
并实现 run
函数,它是 Mix.Task
行为的回调函数,然后我们将要运行的代码放到这里就行了。
做完这一切之后我们就可以使用 mix start
来运行我们的代码了, mix
后面的是不带扩展名的文件名。我们可以通过 mix start xxx xxx
来给 run
函数传递参数, mix start
后面的内容会以空格切分为字符串数组传递给 run
函数,这里是绑定到 args
参数上,更复杂的参数规则解析需要我们手动实现,或者借用第三方库实现。
escript
从这里开始就属于是打包发布运行的方式了,也就是说已经离开开发阶段了。
Escript 是一种发布 elixir 命令行程序的方式,因为 elixir 程序是编译成 .beam
文件在 Beam 虚拟机中运行的,无法直接编译成可执行文件。escript 提供了一种机制,让我们可以像可执行程序那样运行 elixir 程序。elixir 程序在编译时,每个模块都会编译成一个 .beam
文件,文件名就是模块名。escript 可以将 elixir 代码打包进一个文件,然后通过 escript xxx
来运行, escript
是用来运行编译产物的程序,它会随着 erlang 一起安装。
escript
和 escript 编译产物的关系类似于 bash
和 .sh
文件的关系,escript 的编译参数类似一个脚本文件,它的第一行就写着执行它的程序:
#! /usr/bin/env escript
...
所以在 Linux 系统上应该是可以像可执行程序那样运行的,但是 Windows 并没有这种机制,因此需要用 escript xxx
来运行。
编译 escript 的方式也非常简单,其实我在 Elixir 工具篇 中就已经介绍过了。我们只需要一个包含 main
函数的模块以及一些简单的配置即可。
例如我们使用 mix new example
创建了一个工程,然后在 example.ex
中添加一个 main
函数。
defmodule Example do
...
def main(argv) do
IO.inspect(argv, label: "argv")
end
end
然后我们在 mix.exs
中加入 escript
配置。
def project do
[
...
escript: escript()
]
end
defp escript do
[
main_module: Example
]
end
最后我们使用 mix escript.build
来编译工程,它会在工程根目录下生成一个与工程同名的无扩展名的文件,这里是 example
。接下来我们就可以使用下面的命令来运行它了:
> escript example -a=1 -b --c d
argv: ["-a=1", "-b", "--c", "d"]
同样,参数是以空格切分的字符串列表的形式传递给 main
函数,需要我们自己解析。
install
我们还可以通过 mix escript.install
将编译好的文件安装到本地,路径是 ~/.mix/escripts
,在 windows 上则一般是 C:\Users\<user>\.mix\escripts
。安装不仅仅是将编译文件拷贝到安装目录,在 Windows 上同时还会生成一个同名的 .bat
运行脚本,当我们将安装目录添加到 Paht 以后,就可以直接在命令行中输入名称运行了,而不再需要使用 escript
。这个目录有可能已经自动添加到系统环境变量了,如果没有就手动添加一下。
以上面的例子为例,我们运行 mix escript.install
以后,生成的 example.bat
内容如下,其实就是调用了 escript
而已。
@echo off
@escript "%~dpn0" %*
除了安装本地项目,还可以是一个编译文件路径, github 仓库或者 hex 仓库,以下示例摘自官方网站。
mix escript.install escript
mix escript.install path/to/escript
mix escript.install git https://path/to/git/repo
mix escript.install git https://path/to/git/repo branch git_branch
mix escript.install git https://path/to/git/repo tag git_tag
mix escript.install git https://path/to/git/repo ref git_ref
mix escript.install github user/project
mix escript.install github user/project branch git_branch
mix escript.install github user/project tag git_tag
mix escript.install github user/project ref git_ref
mix escript.install hex hex_package
mix escript.install hex hex_package 1.2.3
release
这是另一种打包发布方式,我们也称其为发布镜像,它是一个自包含的发布包,可以在没有安装 erlang/elixir 环境的机器上运行。这就意味着它不仅会打包代码,还会打包 erlang 虚拟机,及其必要的库,事实上,还包括一些非常实用的脚本。完整的细节可以参考官方文档。
制作发布镜像并不支持交叉编译,事实上 elixir 代码也没有所谓的交叉编译,因为它是编译成 .beam
文件在虚拟机上运行,真正需要跨平台的是虚拟机,然而在打包时,使用的是本机上安装的虚拟机,所以发布镜像也只能在同样的操作系统上运行,除非我们在目标操作系统上重新打包。
制作发布镜像不需要有什么特殊的操作,直接运行 mix release
命令即可。如果需要一些配置的话,可以在 mix.exs
文件中的 project
函数中通过 :release
键配置。
def project do
[
releases: [
demo: [
include_executables_for: [:unix],
applications: [runtime_tools: :permanent]
],
...
]
]
end
:release
配置也是一个 keyword 列表,键是应用名。完整配置列表见官网。
打包文件生成在 _build/dev/rel/app_name
下, dev
是开发环境,这里会根据打包环境变化, app_name
是项目名。我们将 app_name
整个目录打包成压缩文件就可以发布了,它包含4个目录:
bin
- 应用启动脚本,包含了调试,运维等等。erts-版本
- erlang 虚拟机目录。lib
- 源代码编译结果目录,包含了项目以及标准库。releases
- 虚拟机 cookie,配置文件以及 elixir 相关脚本,就像我在Elixir 工具篇中讲过的一样,elixir 除了标准库以外就是一些脚本了。
所有库和配置都是带有版本号的,这也就意味着我们可以不停机热更新代码。环境相关的配置包括 cookie 都支持环境变量覆盖,一般情况下我们只需要用到 bin
目录下的启动脚本即可。
release 模式和普通模式的一个重要区别是,release 模式下 Beam 虚拟机启动时会将库加载进来,而普通模式下虚拟机在用到某个函数时才会去加载对应的模块,因此 release 可以减少首次响应时间。此外,release 模式将代码和运行环境一起打包,降低了版本冲突的风险,而且不需要目标机器预先安装环境。干过开发的都知道,配环境简直太痛苦了。
启动脚本非常简单,属于看一眼就会用,这里偷个懒,原谅我直接抄一下官网的内容吧。
start Starts the system
start_iex Starts the system with IEx attached
daemon Starts the system as a daemon (Unix-like only)
daemon_iex Starts the system as a daemon with IEx attached (Unix-like only)
install Installs this system as a Windows service (Windows only)
eval "EXPR" Executes the given expression on a new, non-booted system
rpc "EXPR" Executes the given expression remotely on the running system
remote Connects to the running system via a remote shell
restart Restarts the running system via a remote command
stop Stops the running system via a remote command
pid Prints the operating system PID of the running system via a remote command
version Prints the release name and version to be booted
archive
archive 严格来说不是 mix 工程的运行方式,而是将 mix 工程打包成依赖库,是一种库的发布方式。
运行 mix archive.build
会在项目根目录下生成一个 .ez
文件,我猜意思可能是”elixir zip”吧。而他确实也就是一个压缩文件,我们可以用压缩工具去解压它。其内部目录结构如下。
⊢app-name-version
⊢——ebin
⊢——.elixir
.elixir
文件中记录的是兼容的 elixir 版本, ebin
中是 .beam
文件和 .app
文件。
archive 并不是用来发布 elixir 代码库的,官方建议 archive 仅用于 Mix 任务扩展,包管理等功能,原文如下。
In general, we recommend the usage of archives to be limited for extensions of Mix, such as custom SCMs, package managers, and the like.
install
archive 也可以通过 mix archive.install
命令安装,安装在 ~/.mix/archives
目录下,在 Windows 上一般是 C:\Users\<user>\.mix\archives
目录。
如果你做过 Phoenix 开发的话,那么在这个目录下就能看到熟悉的 hex-xx.xx
和 phx_new-xx.xx
目录,这正是我们安装的 phx
相关的命令,注意它并不是 phoenix
库,仅仅是 mix 命令而已。
同 escript 一样,archive 也可以从网上安装,以下示例摘自官方网站。
mix archive.install archive.ez
mix archive.install path/to/archive.ez
mix archive.install git https://path/to/git/repo
mix archive.install git https://path/to/git/repo branch git_branch
mix archive.install git https://path/to/git/repo tag git_tag
mix archive.install git https://path/to/git/repo ref git_ref
mix archive.install github user/project
mix archive.install github user/project branch git_branch
mix archive.install github user/project tag git_tag
mix archive.install github user/project ref git_ref
mix archive.install hex hex_package
mix archive.install hex hex_package 1.2.3