# 包
Deft 包是带有 `package.dft` 清单的模块目录。包是分发单位:通过 `pkg/install` 安装,以 `owner/name` 引用,可从任意模块导入。
## 包标识
每个包都有一个规范 `owner/name` 标识(例如 `deft/editor`)。所有者名称在注册表上全局唯一;`deft` 保留给官方包。该标识以单个字符串传递给每个注册表操作:
[pkg/install "deft/editor"] [pkg/info "deft/editor"] [pkg/uninstall "deft/editor"]
## 导入包模块
`owner/name` 标识同时也是导入路径前缀。位于 `:pkgs` 搜索根下的包(或由 `pkg/install` 安装的包)会自动挂载为 `owner/name`,因此模块无需任何配置即可导入:
import "deft/ui/components/button" as button
在名称后用 `@version` 固定精确版本:
import "deft/ui@0.1.0/components/button" as button
- 未固定的 `owner/name/...` 按所列顺序通过 `:pkgs` 根解析——第一个提供该包的根优先。对于已安装的包,版本来自 `pkg/install` 写入的 `latest` 指针,否则取已安装的最高 semver 版本。
- `owner/name@latest/...` 是未固定形式的显式别名。
- 与开发检出清单版本匹配的固定版本会解析到开发树(同样遵循根顺序规则);否则匹配已安装的版本目录。
- 固定版本缺失时会报错并列出可用版本;裸包前缀(例如 `ui/...`)会报错并给出 `did you mean "owner/name/..."?` 提示。
开发检出、已安装树以及旧式带版本的布局都受支持——“开发包”与“使用包”的区别只在于由哪个根提供它,而不在于导入语法。
## 清单(`package.dft`)
清单声明包的标识、版本、依赖与入口点:
def manifest %Package{ :owner "alice" :name "math-lib" :version "1.0.0" :description "Personal math helpers" :entry "src/main.dft" :deps @{ %Dep{ :owner "deft" :name "stdext" :version "0.2.0" } | } :capabilities %PackageCapabilities{ :ffi false :tui false | } }
`[pkg/read-manifest "path/to/dir"]` 读取并校验清单,返回 `%Package{ ... }` 或 `%Err{ ... }`。
## `pkg/*` 标准库函数
| 函数 | 返回 | 用途 |
|---|---|---|
| `[pkg/set-registry $url]` | nil | 设置注册表 URL(nil 重置为本地) |
| `[pkg/get-registry]` | url 或 nil | 当前注册表 |
| `[pkg/install "owner/name" ?version]` | 摘要映射或 `%Err` | 安装 |
| `[pkg/install-bytes "owner/name" $ver $bytes]` | 摘要映射 | 安装预取的 tarball |
| `[pkg/uninstall "owner/name"]` | bool 或 `%Err` | 卸载 |
| `[pkg/list]` | 列表 | 所有搜索根中的包 |
| `[pkg/list-paths]` | 字符串列表 | 搜索根 |
| `[pkg/available]` | 列表 | 当前注册表中的包 |
| `[pkg/info "owner/name" ?version]` | 映射 | 包详情 |
| `[pkg/fetch "owner/name" ?version]` | bytes 或 Future | 下载 tarball |
| `[pkg/read-manifest $dir]` | `%Package` 或 `%Err` | 读取清单 |
| `[pkg/mount $export-path $dir]` | nil | 注册导入挂载 |
## 安装位置
安装始终以第一个搜索根为目标(规范位置:`.deft/packages/`),生成按所有者划分的带版本布局:
```
.deft/packages/<owner>/<name>/<version>/package.dft
.deft/packages/<owner>/<name>/<version>/src/...
````
`pkg/install` 还会在版本目录旁写入 `latest` 文本指针(`.deft/packages/<owner>/<name>/latest`),使未固定的 `owner/name` 导入有明确的目标。
## 搜索路径
启动时会扫描每个 `:pkgs` 根,找到的每个包都会自动挂载为 `owner/name`(以及 `owner/name@version` 固定版本和 `@latest` 别名)。根按所列顺序扫描——第一个提供某包的根优先,因此列在 `.deft/packages/` 之前的开发树会遮蔽已安装副本。预置文件中显式的 `:mounts` 条目始终优先于自动扫描。
使用 `[pkg/list-paths]` 查看当前的搜索根:
[pkg/list-paths]
# => @{ ".deft/packages" }
所有磁盘布局都受支持:
- 扁平(开发):`<root>/<name>/package.dft`
- 旧式带版本:`<root>/<name>/<version>/package.dft`
- 按所有者划分的开发:`<root>/<owner>/<name>/package.dft`
- 按所有者划分的已安装:`<root>/<owner>/<name>/<version>/package.dft`
## 注册表
两种注册表传输方式:
- `dir://`(默认)——tarball 位于 `virt-pkg/packages/<owner>/<name>/<version>.tar.gz` 下。会识别 `latest` 符号链接或文本文件。适用于本地开发与离线环境。
- `http(s)://`——tarball 从 `<url>/packages/<owner>/<name>/<version>.tar.gz` 获取。用 `[pkg/set-registry $url]` 或 CLI 标志 `--url` 设置 URL。会识别 `latest` 文件。
pkg/set-registry "https://packages.example.com" echo [pkg/get-registry] # => https://packages.example.com pkg/set-registry nil # reset to local virt-pkg
当前注册表 URL 会持久化到 `prelude.dft` 的 `:registry` 字段中,因此后续的 `deft` 调用会自动采用。
## 安装包
def result [pkg/install "deft/editor"] # => %{ :owner "deft" :name "editor" :version "1.5.0" :path ".deft/packages/deft/editor/1.5.0" } def specific [pkg/install "deft/editor" "1.4.0"]
`pkg/install` 是同步的——它会阻塞直到获取与解压完成。失败时返回 `%Err{ :message ... }`。
### 手动获取 + 安装
如果你已自行获取了 tarball 字节(例如通过代理),请使用 `pkg/install-bytes`:
set bytes [my-fetch "https://.../editor-1.5.0.tar.gz"] pkg/install-bytes "deft/editor" "1.5.0" $bytes
## 手动挂载包
包自动发现覆盖 `:pkgs` 根下的所有内容。`[pkg/mount $name $dir]` 是用于根之外目录(或将名称指向特定检出)的应急手段:
pkg/mount "alice/mylib" "/home/alice/dev/mylib" import "alice/mylib/helpers" as helpers
挂载名称可以是多段的(`alice/mylib`)——导入会优先解析最长匹配的挂载前缀。
## 创建包
1. 布置目录结构:
```
my-package/
├── package.dft
└── src/
├── main.dft
└── helpers.dft
````
2. 编写清单(`package.dft`):
def manifest %Package{ :owner "alice" :name "my-package" :version "0.1.0" :description "An example package." :entry "src/main.dft" :deps @{} | }
3. (可选)通过将父目录加入搜索路径并导入来在本地测试:
pkg/mount "alice/my-package" "/path/to/my-package" import "alice/my-package/src/main" as mp
4. 发布到你的注册表(将该目录打包为 tarball;上传到你的 HTTP 注册表或放到 `virt-pkg/packages/<owner>/<name>/` 下)。
## 能力
包可以在清单中声明它需要的能力(FFI、TUI 等):
:capabilities %PackageCapabilities{ :ffi true :tui false | }
当包被加载到缺少所需能力的运行时时,运行时会利用这些声明给出兼容性警告。
## 应用帮助(`help/`)
应用包可以通过添加 `help/` 目录来附带自己的帮助:
```
my-app/
├── package.dft
├── help/
│ ├── help.md # main page
│ └── shortcuts.md # extra docs
└── src/
````
`help/help.md` 是主页面(目录中任何其他 `.md` 文件都会成为附加文档)。帮助应用的 Apps 标签页会扫描每个已配置的包根中的 `help/` 目录并列出找到的应用;面板名称命令面板的 Help 操作会在那里打开某个应用的帮助。网站会在 `/platform/apps/<name>` 下索引相同的目录,全文 Search/Apropos 会将它们作为 `app` 语料库纳入。
## 快速参考
| 形式 | 用途 |
|---|---|
| `[pkg/install "owner/name" ?ver]` | 安装 |
| `[pkg/uninstall "owner/name"]` | 卸载 |
| `[pkg/list]` | 列出已安装 |
| `[pkg/available]` | 列出注册表中的包 |
| `[pkg/info "owner/name" ?ver]` | 显示详情 |
| `[pkg/set-registry $url]` | 切换注册表 |
| `[pkg/fetch "owner/name" ?ver]` | 下载 tarball |
| `[pkg/mount $export-path $dir]` | 注册挂载 |
| `[pkg/read-manifest $dir]` | 读取 `package.dft` |