将 Crate 发布到 Crates.io

我们使用了 crates.io 的软件包作为 依赖项,但您也可以与其他人共享您的代码 通过发布您自己的包。crates.io 的 crate 注册表分发了 您的软件包,因此它主要托管开源代码。

Rust 和 Cargo 具有使您发布的软件包对用户来说更轻松的功能 查找并使用。接下来,我们将讨论其中的一些功能,然后进行解释 如何发布包。

制作有用的文档注释

准确记录您的包将有助于其他用户知道如何以及何时 使用它们,因此值得花时间编写文档。In Chapter 3、我们讨论了如何使用两个斜杠 .Rust 还具有 一种特定类型的文档注释,方便地称为文档注释,它将生成 HTML 文档。The HTML 显示预期的公共 API 项的文档注释内容 对于有兴趣知道如何使用 crate 而不是如何使用 crate 的程序员 您的 crate 已实现//

文档注释使用三个斜杠 ,而不是两个斜杠和支持 用于格式化文本的 Markdown 表示法。仅放置文档注释 在他们正在记录的项目之前。示例 14-1 显示了文档注释 对于///add_one函数存储在名为my_crate.

文件名: src/lib.rs
/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}
示例 14-1:函数的文档注释

在这里,我们描述了add_one函数执行 部分替换为标题Examples,然后提供演示 如何使用add_one功能。我们可以从 此文档注释cargo doc.此命令运行rustdoc工具与 Rust 一起分发,并将生成的 HTML 文档 在 target/doc 目录中。

为方便起见,运行cargo doc --open将为您的 当前 crate 的文档(以及您所有 crate 的依赖项)并在 Web 浏览器中打开结果。导航到add_one函数,您将看到文档注释中的文本是这样的 rendered,如图 14-1 所示:

为 'my_crate' 的 'add_one' 函数渲染的 HTML 文档

图 14-1..add_one功能

常用部分

我们使用了# Examples示例 14-1 中的 Markdown 标题创建一个 section 在 HTML 中,标题为 “Examples”。以下是其他一些 crate 的部分 作者通常在他们的文档中使用:

  • Panics:被记录的函数可能 恐慌。不希望其程序 panic 的函数调用者应该 确保他们在这些情况下不调用该函数。
  • 错误:如果函数返回Result,描述 可能发生的错误以及可能导致这些错误的条件 returned 对调用方有帮助,以便他们可以编写代码来处理 以不同的方式出现不同类型的错误。
  • 安全性:如果函数是unsafe调用(我们在 第 19 章),应该有一个部分解释为什么该函数不安全 并涵盖函数期望调用者维护的不变量。

大多数文档注释不需要所有这些部分,但这是一个 好的清单可以提醒您代码用户将要注意的方面 有兴趣了解。

作为测试的文档注释

在文档注释中添加示例代码块有助于演示 如何使用你的库,这样做还有一个额外的好处:运行cargo test将文档中的代码示例作为测试运行!没有什么是 比带有示例的文档更好。但没有什么比例子更糟糕的了 这不起作用,因为自文档以来代码已更改 写。如果我们运行cargo test替换为add_one函数,我们将在测试结果中看到如下部分:

   Doc-tests my_crate

running 1 test
test src/lib.rs - add_one (line 5) ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s

现在,如果我们更改函数或示例,则assert_eq!在 示例 panics 并运行cargo test同样,我们将看到 doc tests 捕获 示例和代码彼此不同步!

注释包含的项目

文档注释的样式//!将文档添加到包含 comments 而不是 comments 后面的项目。我们通常使用 这些 doc 注释在 crate 根文件(按照约定是 src/lib.rs)或 来记录 crate 或整个模块。

例如,要添加描述my_cratecrate 中包含add_one函数中,我们添加了文档注释 入手//!添加到 src/lib.rs 文件的开头,如 清单 所示 14-2:

文件名: src/lib.rs
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.

/// Adds one to the number given.
// --snip--
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}
示例 14-2: 文档my_crate板条箱作为一个整体

请注意,最后一行以//!.因为 我们以//!而不是 ,我们记录了项目 ,而不是此注释后面的项。在 在本例中,该项目是 src/lib.rs 文件,它是 crate 根。这些 comments 描述了整个 crate。///

当我们运行cargo doc --open,这些注释将显示在前面 文档的页面my_crate在 crate,如图 14-2 所示:

呈现的 HTML 文档,带有整个 crate 的注释

图 14-2: 渲染的文档my_crate, 包括描述整个 crate 的注释

项中的文档注释可用于描述 crate 和 特别是模块。使用它们来解释容器的总体用途 帮助您的用户了解 crate 的组织。

导出方便的公共 APIpub use

在发布 板条箱。使用你的 crate 的人对结构的熟悉程度不如你 如果您的板条箱 具有大型模块层次结构。

在第 7 章中,我们介绍了如何使用pubkeyword 和 将项放入范围use关键词。但是,结构 对您来说有意义,当您开发 crate 时可能不是很方便 为您的用户。您可能希望在层次结构中组织结构 包含多个级别,但随后想要使用您已有的类型 在层次结构的深处定义可能难以发现该类型是否存在。 他们也可能因为不得不进入而感到恼火use my_crate::some_module::another_module::UsefulType;而不是use my_crate::UsefulType;.

好消息是,如果结构方便其他人使用 从另一个库,您不必重新排列内部组织: 相反,您可以重新导出项目以创建一个不同的公共结构 从您的私有结构中pub use.重新导出需要一个公共的 item 在一个位置,并在另一个位置将其设为公共,就像它是 定义。

例如,假设我们创建了一个名为art用于对艺术概念进行建模。 此库中有两个模块:一个kinds包含两个枚举的模块 叫PrimaryColorSecondaryColor以及utils模块中包含 名为mix,如示例 14-3 所示:

文件名: src/lib.rs
//! # Art
//!
//! A library for modeling artistic concepts.

pub mod kinds {
    /// The primary colors according to the RYB color model.
    pub enum PrimaryColor {
        Red,
        Yellow,
        Blue,
    }

    /// The secondary colors according to the RYB color model.
    pub enum SecondaryColor {
        Orange,
        Green,
        Purple,
    }
}

pub mod utils {
    use crate::kinds::*;

    /// Combines two primary colors in equal amounts to create
    /// a secondary color.
    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
        // --snip--
        unimplemented!();
    }
}
示例 14-3:一个artlibrary 中,将项目组织成kindsutils模块

图 14-3 显示了这个 crate 的文档首页是什么 生成者cargo doc将如下所示:

为 'art' crate 渲染的文档,其中列出了 'kinds' 和 'utils' 模块

图 14-3:文档的扉页art,其中列出了kindsutils模块

请注意,PrimaryColorSecondaryColor类型未列在 front page 的mix功能。我们必须点击kindsutils自 看看他们。

另一个依赖于此库的 crate 将需要use声明 将物品从artinto 范围,指定 currently defined.示例 14-4 显示了一个使用PrimaryColormixart板条箱:

文件名: src/main.rs
use art::kinds::PrimaryColor;
use art::utils::mix;

fn main() {
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}
示例 14-4:使用art导出 crate 及其内部结构的项目

示例 14-4 中代码的作者使用了artcrate,不得不 弄清楚PrimaryColor位于kindsmodule 和mix位于utils模块。的artcrate 与 开发人员正在开发artcrate 而不是使用它的人。内部的 结构不包含任何有用的信息,供尝试 了解如何使用artcrate,但会引起混淆,因为 使用它的开发人员必须弄清楚在哪里查找,并且必须指定 模块名称use语句。

要从公共 API 中删除内部组织,我们可以修改artcrate 代码中添加pub use语句以重新导出 items 的 s,如示例 14-5 所示:

文件名: src/lib.rs
//! # Art
//!
//! A library for modeling artistic concepts.

pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;

pub mod kinds {
    // --snip--
    /// The primary colors according to the RYB color model.
    pub enum PrimaryColor {
        Red,
        Yellow,
        Blue,
    }

    /// The secondary colors according to the RYB color model.
    pub enum SecondaryColor {
        Orange,
        Green,
        Purple,
    }
}

pub mod utils {
    // --snip--
    use crate::kinds::*;

    /// Combines two primary colors in equal amounts to create
    /// a secondary color.
    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
        SecondaryColor::Orange
    }
}
示例 14-5:添加pub use用于重新导出项目的声明

API 文档cargo doc此 crate 的生成现在将列出 并在首页重新导出链接,如图 14-4 所示,使得PrimaryColorSecondaryColortypes 和mix功能更容易找到。

为 'art' crate 渲染的文档,重新导出位于首页上

图 14-4.. 文档的首页。art,其中列出了再导出

artcrate 用户仍然可以查看和使用来自 清单 的内部结构 14-3 中,如示例 14-4 所示,或者他们可以使用更方便的 结构,如示例 14-6 所示:

文件名: src/main.rs
use art::mix;
use art::PrimaryColor;

fn main() {
    // --snip--
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}
示例 14-6:一个程序使用从art板条箱

在有许多嵌套模块的情况下,重新导出顶部的类型 level 替换为pub use可以对 使用板条箱的人。另一个常见用途pub use是再出口 当前 crate 中依赖项的定义,以使该 crate 的 definitions 是 crate 的公共 API 的一部分。

创建有用的公共 API 结构与其说是一门科学,不如说是一门艺术,并且 您可以迭代以查找最适合您的用户的 API。选择pub use让您灵活地在内部构建 crate,并且 将该内部结构与您向用户呈现的内容分离。看 你安装的一些 crate 代码,看看它们的内部结构是否 不同于他们的公共 API。

设置 Crates.io 帐户

在发布任何 crate 之前,您需要在 crates.io 上创建一个账户并获取 API 令牌。为此, 访问 crates.io 和 Log 的主页 in 通过 GitHub 帐户。(GitHub 帐户目前是必需的,但 该网站将来可能支持其他创建帐户的方式。一次 您已登录,在 https://crates.io/me/ 访问您的帐户设置并检索您的 API 密钥。然后运行cargo login命令并在出现提示时粘贴您的 API 密钥,如下所示:

$ cargo login
abcdefghijklmnopqrstuvwxyz012345

此命令将通知 Cargo 您的 API 令牌并将其存储在本地 ~/.cargo/credentials 中。请注意,此令牌是机密:请勿共享 和其他人一起。如果您出于任何原因与任何人分享它,您应该 撤销它并在 crates.io 上生成新 Token。

将元数据添加到新 crate

假设您有一个要发布的 crate。在发布之前,您需要 要在[package]部分。

您的 crate 需要一个唯一的名称。当您在本地处理 crate 时, 你可以随心所欲地给 crate 命名。但是,crates.io 上的 crate 名称是按照先到先得的方式分配的。 先到先得。一旦 crate 名称被占用,其他人就无法发布 crate 替换为该名称。在尝试发布 crate 之前,请搜索 想要使用。如果该名称已被使用,您将需要找到另一个名称,并且 编辑name字段位于 Cargo.toml 文件中的[package]部分设置为 使用新名称进行发布,如下所示:

文件名: Cargo.toml

[package]
name = "guessing_game"

即使您选择了唯一的名称,当您运行cargo publish发布 此时,您将收到警告,然后是错误:

$ cargo publish
    Updating crates.io index
warning: manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
--snip--
error: failed to publish to registry at https://crates.io

Caused by:
  the remote server responded with an error: missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for how to upload metadata

这个错误是因为你错过了一些关键信息:描述 和 需要许可证,这样人们就会知道你的 crate 做什么以及在什么下 他们可以使用的术语。在 Cargo.toml 中,添加一个描述,该描述只是一个 一两个句子,因为它会与你的 crate 一起出现在搜索结果中。为 这license字段中,您需要提供许可证标识符值Linux 的 Foundation 的软件包 Data Exchange (SPDX) 列出了标识符 您可以用于此值。例如,要指定您已为您的 crate 中,添加MIT标识符:

文件名: Cargo.toml

[package]
name = "guessing_game"
license = "MIT"

如果您想使用未出现在 SPDX 中的许可证,则需要将 该文件中的许可证文本,将该文件包含在您的项目中,然后 用license-file指定该文件的名称,而不是使用license钥匙。

关于哪种许可证适合您的项目的说明超出了范围 这本书的。Rust 社区中的许多人在 与 Rust 相同,使用MIT OR Apache-2.0.这种做法 表明您还可以指定多个许可证标识符 由OR为您的项目拥有多个许可证。

添加唯一名称、版本、描述和许可证后,准备发布的项目的 Cargo.toml 文件可能如下所示:

文件名: Cargo.toml

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"

[dependencies]

Cargo 的文档描述了其他 元数据,以确保其他人可以更多地发现和使用您的 crate 容易。

发布到 Crates.io

现在,您已经创建了一个账户,保存了 API 令牌,并为 的 crate 并指定了所需的元数据,即可发布! 发布 crate 会将特定版本上传到 crates.io 以供其他人使用。

请小心,因为发布是永久性的。版本永远不能是 overwritten,并且代码无法删除。crates.io 的一个主要目标是充当永久档案 的代码,以便所有依赖于 crates.io 的 crate 的项目构建将继续工作。允许 版本删除将使该目标无法实现。然而,有 您可以发布的 crate 版本数量没有限制。

运行cargo publish命令。它现在应该成功了:

$ cargo publish
    Updating crates.io index
   Packaging guessing_game v0.1.0 (file:///projects/guessing_game)
   Verifying guessing_game v0.1.0 (file:///projects/guessing_game)
   Compiling guessing_game v0.1.0
(file:///projects/guessing_game/target/package/guessing_game-0.1.0)
    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
   Uploading guessing_game v0.1.0 (file:///projects/guessing_game)

祝贺!您现在已经与 Rust 社区共享了您的代码,并且 任何人都可以轻松地将你的 crate 添加为他们项目的依赖项。

发布现有 crate 的新版本

当您对 crate 进行了更改并准备发布新版本时, 您将version值,以及 Cargo.toml 文件中指定的值 再版。使用语义版本控制规则来决定 适当的 Next Version Number (下一个版本号) 基于您所做的更改类型。 然后运行cargo publish上传新版本。

弃用 Crates.io 的版本cargo yank

虽然您无法删除 crate 的先前版本,但您可以阻止任何 将来的项目,将它们添加为新的依赖项。这在 crate 版本由于某种原因而损坏。在这种情况下,Cargo 支持拉动 crate 版本。

拉出版本可以防止新项目依赖于该版本,而 允许所有依赖它的现有项目继续。本质上,一个 yank 意味着所有带有 Cargo.lock 的项目都不会中断,并且任何未来生成的 Cargo.lock 文件都不会使用 yanked 版本。

要拉扯 crate 的某个版本,请在您拥有 以前发布过,请运行cargo yank并指定要更改的版本 美国 佬。例如,如果我们发布了一个名为guessing_game版本 1.0.1 中,我们想把它拉到项目目录中的guessing_game我们会 跑:

$ cargo yank --vers 1.0.1
    Updating crates.io index
        Yank guessing_game@1.0.1

加入--undo对于该命令,您还可以撤消 Yank 并允许项目 要再次开始依赖版本:

$ cargo yank --vers 1.0.1 --undo
    Updating crates.io index
      Unyank guessing_game@1.0.1

抽搐不会删除任何代码。例如,它不能意外删除 上传的密钥。如果发生这种情况,您必须立即重置这些密钥。

本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准