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 命令,你将看到模块创建向导。通过使用所有默认值,你可以在 my-project 目录中创建一个名为 username/hello 的新模块。

$ moon new
Enter the path to create the project (. for current directory): my-project
Select the create mode: exec
Enter your username: username
Enter your project name: hello
Enter your license: Apache-2.0
Created my-project

如果你想使用所有默认值,你可以使用 moon new my-projectmy-project 目录中创建一个名为 username/hello 的新模块。

理解模块目录结构#

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

my-project
├── LICENSE
├── README.md
├── moon.mod.json
└── src
    ├── lib
       ├── hello.mbt
       ├── hello_test.mbt
       └── moon.pkg.json
    └── main
        ├── main.mbt
        └── moon.pkg.json

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

  • moon.mod.json 用于标识目录为 MoonBit 模块。它包含模块的元数据,如模块名称、版本等。source 指定模块的源目录。默认值是 src

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

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

    • main/moon.pkg.json:

      {
        "is_main": true,
        "import": [
          "username/hello/lib"
        ]
      }
      

    这里,"is_main: true" 声明该包需要被构建系统链接为一个 Wasm 文件。

    • lib/moon.pkg.json:

      {}
      

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

使用包#

我们的 username/hello 模块包含两个包:username/hello/libusername/hello/main

username/hello/lib 包包含 hello.mbthello_test.mbt 文件:

hello.mbt

pub fn hello() -> String {
    "Hello, world!"
}

hello_test.mbt

test "hello" {
  if @lib.hello() != "Hello, world!" {
    fail!("@lib.hello() != \"Hello, world!\"")
  }
}

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

fn main {
  println(@lib.hello())
}

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

$ moon run ./src/main
Hello, world!

你也可以省略 ./

$ moon run src/main
Hello, world!

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

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

包导入#

在 MoonBit 的构建系统中,模块的名称用于引用其内部包。要在 src/main/main.mbt 中导入 username/hello/lib 包,你需要在 src/main/moon.pkg.json 中指定:

{
  "is_main": true,
  "import": [
    "username/hello/lib"
  ]
}

这里,username/hello/lib 指定从 username/hello 模块导入 username/hello/lib 包,所以你可以在 main/main.mbt 中使用 @lib.hello()

请注意,src/main/moon.pkg.json 中导入的包名是 username/hello/lib,在 src/main/main.mbt 中使用 @lib 来引用这个包。这里的导入实际上为包名 username/hello/lib 生成了一个默认别名。在接下来的章节中,你将学习如何为包定制别名。

创建和使用新包#

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

mkdir src/lib/fib

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

a.mbt:

pub fn fib(n : Int) -> Int {
  match n {
    0 => 0
    1 => 1
    _ => fib(n - 1) + fib(n - 2)
  }
}

b.mbt:

pub fn fib2(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:

{}

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

my-project
├── LICENSE
├── README.md
├── moon.mod.json
└── src
    ├── lib
       ├── fib
          ├── a.mbt
          ├── b.mbt
          └── moon.pkg.json
       ├── hello.mbt
       ├── hello_test.mbt
       └── moon.pkg.json
    └── main
        ├── main.mbt
        └── moon.pkg.json

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

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

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

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

  println(@lib.hello())
}

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

$ moon run ./src/main
fib(10) = 55, fib(11) = 89
Hello, world!

添加测试#

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

src/lib/fib/a.mbt

test {
  assert_eq!(fib(1), 1)
  assert_eq!(fib(2), 1)
  assert_eq!(fib(3), 2)
  assert_eq!(fib(4), 3)
  assert_eq!(fib(5), 5)
}

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

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

黑盒测试的独立测试文件#

除了内联测试,MoonBit 还支持独立测试文件。以 _test.mbt 结尾的源文件被视为黑盒测试的测试文件。例如,在 src/lib/fib 目录中,创建一个名为 fib_test.mbt 的文件,并粘贴以下代码:

src/lib/fib/fib_test.mbt

test {
  assert_eq!(@fib.fib(1), 1)
  assert_eq!(@fib.fib2(2), 1)
  assert_eq!(@fib.fib(3), 2)
  assert_eq!(@fib.fib2(4), 3)
  assert_eq!(@fib.fib(5), 5)
}

注意,测试代码使用 @fib 来引用 username/hello/lib/fib 包。构建系统通过使用以 _test.mbt 结尾的文件自动为黑盒测试创建一个新包。这个新包将自动导入当前包,允许你在测试代码中使用 @lib

最后,使用 moon test 命令,它会扫描整个项目,识别并运行所有内联测试以及以 _test.mbt 结尾的文件。如果一切正常,你将看到:

$ moon test
Total tests: 3, passed: 3, failed: 0.
$ moon test -v
test username/hello/lib/hello_test.mbt::hello ok
test username/hello/lib/fib/a.mbt::0 ok
test username/hello/lib/fib/fib_test.mbt::0 ok
Total tests: 3, passed: 3, failed: 0.