MoonBit 构建系统教程#

Moon 是 MoonBit 语言的构建系统,目前基于 n2 项目。Moon 支持并行和增量构建。此外,moon 还在 mooncakes.io 上管理和构建第三方包。

先决条件#

在开始本教程之前,请确保你已安装以下内容:

  1. MoonBit CLI 工具:从 https://www.moonbitlang.cn/download/ 下载。这个命令行工具用于创建和管理 MoonBit 项目。

    使用 moon help 查看使用说明。

    $ moon help
    ...
    
  2. Visual Studio Code 中的 MoonBit 语言 插件:你可以从 VS Code 市场安装。此插件为 MoonBit 提供了丰富的开发环境,包括语法高亮、代码补全等功能。

一旦你满足了这些先决条件,我们就可以开始创建一个新的 MoonBit 模块。

创建一个新模块#

要创建一个新模块,在终端中输入 moon new <path> 命令,其中 path 是你想要创建项目的文件夹目录,之后项目会被创建。使用默认值,你会在 my_project 目录中创建一个名为 username/my_project 的新模块。

$ moon new my_project
Initialized empty Git repository in my_project/.git/
Created username/my_project at my_project

你也可以通过使用 --user 选项和 --name 选项分别指定用户名和模块名称。如果你已经登录,用户名将默认为你的用户名。

理解模块目录结构#

创建新模块后,你的目录结构应该如下:

my_project
├── Agents.md
├── cmd
│   └── main
│       ├── main.mbt
│       └── moon.pkg.json
├── LICENSE
├── moon.mod.json
├── moon.pkg.json
├── my_project_test.mbt
├── my_project.mbt
├── README.mbt.md
└── README.md -> README.mbt.md

备注

在 Windows 系统上,你需要管理员权限或启用开发者模式才能创建符号链接。

以下是目录结构的简要说明:

  • moon.mod.json 用于标识目录为 MoonBit 模块。它包含模块的元数据,如模块名称、版本等。

    {
      "name": "username/my_project",
      "version": "0.1.0",
      "readme": "README.md",
      "repository": "",
      "license": "Apache-2.0",
      "keywords": [],
      "description": ""
    }
    
  • .cmd/main 目录:这些是模块中的包。每个包可以包含多个 .mbt 文件,这些文件是 MoonBit 语言的源代码文件。但是,无论一个包有多少 .mbt 文件,它们都共享一个公共的 moon.pkg.json 文件。*_test.mbt 是独立测试文件,这些文件用于黑盒测试,因此同一个包的私有成员不能直接访问。

  • moon.pkg.json 是包描述符。它定义了包的属性,例如它是否是 main 包以及它导入的包。

    • cmd/main/moon.pkg.json:

      {
        "is-main": true,
        "import": [
          {
            "path": "username/my_project",
            "alias": "lib"
          }
        ]
      }
      

      这里,"is_main: true" 声明该包包含 moon run 指令的入口。

    • moon.pkg.json:

      {}
      

      此文件为空。它的目的只是告诉构建系统这个文件夹是一个包。

  • README.mbt.md 是 README 文件。内部编写的代码块将通过 moon checkmoon test 进行类型检查和测试。

使用包#

我们的 username/my_project 模块包含两个包:username/my_projectusername/my_project/cmd/main

username/my_project 包包含 my_project.mbtmy_project_test.mbt 文件:

my_project.mbt#
///|
pub fn fib(n : Int) -> Int64 {
  for i = 0, a = 0L, b = 1L; i < n; i = i + 1, a = b, b = a + b {

  } else {
    b
  }
}
my_project_test.mbt#
///|
test "fib" {
  let array = [1, 2, 3, 4, 5].map(fib(_))

  // `inspect` is used to check the output of the function
  // Just write `inspect(value)` and execute `moon test --update`
  // to update the expected output, and verify them afterwards
  inspect(array, content="[1, 2, 3, 5, 8]")
}

备注

生成的文件名取决于包名。

username/my_project/cmd/main 包包含一个 main.mbt 文件:

///|
fn main {
  println(@lib.fib(10))
}

要执行程序,在 moon run 命令中指定文件系统路径到 username/my_project/cmd/main 包:

$ moon run cmd/main
89

你可以使用 moon test 命令进行测试:

$ moon test
Total tests: 1, passed: 1, failed: 0.

包导入#

在 MoonBit 的构建系统中,模块的依赖被定义在包级别。要在 username/my_project/cmd/main 中导入 username/my_project 包,你需要在 cmd/main/moon.pkg.json 中指定:

{
  "is-main": true,
  "import": [
    {
      "path": "username/my_project",
      "alias": "lib"
    }
  ]
}

这里,username/my_project 指定从 username/my_project 模块导入 username/my_project 包,并且有别名 lib,所以你可以在 cmd/main/main.mbt 中使用 @lib.fib(10)

创建和使用新包#

首先,在 lib 下创建一个名为 fib 的新目录:

mkdir fib

现在,你可以在 fib 下创建新文件:

slow.mbt#
pub fn fib_slow(n : Int) -> Int {
  match n {
    0 => 0
    1 => 1
    _ => fib_slow(n - 1) + fib_slow(n - 2)
  }
}
fast.mbt#
pub fn fib_fast(num : Int) -> Int {
  fn aux(n, acc1, acc2) {
    match n {
      0 => acc1
      1 => acc2
      _ => aux(n - 1, acc2, acc1 + acc2)
    }
  }

  aux(num, 0, 1)
}
moon.pkg.json#
{}

在创建这些文件后,你的目录结构应该如下:

.
├── Agents.md
├── cmd
│   └── main
│       ├── main.mbt
│       └── moon.pkg.json
├── fib
│   ├── fast.mbt
│   ├── moon.pkg.json
│   └── slow.mbt
├── LICENSE
├── moon.mod.json
├── moon.pkg.json
├── my_project_test.mbt
├── my_project.mbt
├── README.mbt.md
└── README.md -> README.mbt.md

cmd/main/moon.pkg.json 文件中,导入 username/my_project/fib 包并将其别名定制为 my_awesome_fibonacci

{
  "is_main": true,
  "import": [
    {
      "path": "username/my_project/fib",
      "alias": "my_awesome_fibonacci"
    }
  ]
}

这一行导入了 fib 包,它是 username/my_project 模块中 fib 包的一部分。在这样做之后,你可以在 cmd/main/main.mbt 中使用 fib 包。

fn main {
  let a = @my_awesome_fibonacci.fib_slow(10)
  let b = @my_awesome_fibonacci.fib_fast(11)
  println("fib(10) = \{a}, fib(11) = \{b}")
}

为了执行你的程序,指定 main 包的路径:

$ moon run cmd/main
fib(10) = 55, fib(11) = 89

添加测试#

让我们添加一些测试来验证我们的 fib 实现。在 fib/fib_test.mbt 中添加以下内容:

test {
  inspect(fib_slow(0))
  inspect(fib_slow(1))
  inspect(fib_slow(2))
}

这段代码测试斐波那契数列的前三项。test { ... } 定义了一个内联测试块。内联测试块中的代码在测试模式下执行。

内联测试块在非测试编译模式(moon buildmoon run)中被丢弃,因此它们不会导致生成的代码大小膨胀。

这里我们使用了快照测试。执行 moon test --update,文件应该被更改为:

test {
  inspect(@fib.fib_slow(0), content="0")
  inspect(@fib.fib_slow(1), content="1")
  inspect(@fib.fib_slow(2), content="1")
}

注意,测试代码使用 @fib 来引用 fib 包。构建系统通过使用以 _test.mbt 结尾的文件自动为黑盒测试创建一个新包。

最后,使用 moon test 命令,它会扫描整个项目,识别并运行所有测试。如果一切正常,你将看到:

$ moon test
Total tests: 2, passed: 2, failed: 0.