18. 包与项目

  • 第十八章,共二十章
  • 阅读约 3 分钟

第 13 章的项目只要一个目录就够了。一旦它依赖别处的代码,就会多一个 dawn.toml:一份 可选的清单,装目录约定说不出的东西,也就是项目的身份和它的依赖。目录结构、入口、模块路径 仍归目录管;没有这个文件的项目,行为和以前完全一样。

schema = 1        # 永远是第一个键
name = "myapp"    # 项目的身份,[a-z_][a-z0-9_]*

依赖是源码包

包本身也是一个项目,有自己的 dawn.toml 和自己的 src/。下面是一个小包 greet, 就放在 myapp 旁边:

greet/
├── dawn.toml          # schema = 1, name = "greet", version = "1.0.0"
└── src/
    ├── hello.dawn
    └── style.dawn
myapp/
├── dawn.toml
└── src/
    └── main.dawn

greet/src/style.dawn:

# greet 包的每个模块都看得见,包外一概看不见
pub(pkg) fn shout(s: String) -> String = s ++ "!"

greet/src/hello.dawn:

use style.{shout}

pub fn hello(name: String) -> String = shout("hello, ${name}")

包内的模块按它在包自己的 src/ 下的路径互相引入,和第 13 章一样。dawn add 把依赖写进 myapp 的清单:

$ dawn add ../greet --dir myapp
Added greet as `greet` (path myapp/../greet)
schema = 1
name = "myapp"

[deps]
greet = "../greet"

[deps] 下的键是 myapp 对这个包的称呼:它就是 use 行的第一段。

myapp/src/main.dawn:

use greet/hello.{hello}

pub fn main() -> Unit !io = println(hello("Dawn"))

dawn run myapp 打印 hello, Dawn!。

pub(pkg):包内共享,包外不可见

在模块私有(什么都不写)和 pub 之间还有第三级。pub(pkg) 声明对它所在包(一份 dawn.toml 描述的那个单元)的每个模块可见,对包外一概不可见。shout 是 greet 的各个 模块共用的辅助函数,myapp 够不着它:

$ dawn run myapp      # main.dawn 里又加了一行:use greet/style.{shout}
error: `shout` is package-private to package `greet`
  --> myapp/src/main.dawn:2:18
  |
2 | use greet/style.{shout}
  |                  ^^^^^
  = hint: only modules of package `greet` may name it

pub 声明的签名里不许出现 pub(pkg) 类型,和不许模块私有类型出现在公开签名里是同一条 规则;dawn doc 也只列 pub 的条目。没有 dawn.toml 的项目是一个包,打包附带的标准库 整体也是一个包。

远程包、版本与 MVS

路径依赖适合放在你旁边的代码。发布出来的包是某个 URL 上的一个归档,用解包后内容的哈希 钉住:

[deps.json]
url = "https://github.com/dawnop/dawn-lang/archive/refs/tags/v0.7.0.zip"
version = "1.0.0"
hash = "d1:<sha256>"          # 解包后文件树的内容哈希
subdir = "packages/json"      # 包在归档里的位置

这个哈希没人手算。dawn add <url> 会抓取归档、算哈希、读包自己的清单拿到名字和版本, 再把条目写进去,文件其余部分的注释和排版原样保留;--subdir 指明包在归档里的位置, --as 换一个键。对同一个包再 add 一次会就地更新它的条目,升版本就是这么升的。

一个程序里的两个包以不同版本依赖同一个第三方包时,程序只得到它的一份:所要求的各个 最低版本里最高的那个。这就是最小版本选择(MVS),Go 用的那套算法;要求永远只是一个最低 版本,没有上界,也没有排除。对 Dawn 来说只有一份不是图省事。每个「trait × 类型」在整个 程序里恰有一个 impl(第 16 章),一个包有两份,它的每个类型就有两份,各带两个 impl。包的 身份是它自己清单里的 name,不是你给它起的键,所以一个改了名的大版本(json2)只要保留 旧键,照样可以写作 use json/...。

第三张表 [java-deps] 列的是给第 11 章 use java 用的 Maven 坐标 (sqlite = "org.xerial:sqlite-jdbc:3.36.0.3",只收精确版本)。JVM 工具链负责解析和 下载;dawnc 拒绝 use java,也就用不上它们。设计与理由见 package-design.md 和 package-visibility-design.md。

另见:spec.md §10.1、§10.4