Learning
VOL. XIII · NO. 11 · Elixir · 01 JAN 1970

Mix 构建工具速通

Elixir 编程 · 01 JAN 1970 · 12 min read · 372 words
· · ·

Elixir 的官方构建工具,对比你熟知的 Clojure Leiningen 和 Java Maven。

这节课回答三个问题:

  1. Mix 是什么、项目结构长什么样
  2. 常用命令(compile / test / deps / release / format / credo)
  3. 跟 Lein / Maven 一一对应(从你已经会的快速迁移心智模型)

所有例子都来自 ex/elixir/ 真实项目。

**预计时间:**45 分钟。

你已具备
你用过 Clojure 的 Leiningen(project.clj + lein new + lein deps)或 Java 的 Maven(pom.xml + mvn package)。本课不再讲"为什么需要构建工具",只讲 Elixir 的 Mix 特殊点

一、Mix 是什么?

Mix 是 Elixir / Erlang 生态的官方构建工具,类似:

生态工具配置文件
Elixir / ErlangMixmix.exs(Elixir 脚本)
ClojureLeiningenproject.clj(Clojure 脚本)
JavaMavenpom.xml(XML)
JavaGradlebuild.gradle(Groovy/Kotlin DSL)
Node.jsnpm / yarn / pnpmpackage.json(JSON)
RustCargoCargo.toml(TOML)
Go(无)go modgo.mod

**关键直觉:**Mix 的 mix.exsElixir 代码(不是 XML/JSON/TOML)——因为它本身是 Elixir 脚本,能写任意逻辑(条件、宏、调用函数)。

二、Mix 创建的项目结构

运行 mix new my_app 生成:

my_app/
├── mix.exs                # 项目配置(依赖、版本、aliases)
├── README.md
├── .gitignore
├── lib/
│   ├── my_app.ex          # 主模块(默认一个 module)
│   └── my_app/
│       └── ...            # 子模块
├── test/
│   ├── test_helper.exs    # 测试公共配置
│   └── my_app_test.exs    # 一个测试文件
└── config/
    ├── config.exs         # 共享配置
    ├── dev.exs            # dev 环境配置
    ├── test.exs           # test 环境配置
    └── prod.exs           # prod 环境配置

对比 Lein / Maven:

目录Mix (Elixir)Lein (Clojure)Maven (Java)
源码lib/src/src/main/java/
测试test/test/src/test/java/
资源priv/resources/src/main/resources/
配置config/*.exsproject.clj 里的 :profilessrc/main/resources/ + filter
依赖deps/(自动管理).m2/(Maven 本地仓).m2/(Maven 本地仓)
编译产物_build/target/target/

**关键直觉:**Mix 自动建 _build/,类似 Lein/Maven 的 target/应该 gitignore

三、Mix.exs 全解剖

项目 ex/elixir/mix.exs 全文:

defmodule Pipeline.MixProject do
  use Mix.Project

  def project do
    [
      app: :pipeline,                              # 应用名(atop 上也是这个名字)
      version: "0.1.0",
      elixir: "~> 1.18",                            # Elixir 版本要求
      start_permanent: Mix.env() == :prod,         # prod 启动失败时整个 VM 崩溃(vs dev 抛异常)
      deps: deps(),                                # 函数返回依赖列表
      elixirc_paths: elixirc_paths(Mix.env()),      # 不同 env 编不同目录
      test_paths: ["test"]
    ]
  end

  # 测试与 dev 额外编译 test/support
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]

  def application do
    [
      extra_applications: [:logger],                # 启动时额外跑的 OTP app
      mod: {Pipeline.Application, []}               # OTP Application 入口模块
    ]
  end

  defp deps do
    [
      {:oban, "~> 2.23"},
      {:ecto_sql, "~> 3.14"},
      {:postgrex, "~> 0.19"},
      {:pgvector, "~> 0.4"},
      {:finch, "~> 0.23"},
      {:jason, "~> 1.4"},
      {:lang_ex, "~> 0.11"},
      {:plug, "~> 1.16"},
      {:bandit, "~> 1.6"},
      {:redix, "~> 1.5"}
    ]
  end
end

三段式:project/0(项目元信息)、application/0(运行时行为)、deps/0(依赖列表)。 对比: | 段 | Mix | Lein | Maven | | --- | --- | --- | --- | | 元信息 | project/0 返回 keyword list | defproject 宏 | <project><groupId/><artifactId/>...</project> | | 依赖 | deps/0 返回 {:pkg, "~> ver"} 列表 | :dependencies [...] | <dependencies> XML | | 运行时 | application/0mod: 指向 Application 模块) | :main ns.symbol | <build><plugins> 配置 main class | **关键直觉:**Mix 的 mod: {Pipeline.Application, []} 就是”启动时调这个 OTP Application 回调”——比 Java 的 public static void main(String[]) 更结构化(详见 0002)。 ## 四、最常用 12 个 Mix 命令 每个命令给出 + 对应的 Lein / Maven 命令: | 用途 | Mix | Lein | Maven | | --- | --- | --- | --- | | 新建项目 | mix new my_app | lein new app my-app | mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=com.x -DartifactId=my-app | | 下载依赖 | mix deps.get | lein deps | mvn dependency:resolve | | 编译 | mix compile | lein compile | mvn compile | | 清理 | mix clean | lein clean | mvn clean | | 运行测试 | mix test | lein test | mvn test | | 启动 REPL | iex -S mix | lein repl | (无内置) | | 运行单文件 | mix run my_script.exs | lein run -m my.ns | mvn exec:java -Dexec.mainClass=... | | 生成 release | mix release | lein uberjar | mvn package | | 格式化 | mix format | (无内置) | mvn spotless:apply(插件) | | 代码质量 | mix credo | lein kibit | mvn checkstyle:check | | 静态类型 | mix dialyzer | lein typed | mvn compile(编译期检查) | | DB 迁移(Ecto) | mix ecto.migrate | lein migratus | mvn flyway:migrate |

快捷组合
  • 全新跑项目:mix deps.get && mix compile && mix test
  • 开发循环:iex -S mix(REPL + 加载所有依赖)
  • 生产部署:MIX_ENV=prod mix release(生成自包含目录)
## 五、Mix.Release — Elixir 的杀手锏 mix release 是 Elixir 部署的核心,会生成自包含的目录:

MIX_ENV=prod mix release
# 产物在 _build/prod/rel/my_app/

_build/prod/rel/my_app/
├── bin/
│   ├── my_app        # 启动脚本(前台 / daemon / remote_console)
│   └── my_app.bat    # Windows 启动
├── lib/              # 所有依赖 + VM 编译产物
├── releases/
│   ├── 0.1.0/
│   │   ├── my_app.bat
│   │   ├── my_app.sh
│   │   └── runtime.exs    # prod-only 启动配置
│   └── COOKIE
└── ...

vs Lein uberjar / Maven package: | 维度 | mix release | lein uberjar | mvn package | | --- | --- | --- | --- | | 产物 | 目录(bin + lib + releases) | 单一 fat JAR | JAR / WAR | | 包含 BEAM VM? | 可选(混编模式自动嵌入) | N/A(JVM 已装) | N/A | | 运行时 | ./bin/my_app start | java -jar my-app.jar | java -jar my-app.jar | | 远程 console | ./bin/my_app remote(直连 prod 进程) | lein repl :connect | jmx / 调试器 | | 配置注入 | runtime.exs(启动时读 env) | profiles.clj | Spring profile / -Dkey=value | | 冷启动 | 2-3 秒(含 VM) | 3-10 秒(仅应用) | 3-10 秒 |

Elixir 在 serverless 时代的隐藏优势
mix release 的产物 = 一个目录 + 一个启动脚本。整个目录可以直接 tar 打包,scp 到任何 Linux VM,15 秒内可服务(冷启动 + 应用启动)。这是 Java/Spring Boot 做不到的(Spring Boot 启动 30-60 秒)。


这是 Elixir + Vultr "按需起 VM" 架构能 work 的技术基础(详见 extensions-serverless-architecture.md)。
## 六、Mix Tasks(自定义命令) 类似 Lein 的”插件”、Maven 的”goal”——你可以在项目里定义自己的 mix my_task

# lib/mix/tasks/deploy.ex
defmodule Mix.Tasks.Deploy do
  use Mix.Task

  @shortdoc "Deploy to staging via cloud-init"
  def run(args) do
    Application.ensure_all_started(:pipeline)

    # 读环境变量
    env = System.get_env("MIX_ENV", "dev")
    snapshot_id = System.get_env("VULTR_SNAPSHOT_ID")

    # 调 Vultr API
    case Vultr.spin_up(snapshot_id, env) do
      {:ok, vm} ->
        Mix.shell().info("✓ Deployed: #{vm.label} (#{vm.ip})")
      {:error, reason} ->
        Mix.raise("✗ Deploy failed: #{inspect(reason)}")
    end
  end
end

# 用法
# mix deploy MIX_ENV=prod VULTR_SNAPSHOT_ID=abc123

关键点: - 文件放 lib/mix/tasks/your_task.ex,模块名 Mix.Tasks.YourTask - 执行 mix your_task下划线 不用连字符) - 可以 Mix.shell().info/1 输出、Mix.raise/1 抛错、Mix.shell().prompt/1 交互 对比 Lein 的 :hooks / Maven 的 maven-antrun-plugin:更轻量、无需写 plugin descriptor。 ## 七、配置系统(config/*.exs) Elixir 的配置分两层——这点和 Lein 的 profile 不一样,要特别注意: ### 7.1 编译时配置(config.exs) config/config.exs编译期被读取,Application.compile_env/2 把值烧进编译产物。改完需要重新 mix compile

# config/config.exs
import Config
config :pipeline, web_port: 8002
config :pipeline, schema_prefix: ""
# 在 lib/pipeline/feishu_message.ex
@schema_prefix Application.compile_env(:pipeline, :schema_prefix, "") <> "public"

7.2 运行时配置(runtime.exs) release 启动时读 releases/0.1.0/runtime.exs,从环境变量取配置:

# _build/prod/rel/pipeline/releases/0.1.0/runtime.exs
import Config
config :pipeline, llm_url: System.get_env("LLM_URL")
config :pipeline, llm_key: System.get_env("LLM_KEY")
config :pipeline, database_url: System.get_env("DATABASE_URL")

关键原则(Elixir 1.11+ 强制): - 编译时配置 → config/*.exs,只放”不依赖环境的常量” - 运行时配置 → runtime.exs所有 secrets、URL、key 放这里 - 代码里用 Application.get_env/2,3Application.fetch_env!/2 读运行时值 对比: | 时点 | Elixir | Java Spring | | --- | --- | --- | | 编译时 | config/config.exs + compile_env | application.yml + @Value(编译时占位) | | 运行时 | runtime.exs + env var | 环境变量 + ${ENV_VAR:default} |

常见坑
compile_envtest 隔离场景特别有用:项目里 @schema_prefix Application.compile_env(:pipeline, :schema_prefix, "") <> "public"MIX_ENV=test mix compile 时就烧成 "test_public",测试数据不会污染 prod 表。
## 八、依赖管理

defp deps do
  [
    {:oban, "~> 2.23"},          # 兼容 2.23.x(Patch 版本可升)
    {:ecto_sql, "~> 3.14"},
    {:postgrex, "~> 0.19"},
    {:pgvector, "~> 0.4"},
    {:finch, "~> 0.23"},
    {:jason, "~> 1.4"},
    {:lang_ex, "~> 0.11"},
    {:plug, "~> 1.16"},
    {:bandit, "~> 1.6"},
    {:redix, "~> 1.5"},
    # Hex 上找不到的,可指定 git:
    # {:my_lib, git: "https://github.com/me/my_lib.git", branch: "main"},
    # 或本地 path:
    # {:my_lib, path: "../my_lib"}
  ]
end

版本约束语法: | 写法 | 允许范围 | 类比 Lein/Maven | | --- | --- | --- | | "~> 2.23" | 2.23.0 ≤ ver < 3.0 | Lein [2.23]、Maven [2.23,3.0) | | "~> 2.23.0" | 2.23.0 ≤ ver < 2.24 | Maven [2.23.0,2.24.0) | | "2.23.0" | 只能 2.23.0 | Maven [2.23.0] | | ">= 2.23.0" | 2.23.0 及以上 | Maven [2.23.0,) | **锁文件 mix.lock:**类似 Lein 的 :pin 或 Maven 的 dependency:resolve-plugin.lock,记录实际装的版本。应该 git commit,保证团队/CI 装到一样版本。 ## 九、3 个推荐工具(必装) ### 9.1 mix format(项目根 .formatter.exs

# .formatter.exs
[
  inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
  line_length: 120
]

类似 gofmt / prettier / clang-format。mix format 改文件,mix format --check-formatted 只检查(CI 必跑)。 ### 9.2 mix credo(代码质量 lint)

# mix.exs deps
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}

# 用法
mix credo                # 跑所有检查
mix credo --strict       # 严格模式
mix credo MyApp.SomeMod  # 跑单个模块

类似 Java 的 Checkstyle / PMD / SpotBugs。Elixir 项目默认必装。 ### 9.3 mix dialyzer(静态类型检查)

# mix.exs deps
{:dialyxir, "~> 1.4", only: [:dev], runtime: false}

# 用法(首次很慢,要建 PLT)
mix dialyzer
# 跑完会报告 type mismatch、unused functions 等

类似 TypeScript 的 tsc --noEmit、Haskell 的 GHC type check。Elixir 动态类型,但 @spec 注释让 dialyzer 能静态推断。 ## 十、Mix 的 4 个特殊概念(Lein/Maven 没有的) ### 10.1 Mix env(环境) 三环境::dev / :test / :prod。通过 MIX_ENV=... 切换:

MIX_ENV=prod mix compile
MIX_ENV=test mix test
MIX_ENV=dev iex -S mix

作用: - 不同环境加载不同 config/{env}.exs - Mix.env() == :prodstart_permanent: true → 启动失败直接崩 VM(vs dev 时抛异常) - 代码里 if Mix.env() == :test, do: ... 可写测试专用逻辑(不推荐滥用) ### 10.2 Mix.Project callbacks 你的 mix.exs 必须 use Mix.Project 并实现 3 个 callback(project/0application/0deps/0)。这是强制的,编译器知道你的项目需要哪些字段。 ### 10.3 Mix.Config vs Config(API 改名史) Elixir 1.9 之前用 Mix.Config,1.9+ 改用 import Config。你看到的老项目用 use Mix.Config 是历史代码。 ### 10.4 mix new 的 4 种类型

mix new my_app              # 普通 library
mix new my_app --sup        # 带 Supervisor 模板(OTP 入口)
mix new my_app --umbrella   # umbrella 项目(多 app 单仓)
mix new my_app --module MyApp  # 指定主模块名

—sup 直接生成 OTP 项目骨架;umbrella 用于微服务(多个 app 共享 deps/)。 ## 十一、测验(12 道)

1

Mix 的项目配置文件是?

2

mix deps.get 对应 Maven 什么?

3

Mix release 的产物是?

4

启动 Mix release 的 prod 应用的命令?

5

MIX_ENV=prod mix release 生成的 release 里,运行时配置(secrets 等)从哪读?

6

~> 2.23 表示什么版本范围?

7

mix new my_app --supmix new my_app 多了什么?

8

mix.lock 应该?

9

Application.compile_env(:pipeline, :schema_prefix, "")Application.get_env(:pipeline, :schema_prefix, "") 区别?

10

mix format --check-formatted 用途?

11

Mix.exs 三段式是?

12

mix format 对应 Lein / Maven 的什么?

### 读代码题

R1

R1. 读下面 mix.exs,MIX_ENV=prod mix release 后启动 ./bin/pipeline start,Llm URL 从哪读?

R2

R2. 读下面 deps,运行 mix deps.get 装的是哪个版本的 bandito?

下一步:extensions-serverless-architecture.md — Mix release 是 Elixir 在”按需起 VM”架构里能 work 的技术基础。 “