版本管理与发布
PurRDF 以同一个工作区版本、同步地发布到三个注册表——crates.io 上的 crate 套件、PyPI 上的 purrdf 与 purrdf-rdflib 分发包,以及 npm 上的 @blackcatinformatics/purrdf 包。完整流程见 docs/RELEASE.md。
自 1.0.0 起的语义化版本(semver)策略
自 1.0.0 起,整套软件完整遵循语义化版本:
- 破坏性变更递增主版本号。带有
!或BREAKING CHANGE:脚注的提交会触发主版本递增,变更日志会把每个此类条目标记为 BREAKING; - 次版本递增仅作增补,且与 API 兼容;
- 补丁版本递增仅修复缺陷。
这就是版本号所承诺的内容;它是一项策略声明,而不是超出 semver 含义之外的稳定性声称。全部三个已发布的接口共享同一个工作区版本并一同发布;CI 中的版本一致性检查会在各版本来源(Cargo.toml、pyproject.toml、package.json、CITATION.cff)不一致时使构建失败。
唯一的例外是 C ABI。libpurrdf 的 purrdf.h 带有自己的 PURRDF_ABI_MAJOR.PURRDF_ABI_MINOR,在该生成头文件中声明,每当导出签名变更时递增,由 crates/rdf-capi/tests/abi_signatures.rs 固定,并在运行时通过 purrdf_abi_version 读回。它的版本独立于工作区,并保持在 0.x:它并未冻结,工作区的 1.0.0 对它不作任何承诺。
MSRV 策略
支持的最低 Rust 版本是根 Cargo.toml 中的 rust-version——当前为 1.98——位于 stable 通道,由专门的 CI MSRV 作业强制执行:该作业显式设置 RUSTUP_TOOLCHAIN,并断言它所度量的编译器确实是 1.98。提高 MSRV 是一项记入变更日志的显著变更;它随次版本提升进行,绝不在修订版发布中出现。
MSRV 是对使用方的承诺;开发工具链是一项工具选择,二者相互正交。rust-toolchain.toml 为本地工作与 CI 关卡指定了一个浮动的 nightly,以使用其当前的 clippy 与 rustdoc 分析。按照策略,源码不使用任何 nightly(不稳定)特性(没有任何 #![feature(...)] 属性,MSRV 作业在每次变更时都会证明这一点),而发布通道在 stable 上构建每一个已发布的工件。
由标签驱动的可信发布
发布由标签驱动:rust-v<version> 把 crate 套件发布到 crates.io,py-v<version> 发布到 PyPI,npm-v<version> 把 wasm 包发布到 npm。各通道共享 cargo 通道的供应链安全姿态:
- 发布通过 GitHub Actions OIDC 使用可信发布(Trusted Publishing)——没有长期有效的注册表密钥;
- 拥有特权的发布作业使用固定版本的 action,且不使用依赖缓存;
- 每个
.crate包都会获得一份 GitHub 构建溯源证明(build-provenance attestation); - 整套包会获得一份 SPDX SBOM 及 SBOM 证明;
- 发布的 crate 集合在发布前会在
wasm32-unknown-unknown上接受检查; - 每个工作区 crate 的版本都必须与标签版本一致。
33 个 crate 的发布集合中,每个 crate 的每个功能版本都由该通道发布。功能发布要求 crate 记录以 Require trusted publishing(trustpub_only)锁定。该设置阻止 API 令牌发布新版本:crates.io 会回应 403 Forbidden: New versions of this crate can only be published using Trusted Publishing。
在打标签之前建立新 crate 的记录。使用令牌发布一个独立、空且无依赖的 0.0.0 包,以创建每个缺失的记录;实际工作区 crate 保持其功能发布版本。配置其可信发布者(Trusted Publisher)条目并启用 Require trusted publishing,然后核对并更新 scripts/release-crates.sh 中的 PURRDF_UNBOOTSTRAPPED_CRATES。功能发布开始前,全部 33 个记录必须存在、全部发布者条目必须配置完毕、每个记录必须锁定,且台账必须为空。scripts/check-crates-io-records.sh --require-all 验证公开记录与锁定状态;发布者条目的配置需要单独确认。
随后,由标签触发的可信发布运行按依赖顺序发布功能版本。在这些版本通过验证后,使用具有独立撤回(yank)权限的令牌撤回空的 0.0.0 版本,同时保留记录与发布者设置。完整的设置流程与历史记录引导凭据见上文链接的发布流程文档。
有十二个工作区成员有意从不发布到 crates.io:purrdf-capi(经由 cargo-c 构建,以 libpurrdf 的形式分发)、purrdf-sparql-conformance(测试框架)、purrdf-conformance-kit(社区语料运行器所使用的清单、报告与结果评分)、purrdf-hash-conformance(purrdf-hash 的冻结向量测试套件)、purrdf-cli(purrdf 二进制文件)、purrdf-envelope-probe(微型硬件包络采集工具)、purrdf-bench(基准测试工具)、purrdf-alloc-probe(分配测试与基准测试进行测量时所共用的计数分配器)、purrdf-testkit(共用测试支持:黄金文件、临时路径、冻结向量与 harness = false 运行器)、wasm-link(wasm 包的链接后处理工具)、helper-census(共享辅助函数门禁背后的结构普查工具),以及 purrdf-python(扩展 crate,改为经由 maturin 发布到 PyPI)。
这十二个成员中,只有 purrdf-alloc-probe 和 purrdf-testkit 被已发布的 crate 所依赖,并且仅通过 [dev-dependencies] 引入。因此,它们在根 [workspace.dependencies] 中的条目只有路径、没有 version 键,这正是 cargo 将它们从打包后的清单中去除的原因:未发布的 crate 不能成为已发布 crate 的带版本依赖,而 cargo publish 的验证步骤也会解析 dev-dependencies。
切出一个发布
在隔离分支上准备版本递增,使用 make 辅助目标保持三个发布通道一致。先合并经过审阅的发布变更,再从干净且已同步的 main 创建标签:
将变更日志的 Unreleased 小节改为发布版本号和日期,并在 docs/releases/<version>.md 写入经过审阅的简短摘要。make changelog 仅用于依据提交历史生成的说明。
# 1. 同步递增发布版本号(除非最终各版本号相等,否则失败)。
make bump VERSION=0.2.2
# 2. 依据递增后的 crate 版本重新生成已提交的 C-ABI 头文件。
make capi-header
# 3. 完善变更日志与经过审阅的摘要,保留迁移指导。
# 4. 仅暂存经过审阅的版本递增、头文件、变更日志与摘要,然后提交。
git commit -m "chore(release): 0.2.2"
# 5. 合并发布 PR 后,在最新的 main 上执行带关卡的标签推送。
make release-tags VERSION=0.2.2
make release-tags 在以下条件未全部满足时拒绝运行:工作树干净、分支为 main 且与 origin/main 同步、版本及 crates.io 记录/锁定检查通过、VERSION 与工作树一致、完整的变更日志小节与通过验证且经过审阅的摘要均存在,且三个标签均未在本地或远程存在。随后它按以下顺序运行各项关卡:
make check:Rust 与 wasm 工作区关卡及仓库卫生检查。make capi-check:生成的 C-ABI/头文件检查,以及链接后的 C 冒烟测试。make pytest:原生 Python 绑定测试套件。make python-release-check:构建并审计两个 Python 分发包各自的 wheel/sdist,使用固定版本的发布工具检查它们,并将它们一同安装。make wasm-pkg-test:经过优化、带体积关卡的 npm/wasm 包测试。make capi-bundle:构建并审计原生 C 分发包,包括接收方许可声明与重定位后的链接冒烟测试。
只有在每个接口都通过之后,它才会重新检查干净且已同步的状态,并以原子方式一同推送 rust-v、py-v 与 npm-v 标签。在完整的跨接口预检通过之前,不会创建任何标签。每个标签触发各自的通道,而 cargo 通道还会额外发布一个 GitHub Release,使用 docs/releases/<version>.md 中已提交且经过审阅的摘要,并链接到完整的变更日志。共享检查器会在打标签与发布之前验证精确的版本及 64 KiB 的 UTF-8 体积上限;发布说明不会被截断。
引用 PurRDF
每个发布都带有 DOI;若在研究中使用 PurRDF,请引用它——参见仓库中的 CITATION.cff。