MoonBit 构建系统教程
moon
是 MoonBit 语言的构建系统,目前基于 n2 项目。moon
支持并行构建和增量构建,此外它还支持管理和构建 mooncakes.io 上的第三方包。
准备工作
在开始之前,请确保安装好以下内容:
-
MoonBit CLI 工具: 从这里下载。该命令行工具用于创建和管理 MoonBit 项目。
使用
moon help
命令可查看使用说明。$ moon help ...
-
Moonbit Language Visual Studio Code 插件: 可以从 VS Code 市场安装。该插件为 MoonBit 提供了丰富的开发环境,包括语法高亮、代码补全、交互式除错和测试等功能。
安装完成后,让我们开始创建一个新的 MoonBit 模块。
创建一个新模块
使用 moon new
创建一个新项目,默认的配置是:
$ 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
这会在 my-project
下创建一个名为 username/hello
的新模块。上述过程也可以使用 moon new my-project
代替。
了解模块目录结构
上一步所创建的模块目录结构如下所示:
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
用来标记这个目录是一个模块。它包含了模块的元信息,例如模块名、版本等。{ "name": "username/hello", "version": "0.1.0", "readme": "README.md", "repository": "", "license": "Apache-2.0", "keywords": [], "description": "", "source": "src" }
source
字段指定了模块的源代码目录,默认值是src
。该字段存在的原因是因为 MoonBit 模块中的包名与文件路径相关。例如,当前模块名为username/hello
,而其中一个包所在目录为lib/moon.pkg.json
,那么在导入该包时,需要写包的全名为username/hello/lib
。有时,为了更好地组织项目结构,我们想把源码放在src
目录下,例如src/lib/moon.pkg.json
,这时需要使用username/hello/src/lib
导入该包。但一般来说我们并不希望src
出现在包的路径中,此时可以通过指定"source": "src"
来忽略src
这层目录,便可使用username/hello/lib
导入该包。 -
src/lib
和src/main
目录:这是模块内的包。每个包可以包含多个.mbt
文件,这些文件是 MoonBit 语言的源代码文件。但是,无论一个包有多少.mbt
文件,它们都共享一个moon.pkg.json
文件。lib/*_test.mbt
是lib
包中的独立测试文件,这些文件用于黑盒测试,无法直接访问lib
包的私有成员。 -
moon.pkg.json
是包描述文件。它定义了包的属性,例如,是否是main
包,所导入的其他包。-
main/moon.pkg.json
:{ "is_main": true, "import": [ "username/hello/lib" ] }
这里,
"is_main: true"
表示这个包需要被链接成目标文件。在wasm/wasm-gc
后端,会被链接成一个wasm
文件,在js
后端,会被链接成一个js
文件。-
lib/moon.pkg.json
:{}
这个文件只是为了告诉构建系统当前目录是一个包。
-
如何使用包
我们的 username/hello
模块包含两个包:username/hello/lib
和 username/hello/main
。
包 username/hello/lib
包含 hello.mbt
和 hello_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"
}
]
}
这行导入了 username/hello/lib/fib
包。导入后,你可以在 main/main.mbt
中使用 fib
包了。
将 main/main.mbt
的文件内容替换为:
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())
}
为了执行程序,需要在 moon run
命令中指定 username/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 build
和 moon 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)
}
注意,构建系统会自动为以 _test.mbt
结尾的文件创建一个新的包,用于黑盒测试,并且自动导入当前包。因此,在测试块中需要使用 @fib
来引用 username/hello/lib/fib
包,而不需要在 moon.pkg.json
中显式地导入该包。
最后,使用 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.