设计一个包管理工具
背景
包管理工具影响了语言的使用体验,是语言生态的基础设施之一。
因此设计新语言时,包管理工具也是需要仔细设计的一个重要部分。
调研
在设计之前,必须先调研其他语言是怎么设计的。
| 语言 | 官方 / 主流包管理 | 官方仓库 |
|---|---|---|
| Python | pip | PyPI |
| JavaScript / TypeScript | npm、pnpm、Yarn | npm Registry |
| Java | Maven、Gradle | Maven Central |
| C# | NuGet | NuGet Gallery |
| Go | go modules | proxy.golang.org |
| Rust | Cargo | crates.io |
| Swift | Swift Package Manager | Swift Package Index / Git |
| Kotlin | Gradle / Maven | Maven Central |
| PHP | Composer | Packagist |
| Dart | pub | pub.dev |
包配置文件
每个语言都有一个包配置文件,配置文件里面一般都有:
nameversiondependencieslicenseauthorsrepository
配置文件的文件名和格式各不相同:
| 语言 | 配置文件 | 格式 |
|---|---|---|
| npm | package.json | JSON |
| Cargo | Cargo.toml | TOML |
| C# | csproj | YAML |
| Python | pyproject.toml / requirements.txt | TOML |
| Go | go.mod | 自定义 |
| Java | pom.xml / build.gradle | XML / DSL |
| Swift | Package.swift | Swift 代码 |
| Dart | pubspec.yaml | YAML |
版本合并
每个语言都支持语义版本(SemVer),同个包的不同版本会被合并,依赖可以指定版本规则:
>=1.2^1.2~1.2
只是符号略有区别。
Go 设计地更简单:只支持 >=1.2。
版本锁
大多有锁文件,以避免版本被自动更新:
package-lock.jsonCargo.lockpoetry.lockpnpm-lock.yamlcomposer.lock
只有 Go 有区别,因为它不支持自动更新,相当于默认就是带锁的效果。
中央仓库
所有语言都有一个中央仓库,也允许私有仓库。
PyPInpmcrates.ioNuGetMaven Centralpub.devPackagist
工作空间
工作空间可以将多个包打包为整体。
很多语言开始不支持,但新版本支持。
| 语言 / 工具 | Monorepo / Workspace 配置方式 | 配置示例 |
|---|---|---|
| Rust | Cargo.toml 中的 workspace.members |
Cargo.tomlmembers = ["a", "b", "c"] |
| Go | go.work |
go.work |
| JavaScript / npm | package.json 中的 workspaces |
workspaces |
| pnpm | pnpm-workspace.yaml |
pnpm-workspace.yaml |
| Yarn | package.json 中的 workspaces |
workspaces |
| Gradle | settings.gradle(.kts) 中使用 include(...) |
include("a", "b", "c") |
几乎每个语言都设计为:当有工作空间时,包的配置依然独立,不会自动从工作空间继承。
没有语言设计为可以通过唯一的配置文件管理所有子包。
是否支持源码和二进制混编
| 语言 | 支持 |
|---|---|
| Rust | 可以 |
| Go | 基本源码 |
| Java | 可以 |
| C# | 可以 |
| Python | 可以(wheel) |
| npm | 可以(native addon) |
| Dart | 基本源码 |
设计决策
很多古老语言一开始都没有统一的包管理工具,直到后来才增加。
说明包管理是前人在项目实践中总结的重要经验。
其他现代新语言在设计时,也都将包管理工具作为一等公民进行考虑的。
总体上,包管理工具并不是只需一两个配置就能实现的,虽然很多人在实际项目中只用到少数配置。
Go 是所有设计方案中,我认为相对最简单的一种,而且实践证明够用。
因此,新设计时,我应该以 Go 的方案为基础进行设计。