OCaml 入门:Dune 构建系统全方位解析,助你快速上手开发!
发布时间:2026/6/10 0:56:22
分类:文化教育
浏览:1234

一位 OCaml 开发者的基础Dune刚接触 OCaml 或其他编程语言首先要面对代码的构建、运行和测试。好在有强大的构建系统 dune它应用广泛能让项目设置和编译简单直接。理解 dune 工作原理是在 OCaml 生态系统高效开发的关键一步。本文将带你了解如何用 dune 构建库、可执行文件和测试以及管理项目结构。无论编写首个 OCaml 程序还是处理基于 dune 的新代码库这篇指南都能助你快速上手。我们坚信从零开始是学习全新技术主题的关键 —— 今天的主题也不例外。任何在探索新代码库时感到迷茫的人都知道简单示例往往是培养直觉的最佳方式。参考资源本文在最新的 [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 官方讨论论坛](https://discuss.ocaml.org/) 与 OCaml 社区交流。项目元数据和构建规范文件dune-project每个由 Dune 驱动的项目根目录下都应有 dune-project 文件。它是项目入口点内容是项目元数据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 文件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 文件库、可执行文件和测试目标如何定义。关键节stanzas在 Dune 里[节stanza](https://dune.readthedocs.io/en/stable/overview.html#term-stanza) 指配置块告诉构建系统要定义的工件类型 —— 可以是库、可执行文件、测试、文档别名甚至可安装的二进制文件。每个节在 dune 文件中遵循结构化声明性语法。它们通常按用途分组每种类型有预期字段。每个节都值得深入研究这里先快速概述。library 节(library(name helloer_lib)(modules helloer_lib))library 节告诉 Dune 如何将一组模块编译成可重用包。此节用途定义名为 helloer_lib 的库该库由模块 helloer_lib.ml 构建默认每个 .ml 文件定义同名模块只列公开模块即作为库公共 API 一部分可供项目其他部分或外部代码使用的模块。OCaml 模块名应与文件名匹配所以 helloer_lib.ml 文件应在该目录中。executable 节(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 博客文章 [中了解如何在 opam 中查找和安装 cmdliner](https://ocamlpro.com/blog/2025_04_29_opam_103_starting_new_project/#clitooling)那里还有 [一个简单的 opam 文件分解](https://ocamlpro.com/blog/2025_04_29_opam_103_starting_new_project/#minimalopamfile)。test 节(test(name test)(libraries alcotest helloer_lib)(modules test))作用声明名为 test 的测试目标定义在 test.ml 文件中。test 节将该可执行文件注册为 runtest 规则别名一部分调用 dune runtest或其别名 dune test时它将被编译并自动运行使用 alcotest 测试库也用 helloer_lib 测试其功能。构建并运行你的项目dune builddune 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代码能成功构建且项目有合适的 dune-project 文件可用以下命令生成文档$ dune build doc作用幕后用 odoc 从 OCaml 代码构建 API 文档使用此功能须安装 odoc执行 opam install odoc 即可在 _build/default/_doc/_html/ 目录下生成 HTML 文件。确保 dune-project 文件含 (package ...) 节且库用 OCaml 注释 (** 注释 *) 适当文档化。可在 [这里](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 --此命令用于运行项目中定义的可执行文件。例如$ dune exec -- ./helloer.exeHello OCamlers!!$ dune exec -- ./helloer.exe --gentleWelcome 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 测试你的项目Cram 测试在 helloer 项目中内部 helloer_lib 使用 alcotest 库很常见。借助 Cram 测试可在不依赖外部工具的情况下测试可执行文件本身。Dune 支持 Cram 测试灵感源于原始的 [Cram](https://bitheap.org/cram/)用于检查命令行示例是否产生预期输出。“预期输出” 指 shell 会话本身测试运行时可执行文件输出会与 Cram 文件中写入的预期输出对比。创建 Cram 测试只需编写 .t 文件包含一系列类似 shell 的会话用空行分隔如下$ helloerHello OCamlers!!$ helloer --gentleWelcome my dear OCamlers.工作原理运行 .t 文件中的命令将二进制文件输出到 stdout 的内容与 Cram 文件中写入的预期输出比较输出不同则测试失败。对二进制文件输出到 stdout 的内容更改时可用 dune promote 命令将所有失败测试替换为新输出。可在 [这里](https://github.com/OCamlPro/opam_bp_examples/commit/8415437d2a3d13c890af4eb7406f0803a185d6a6) 测试它。dune runtest可用以下命令运行所有测试$ dune runtest构建项目中定义的 test 目标查找以 .t 结尾的文件或标记为测试的 .ml 文件执行测试通常用 _expect_ 风格测试如 ppx_expect 或 alcotest。很简单有 inline_tests 节或 expect 测试它会运行并告知是否有失败情况。例如有效 Cram 测试输出如下$ dune runtestTesting Tests.This run has ID N39NJ5ZE.[OK] messages 0 normal.[OK] messages 1 gentle.Full test results in ~/ocamler/dev/helloer/_build/default/_build/_tests/Tests.Test Successful in 0.000s. 2 tests run.若一个测试失败会看到类似以下输出$ dune runtestFile test.t, line 1, characters 0-0:diff --git a/_build/.sandbox/e6d6dcfb864b62e42104889af2a44f23/default/test.t b/_build/.sandbox/e6d6dcfb864b62e42104889af2a44f23/default/test.t.correctedindex f79b63c..70c7a17 100644--- a/_build/.sandbox/e6d6dcfb864b62e42104889af2a44f23/default/test.t b/_build/.sandbox/e6d6dcfb864b62e42104889af2a44f23/default/test.t.corrected -3,7 3,7 Default behaviourHello OCamlers!!Gentle behaviour$ helloer --gentle- Welcome my deer OCamlers. Welcome my dear OCamlers.Unknown behaviour$ helloer --unknownhelloer: unknown option --unknown.File dune, line 16, characters 7-11:16 | (name test)^^^^Testing Tests.This run has ID 1OS0H3WP.[OK] messages 0 normal. [FAIL] messages 1 gentle.