OCaml 入门:Dune 构建系统简介
摘要
一份实用指南,介绍适用于 OCaml 的 Dune 构建系统,涵盖项目设置、库和可执行文件的构建以及测试。
暂无内容
查看缓存全文
缓存时间: 2026/06/08 18:18
# OCaml 入门:Dune 构建系统简介
来源:https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/
日期:2025\-07\-29
---
一只骆驼坐在沙漠中央的沙丘上。他戴着安全帽,正在从 OCaml 代码的构建和运行中休息。想要开始构建自己的项目吗?请跟随下面的足迹。(https://ocamlpro.com/blog/assets/img/ai_construction_camel.png)
一只骆驼坐在沙漠中央的沙丘上。他戴着安全帽,正在从 OCaml 代码的构建和运行中休息。想要开始构建自己的项目吗?请跟随下面的足迹。
#### 欢迎各位 Camleer
我们又回来为 OCaml 生态系统的新手提供另一个实用演练。根据多年收集的反馈,我们了解到,入门 OCaml 发行版有时在最初可能被认为具有挑战性。这就是为什么我们在规划每篇文章时都牢记这一点——让您的上手过程更加顺畅和易于理解。
今天的话题就是一个很好的例子:它源于我们制作最新的`opam 深度探索`系列中的*Opam 103:使用 opam 引导新 OCaml 项目 (https://ocamlpro.com/blog/2025_04_29_opam_103_starting_new_project/)* 的过程中。
我们意识到,我们假设读者对工具链有一定熟悉程度,而我们从未明确解释或说明过。因此,我们决定为寻找快速、即时 OCaml 教程的新手开发者编写一份简短实用的指南。🛠️
### Camleer 的基础知识:Dune (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#basics)
如果你是 OCaml(或其他编程语言)的新手,你首先会遇到的需求就是**构建**、**运行**和**测试**你的代码。幸运的是,有一个强大的构建系统叫做`dune`可以使用。它非常普遍,使项目设置和编译变得简单明了。理解`dune`的工作方式是成为 OCaml 生态系统中高效一员的关键步骤。
在本文中,我们将引导您了解使用`dune`**构建库**、**可执行文件**和**测试**,以及管理您的**项目结构**的基本知识。无论您是在编写第一个 OCaml 程序,还是进入一个基于 dune 的现有代码库,本指南都将帮助您快速上手并运行。
> 我们坚信**从零开始是接触全新技术主题的关键**——今天的话题也不例外。任何曾在探索新代码库时感到迷茫的人都知道,最小化的玩具示例通常是建立直觉的最佳方式。
**目录**
- Camleer 的基础知识:Dune (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#basics)
- 资源 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#ressources)
- 项目元数据和构建规范文件 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#setupyourproject)- dune-project (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#duneproject) - dune 文件 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#dunefile)- 关键节 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#keystanzas)
- 构建和运行您的项目 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#buildyourproject)- dune build (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#dunebuild) - dune build @doc (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#dunebuilddoc) - dune exec \-\- (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#duneexec)
- 使用 Dune 测试您的项目 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#testyourproject)- Cram 测试 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#dunecramtests) - dune runtest (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#duneruntest)
- 使用 dune init 搭建脚手架 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#duneinit)
### 资源 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#ressources)
如前所述,本文是在最新的*Opam 103:使用 opam 引导新 OCaml 项目 (https://ocamlpro.com/blog/2025_04_29_opam_103_starting_new_project/)* 的背景下撰写的。那篇文章解释了当 OCaml 开发者打算将项目与`opam`一起使用时,应该如何构建 OCaml 项目的结构。
今天话题的重点是关注 OCaml 项目结构的另一个决定性参数:您的构建系统。目标是展示`opam`和`dune`的工作流程如何协同工作,同时让您对 dune 的基础知识有扎实的了解。
我们使用同一个玩具项目 (https://gitlab.ocamlpro.com/raja/opam_bps_examples/-/tree/dune-minimal/opam-103)`helloer`作为本演示的基础。这是一个简单、范围清晰的示例,其结构对于 opam 和 dune 都是惯用的,非常适合演示基础知识,而不会引入不必要的复杂性。
请注意,`helloer`并非使用本文末尾将介绍的`dune init`创建的。首先,理解 Dune 的内部工作原理很重要——这样你才能知道它为你生成了什么,如何自信地修改它,以及它如何融入你的整体构建工作流程。
> 可以考虑查看**Dune 的官方参考手册 (https://dune.readthedocs.io/en/latest/reference/index.html)**或访问**官方 OCaml Discuss (https://discuss.ocaml.org/)**论坛以联系 OCaml 社区。
### 项目元数据和构建规范文件 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#setupyourproject)
#### dune-project (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#duneproject)
让我们首先从`dune-project`文件开始,因为每个由 Dune 驱动的项目都应该在其根目录下有一个这样的文件。
这个文件是您项目的**入口点**,其内容是其元数据——Dune 用这些元数据来理解您的项目是如何组织的。
这些元数据包括:
- 您使用的`dune`版本;
- 您项目生命周期的重要 URL;
- 可选设置,如依赖项、许可证、文档;
- 甚至包括自动生成 opam 文件的配置。**更多信息请参见 Opam 103 (https://ocamlpro.com/blog/2025_04_29_opam_103_starting_new_project)**。
这些信息不仅指导 Dune,还帮助 opam 等工具理解如何构建、分发和记录您的项目。
``
$ cat dune-project
(lang dune 3.15)
(package (name helloer))
(cram enable)
``
注意:第一行必须是`\(lang dune X\.Y\)`——不要有注释或额外的空白。此行决定了 dune 将识别哪些功能和语法。
> **注意**:您可以在**官方文档** (https://dune.readthedocs.io/en/latest/reference/dune-project/index.html)👈 中找到所有补充信息。
#### dune 文件 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#dunefile)
一个`dune`文件是一个**构建规范**文件,告诉 Dune 如何编译特定目录中的 OCaml 代码。
通常每个子目录都有一个`dune`文件,描述该目录下的内容——库、可执行文件或一些测试。由于我们的玩具项目`helloer`结构是扁平的,我们将此文件放在项目的根目录 (https://gitlab.ocamlpro.com/raja/opam_bps_examples/-/tree/dune-minimal/opam-103)。
``
$ cat dune
(library
(name helloer_lib)
(modules helloer_lib)
)
(executable
(public_name helloer)
(name helloer)
(libraries cmdliner helloer_lib)
(modules helloer)
)
(test
(name test)
(libraries alcotest helloer_lib)
(modules test)
)
``
**实际上,这告诉`dune`:**
- 如何编译该目录中的 OCaml 文件;
- 如何定义**库**、**可执行文件**和**测试目标**。
#### 关键节 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#keystanzas)
在 Dune 的上下文中,一个**节 (https://dune.readthedocs.io/en/stable/overview.html#term-stanza)**只是**配置块**的一个花哨说法。它告诉构建系统你想要定义哪种类型的工件——可以是库、可执行文件、测试、文档别名,甚至是一个可安装的二进制文件。每个节都位于`dune`文件中,并遵循结构化的声明式语法。
它们通常按用途分组,每种类型都有自己预期的字段。每个节都值得深入探讨,但这里有一个快速概览来帮助你入门。
##### `library`节 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#librarystanza)
``
(library
(name helloer_lib)
(modules helloer_lib)
)
``
> 一个`library`节告诉 Dune 如何将一组模块编译成一个可重用的包。
**此节的目的**:
- 定义一个名为`helloer_lib`的库;
- 该库将由模块`helloer_lib.ml`构建(默认情况下,每个 .ml 文件定义了一个同名的模块);
- 并且只应列出公开的模块——即那些旨在作为库公共 API 一部分并可供项目其他部分或外部代码使用的模块。
OCaml 模块名称应与文件名匹配,因此该目录中应存在`helloer_lib.ml`。
##### `executable`节 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#executablestanza)
``
(executable
(public_name helloer)
(name helloer)
(libraries cmdliner helloer_lib)
(modules helloer)
)
``
> 一个`executable`节说明了如何将一些代码打包成一个可运行的二进制文件。
**目的**:
- `name`:构建一个名为`helloer`的可执行文件;
- 需要库:**外部**的`cmdliner`(用于 CLI 解析)和**内部的**`helloer_lib`(我们自己的库);
- `public_name helloer`:这使得可执行文件公开可用。例如,在`opam`文件中用于`dune install helloer`。
> 你可以了解如何在最新的 Opam 103 博文 (https://ocamlpro.com/blog/2025_04_29_opam_103_starting_new_project/#clitooling) 中找到并安装`cmdliner`,你还可以在那里找到`opam`文件的简单分解 (https://ocamlpro.com/blog/2025_04_29_opam_103_starting_new_project/#minimalopamfile)。
##### `test`节 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#teststanza)
``
(test
(name test)
(libraries alcotest helloer_lib)
(modules test)
)
``
**它的作用**:
- 声明一个名为`test`的测试目标,定义在文件`test.ml`中。一个`test`节将该可执行文件注册为`runtest`规则别名的一部分,这意味着当你调用`dune runtest`(或其别名`dune test`)时,它将自动编译和运行;
- 使用`alcotest`测试库;
- 也使用`helloer_lib`来测试其功能。
---
**现在你的项目已经设置并组织好了。接下来,让我们看看如何构建它。**
### 构建和运行您的项目 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#buildyourproject)
#### `dune build` (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#dunebuild)
如下所示,`dune build @all`命令将构建你的`dune`文件中定义的**所有**目标,这是`dune build`命令的默认行为。
``
$ tree
.
├── dune
├── dune-project
├── helloer_lib.ml
├── helloer.ml
├── helloer.opam
└── test.ml
$ dune build @all
$ tree -L 2
.
├── _build
│ ├── default
│ │ ├── helloer.exe // 构建目录中的可执行文件
│ │ ├── helloer_lib.cmxs // 构建的库
│ │ ├── test.exe // 测试可执行文件
│ │ └── [...]
│ ├── install
│ └── log
├── dune
├── dune-project
├── helloer_lib.ml
├── helloer.ml
├── helloer.opam
└── test.ml
``
**解释**:
- `@all`是一个别名,包含你的 dune 文件中所有可构建的目标:可执行文件、库、测试、文档等;
- 它对于执行完整构建以确保所有内容都能编译非常有用。
你也可以使用自定义别名(如`@doc`、`@runtest`等),或者在你的 dune 文件中定义自己的别名。
#### `dune build @doc` (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#dunebuilddoc)
一旦你的代码构建成功并且项目有合适的`dune-project`文件,你可以使用以下命令生成文档:
``
$ dune build @doc
``
**它的作用**:
- 在后台使用`odoc`从你的 OCaml 代码构建 API 文档。这意味着安装`odoc`是使用此功能的必要条件,只需执行`opam install odoc`即可;
- 在`_build/default/_doc/_html/`中构建 HTML 文件。
确保你的`dune-project`文件包含一个`\(package \.\.\.\)`节,并且你的库已使用 OCaml 注释`\(\*\* your comment \*\)`正确记录。
你可以在此处查看为玩具项目生成的文档 (https://github.com/OCamlPro/opam_bp_examples/commit/5ec8dd28115f72df44fd9f1b4de4379d2bf54d5f)
> **注意**:你可以在**官方文档** (https://ocaml.github.io/odoc/odoc/odoc_for_authors.html)👈 中找到所有补充信息。
构建完成后,你可以查看生成的文档:
``
$ open _build/default/_doc/_html/index.html
``
**这对于检查模块接口或在线发布文档非常有用。**
#### `dune exec \-\-` (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#duneexec)
此命令用于**运行项目**中定义的可执行文件。
**因此,类似这样**:
``
$ dune exec -- ./helloer.exe
Hello OCamlers!!
$ dune exec -- ./helloer.exe --gentle
Welcome my dear OCamlers.
``
这告诉 dune 在必要时构建可执行文件,然后运行它。`\-\-`将 dune 选项与可执行文件及其参数分隔开。`\-\-`后面的第一项是要运行的可执行文件。
这可以是:
- 指向构建目标的相对路径,例如:`dune exec \-\- \./path/to/executable`
- 已安装可执行文件的公共名称,即:`dune exec \-\- \./helloer`。
可执行文件名称之后的所有额外参数(如`\-\-gentle`)都将传递给可执行文件本身。
本质上,`dune exec \-\- COMMAND`的行为与先调用`dune install`然后**接着**调用`COMMAND`相同。
> **注意**:如果你想将可执行文件复制到项目根目录(在`_build/`之外),可以在可执行文件节中添加`\(promote \(until\-clean\)\)`。
---
**很好,我们的小项目构建并运行顺利,现在开始测试它。**
### 使用 Dune 测试您的项目 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#testyourproject)
在我们的`helloer`项目中,我们在内部的`helloer_lib`上使用了`alcotest`库。这是相当标准的。然而,测试可执行文件本身可以在**不依赖**外部工具的情况下,借助**cram 测试**来完成。
#### Cram 测试 (https://ocamlpro.com/blog/2025_07_29_ocaml_onboarding_introduction_to_dune/#dunecramtests)
Dune 支持一种特殊类型的测试,称为 cram 测试,其灵感来源于原始的 Cram (https://bitheap.org/cram/),它检查命令行示例是否产生预期的输出。
*“预期输出”*就是 shell 会话本身以及你的可执行文件在测试运行中针对特定调用所打印的内容,并将结果与之比较。
要创建一个 cram 测试,你只需编写一个包含一系列类似 shell 的会话(由空行分隔)的`.t`文件,如下所示:
``
$ helloer
Hello OCamlers!!
$ helloer --gentle
Welcome my dear OCamlers.
``
**工作原理**:
- 它运行`.t`文件中的命令;
- 它将我们的二进制文件打印到`stdout`的内容与 cram 文件中编写的预期输出进行比较;
- 如果输出不同则失败。但是,当你希望将实际输出替换为新的预期输出时,可以使用`dune promote`。
相似文章
太空中的O(x)Caml
一个纯OCaml实现的CCSDS协议栈,代号Borealis,在低地球轨道上的DPhi Space ClusterGate-2载荷模块上成功启动,展示了在太空中安全且高性能的OCaml。
OxCaml 中的数据竞态自由
OxCaml 是 Jane Street 对 OCaml 编译器的分支,它引入了编译时对数据竞态的保证,从而在不增加运行时开销的情况下实现顺序一致性。这篇博文解释了新的模式轴及其对并行编程的影响。
@charles_irl: CuTe 和 CuTe DSL 文章包含最小代码片段,说明核心原则和基本用法。这些片段…
CuTe 和 CuTe DSL 文章提供了最小代码片段和 Modal Notebooks,以便动手学习。
@DeRonin_: 任何使用或学习智能体系统的人都应该读一读这个。我在每个新智能体项目前执行的安装顺序:1.…
一条分享智能体项目结构化安装顺序的推文:使用 direnv 配合密码管理器保障凭证安全,使用 litellm 或 portkey 作为模型代理管理成本和回退,使用 uv + git 在评估通过时提交以确保可复现性,使用 mitmproxy 实现 LLM 调用的全面可观测性。重点介绍了常见故障模式和安全漏洞。
Dune
Dune 是一款上下文感知的 Mac 按键板,专为自动化工作流与会议流程而设计,目前已作为新品在 Product Hunt 平台发布。