Menu

Zero 包详解:zero.json、src/ 与构建目标

Zero 包的结构:zero.json 清单文件、src/ 目录,以及让一个包从同一棵源代码树生成可执行文件、库和测试的构建目标机制。

为什么需要包

学习这门语言或者试一段代码时,一个 .0 文件就够了。可一旦项目超过一个文件,你就需要一个包:一个带清单文件、采用工具链能识别的固定结构的目录。

从零散文件转到包,有这些好处:

  • 项目的名称、版本和元数据有一个统一的存放位置。
  • 一棵源代码树里可以有多个入口(可执行文件、库、测试)。
  • 结构可预测:工具无需配置就能找到你的源代码。
  • 可以对整棵源代码树运行 zero check / zero build,而不用一个文件一个文件地处理。

生成包的脚手架

最快的开始方式是 zero new:

zero new cli hello

这会创建一个 hello/ 目录,结构如下:

hello/
├── zero.json
└── src/
    └── main.0

cli 是模板名,它会生成一个可执行的命令行程序。其他模板(库、系统程序)结构相同,只是默认设置不同。

用 cd 进入新目录,运行它,就可以开始了:

cd hello
zero run

在包目录里运行 zero run 而不指定文件时,它会从 zero.json 中读取默认目标并运行。

zero.json 清单文件

用 cli 模板生成的包,清单文件是这样的:

{
    "package": { "name": "hello", "version": "0.1.0" },
    "targets": { "cli": { "kind": "exe", "main": "src/main.0" } }
}

它有两个顶层键:package 和 targets。前者标识这个包,后者告诉编译器要构建什么。

package

"package": {
    "name": "hello",
    "version": "0.1.0"
}
  • name:标识包的短名称。使用小写字母和连字符。
  • version:语义化版本字符串。1.0 之前的包使用 0.x.y。

可能还支持其他元数据字段(描述、作者、许可证、代码仓库)。清单格式仍在演进,权威的结构请以当前的 Zero 文档为准。

targets

"targets": {
    "cli": { "kind": "exe", "main": "src/main.0" }
}

键(这里是 cli)是你自己起的目标名称。值描述每个目标:

  • kind:目标的类型。exe 表示可执行文件。其他类型(库、测试)结构相同。
  • main:入口源文件,路径相对于包的根目录。

同一个包里可以声明多个目标:

{
    "package": { "name": "image-tools", "version": "0.1.0" },
    "targets": {
        "convert": { "kind": "exe", "main": "src/convert.0" },
        "resize":  { "kind": "exe", "main": "src/resize.0" },
        "lib":     { "kind": "lib", "main": "src/lib.0" }
    }
}

在 CLI 中写上目标名称,就能构建指定的目标:

zero build convert
zero run resize

src/ 目录

所有源文件都放在 src/ 下。编译器会自动遍历这个目录,你不需要在清单文件中列出每个文件。每个目标的 main 字段指向它的入口文件,编译器从那里顺着导入找到需要的其他所有内容。

一个带有几个辅助模块的包可能是这样的:

image-tools/
├── zero.json
└── src/
    ├── convert.0
    ├── resize.0
    ├── lib.0
    └── internal/
        ├── decoder.0
        └── encoder.0

internal/ 子目录只是一种约定,清单文件里不会提到这些文件。convert.0 中的导入可以直接访问 internal/decoder.0。

构建与运行

进入包之后,常用的操作有:

zero check        # type-check the whole tree
zero run          # build and run the default target
zero run convert  # build and run a specific named target
zero build        # build the default target
zero build --all  # build every target (when supported)
zero test         # run every test target

CLI 会读取 zero.json,弄清楚要做什么,然后执行。在包里工作时,你很少需要写出完整路径。

多个源文件:一个简单示例

假设 src/main.0 调用了 src/math.0 中的一个辅助函数。辅助函数所在的文件:

pub fun double(value: i32) -> i32 {
    return value * 2
}

入口文件:

pub fun main(world: World) -> Void raises {
    let result = double(21)
    if result == 42 {
        check world.out.write("forty two\n")
    }
}

用 zero run 运行它。在这个简单的例子里,即使没有显式的导入声明,编译器也会在源代码树的其他文件中解析对 double 的引用。包变大以后,跨模块的可见性由显式的导入系统处理。导入语法请参考当前的 Zero 文档,这是 1.0 之前最可能发生变化的部分之一。

哪些东西不该提交到 git

Zero 包的 .gitignore 通常需要包含:

# build artifacts and caches
/build/
/target/

# editor cruft
.DS_Store
*.swp

构建输出目录的具体名称可能不同,请查看当前的工具链文档。但规则不变:源代码提交进去,构建产物留在外面。

共享包

Zero 还没到 1.0,包注册中心还不属于稳定的功能。目前共享包的实用方式有:

  • Git:克隆仓库,然后对它运行 zero check。
  • 内置副本(vendoring):把源代码复制一份放进另一个项目。

等注册中心推出后,包的引用很可能会移到 zero.json 中的依赖字段里。把这当作未来的功能,现在不要写脚本依赖它。

下一篇:语言基础

现在你已经掌握了组织一个真实 Zero 项目所需的一切。下一章将聚焦语言本身,从 let 绑定 开始,也就是 Zero 中的值如何被命名。

常见问题

Zero 包是什么?

Zero 包是一个目录,里面有一个 zero.json 清单文件和一个存放 .0 源文件的 src/ 文件夹。清单文件声明包的名称、版本,以及一个或多个“目标”(target):每个目标告诉编译器如何从源代码构建出某样东西(可执行文件、库或测试程序)。

如何创建一个新的 Zero 包?

运行 zero new <template> <name>,例如 zero new cli hello。CLI 会生成一个目录,里面有 zero.json、src/main.0,以及所选模板需要的其他文件。之后你就可以在包里运行 zero check、zero run 和 zero build。

zero.json 里有什么?

至少包含一个带 name 和 version 的 package 对象,以及一个描述包所构建内容的 targets 对象。每个目标有一个 kind(例如表示可执行文件的 exe)和一个指向入口源文件的 main。一个清单文件里可以声明多个目标。

一个 Zero 包可以有多个目标吗?

可以。一个包可以声明任意数量的目标,例如一个用于命令行工具的 exe 目标、一个用于可复用库的 lib 目标,以及一个或多个测试目标。每个目标在 src/ 下都有自己的入口,你可以通过 CLI 单独构建或运行它们。

编译器把构建输出放在哪里?

构建产物会放在包内的一个构建目录里(具体路径由实现决定,在 Zero 1.0 之前可能会变)。src/ 下的源代码树永远不会被修改。把构建目录当作可以随时删除的东西,把它提交到 git 不是个好主意。

Coddy 编程语言插图

和 Coddy 一起学编程

开始