如何发布子 crate?

解读

在 Rust 生态里,“子 crate”通常指**工作区(workspace)**里的成员 crate,而不是 git submodule 或 path 依赖。面试官问“如何发布”,核心想确认两点:

  1. 你是否理解 Cargo 工作区 的发布边界——workspace 本身不可发布,只有单个 crate 可发布
  2. 你是否掌握国内网络环境下( crates.io 访问受限)的完整发布流程,包括本地验证、版本同步、依赖源切换、CI 集成等细节。
    回答时若只说一句 cargo publish 会被视为“学生级答案”;必须体现出工程化落地经验

知识点

  • 工作区与 package 边界:workspace 根目录下 Cargo.toml[workspace] 仅做聚合,不含 [package] 字段,因此无法 publish;只有成员目录下包含 [package] 的 crate 才能发布。
  • 版本号一致性:workspace 成员之间若用 path = "../xxx" 依赖,发布前需把版本号改成已发布版本同次发布版本,否则 crates.io 拒绝上传。
  • 国内网络:crates.io 索引走 git 协议,443 端口常被限速;需配置 国内镜像源(如 tuna、ustc)或代理;发布阶段仍需走 https 直链,需确保 443 端口畅通。
  • --dry-run 与本地验证cargo publish --dry-run 会在本地完整走一遍打包、校验、编译流程,提前暴露 include/exclude 字段拼写错误、README 缺失、license 文件未跟踪等问题。
  • token 管理cargo login 生成的 API Token 默认保存在 ~/.cargo/credentials,CI 场景需通过环境变量 CARGO_REGISTRY_TOKEN 注入,避免明文落盘。
  • 子 crate 发布顺序:若 A 依赖 B,必须先 cargo publish B,再把 A 的 Cargo.toml 里 B 的依赖版本指向刚发布的版本号,然后发布 A;workspace 里可用 cargo-release 工具一键完成“自底向上”顺序发布并自动打 git tag。
  • yank 与撤销:发布后 72 小时内可 cargo yank --vers x.y.z 下架,但文件不可删除,国内大厂审计要求“禁止 yank”时,需在 CI 加脚本检查版本号是否已存在。
  • 私有 registry:企业内网场景用 cargo-zng-registrykellnr 搭建私有源,发布命令换成 cargo publish --registry mycorp,需在 ~/.cargo/config.toml 里配置 [registries.mycorp] 索引地址与 token。

答案

  1. 确认目录结构
    workspace 根 Cargo.toml 仅含

    [workspace]
    members = ["crates/foo", "crates/bar"]
    

    子 crate crates/foo/Cargo.toml 必须含

    [package]
    name = "foo"
    version = "0.1.0"
    license = "MIT OR Apache-2.0"
    description = "xxx"
    repository = "https://github.com/yourname/repo"
    

    缺少任何必填字段都会导致 cargo publish 直接失败

  2. 国内网络前置配置
    ~/.cargo/config.toml 加入

    [source.crates-io]
    replace-with = 'ustc'
    [source.ustc]
    registry = "https://mirrors.ustc.edu.cn/crates.io-index"
    

    发布阶段镜像源不影响上传,但可显著加快依赖校验速度;若公司出口封 443,需临时走 export https_proxy=http://proxy.xxx:端口

  3. 本地一次性验证

    cd crates/foo
    cargo publish --dry-run
    

    观察输出是否包含

    Packaged 12 files, 46.3KiB
    

    若出现 warning: file README.md is not included,检查 include = ["src/", "README.md", "LICENSE-*"] 是否拼错。

  4. 登录与发布

    cargo login
    # 粘贴 crates.io 个人中心生成的 API Token
    cargo publish --no-verify
    

    --no-verify 可跳过再次编译,节省 30~60 秒;首次发布无需加 --no-verify,因为 crates.io 会强制做全量编译校验。

  5. 顺序发布兄弟 crate
    bar 依赖 foo = { version = "0.1", path = "../foo" },先发布 foo,再把 bar 的依赖改为 foo = "0.1.0",再发布 bar;可用工具链

    cargo install cargo-release
    cargo release patch --execute
    

    它会自动按拓扑序发布并打 tag。

  6. 验证已发布
    新建空白目录执行

    cargo new test-use
    cd test-use
    cargo add foo@0.1
    cargo check
    

    确保拉取的是 crates.io 版本而非本地 path,防止“本地能编、线上失败”

拓展思考

  • CI 全自动发布:在 GitHub Actions 里用 on: push: tags: - '*' 触发,步骤:

    1. 设置 CARGO_REGISTRY_TOKEN 为 GitHub secret;
    2. cargo install cargo-release
    3. cargo release ${{ github.ref_name }} --execute --no-confirm
    4. 发布完成后通过企业微信/飞书机器人通知版本号。
      注意:国内 GitHub Runner 需选香港节点,否则 cargo publish 上传阶段超时。
  • 语义化版本与破坏性变更:workspace 多 crate 共用一张变更日志,需在根目录维护 CHANGELOG.md,用 cargo-smart-release 自动解析 PR label 生成日志,避免人工漏写导致用户升级失败。

  • 私有 crate 混用:若部分子 crate 含源码专利不便公开,可配置

    [package]
    publish = ["mycorp"]
    

    阻止误发到 crates.io;CI 里用 cargo publish --registry mycorp 统一收口,实现“公开 crate 走 crates.io,内部 crate 走私有源” 的混合策略。

  • 回滚方案:一旦 cargo yank 被审计禁止,可在下一个 patch 版本里revert 问题代码并立即发布,再通过内部文档通知业务方升级;同时把出问题的版本号写入 deny.toml 黑名单,用 cargo-deny 在 CI 阶段强制拦截引用,实现“物理隔离”而非物理删除。