写点什么

Rust 从 0 到 1-Cargo- 发布到 Crates.io

用户头像
关注
发布于: 2 小时前
Rust从0到1-Cargo-发布到Crates.io

我们曾在项目中使用 crates.io 上的 package 作为依赖,反过来我们也可以通过它发布自己的 package 来将代码分享给其他人。在 crates.io 注册的 crate 会包含源代码的发布,因此它主要用于开源代码。

Rust 和 Cargo 包含了一些功能,可以帮助他人找到和开始使用你发布的 package。下面我们将对其中一些功能做介绍,然后讲如何发布一个 package 。

文档注释

准确的文档对于其他用户使用我们的 package 非常有帮助,因此花一些时间编写文档是值得的。之前我们介绍过如何使用 // 注释代码。Rust 还有一种被称为文档注释(documentation comments)的特定注释类型,它会生成 HTML 文档,用于展示公有 API 的文档注释,主要目的是帮助对如何使用我们的 crate 感兴趣的程序员,并不是对具体实现感兴趣的程序员(关心具体实现的话,阅读源码应该是更好的方式)。

文档注释使用 /// ,并支持 Markdown 语法,位置一般位于需要注释的 API 之前。参考下面的例子:

/// 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}
复制代码

上面的例子中,我们在文档注释中对 add_one 函数的功能做了描述,接着在 Examples 的部分,展示了如何使用它。我们可以运行 cargo doc 来生成 HTML 文档并存放在 target/doc 目录下。

更方便的是,我们可以直接通过运行 cargo doc --open 生成 HTML 文档(同时还包括其所有依赖项的文档)并在浏览器中打开。导航到 add_one 函数,我们可以看到最终生成的文档类似下图所示(来自官网):

常用文档章节

上面的例子中使用了 # Examples (Markdown 语法)在 HTML 中创建了一个 “Examples” 章节。另外一些经常在文档注释中使用的章节还有:

  • Panics:函数可能会发生 panic! 的场景。函数调用者如果不希望程序发生 panic 需要注意避免在相应的场景调用函数。

  • Errors:当函数返回 Result 时,描述可能会出现的错误以及场景,帮助调用者针对性的处理错误。

  • Safety:如果这个函数的是 unsafe 的(后面章节会讨论 unsafe),这部分章节会解释 unsafe 的原因,并包含函数期望调用者所需要确保的不变因素(invariants,我理解为函数所假设的一些条件、约束必须为真)。

大部分文档注释并不需要所有这些章节,不过这可以作为一个检查列表,用于提醒我们调用者可能感兴趣了解的内容。

在文档注释中包含测试

在文档注释中增加示例代码除了可以清楚的说明库的使用方法以外,还有一个额外的好处:cargo test 也会运行文档中的示例代码!没有什么比有例子的文档更好了。但也没有什么比由于代码发生了修改,例子不能正常运行更糟了。在文档注释中增加示例代码可以帮助我们保持文档和代码的同步。执行 cargo test 运行前面的例子,我们可以看到类似下面的结果:

Doc-tests my_crate
running 1 testtest 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
复制代码

我们可以尝试改变函数或例子来看看会发生什么。;)

为注释所在项提供文档

还有另一种文档注释 //!,它不是注释之后的内容,而是为注释所在的项提供文档,通常用于 crate 根文件(通常是 src/lib.rs)或模块,为 crate 或模块整体提供说明文档。参考下面的例子:

//! # My Crate//!//! `my_crate` is a collection of utilities to make performing certain//! calculations more convenient.
/// Adds one to the number given.// --snip--
复制代码

上面的例子是包含 add_one 函数的 crate 的文档注释(my_crate)。注意 //! 最后一行之后没有任何代码。因为使用 //! 的注释属于包含此注释的项而不是注释之后的代码。在上面的例子中,包含 //! 注释的项是 src/lib.rs ,即 crate 的根文件,这些注释是对整个 crate 的描述说明。运行 cargo doc --open,我们可以看到这些注释显示在 my_crate 文档的首页,类似下图所示(来自官网):

这类注释对于描述 crate 和模块特别有用,可以通过总体介绍来帮助使用者理解 crate 的组织结构。

使用 pub use 导出公有 API

前面的章节我们介绍过如何使用 mod 来组织代码,如何使用 pub 将访问权限变为公有的,和如何使用 use 将路径引入作用域。然而,对于我们开发 crate 来说良好的组织结构可能对于外部调用者并不方便。我们可能会使用多层级的结构对代码进行组织,这样定义于较深层级中的类型或函数等将不容易被找到;同时,使用 use my_crate::some_module::another_module::UsefulType; 相对 use my_crate::UsefulType; 来说也更加繁琐。

API 的结构是我们发布 crate 时需要考虑的主要内容之一。对于使用者来说,他们并不会那么熟悉 crate 的结构,如果模块层级过深,可能会难以找到所需的内容。

好消息是,我们无需为此重新进行代码的内部组织,我们可以选择使用 pub use 进行重导出(re-export,前面介绍代码组织的时候有提到过),从而获得另外一种公开的组织结构。重导出将位于某个代码层级的公有项在另一个层级再次公开,就像它本身就定义在那里一样。下面我们通过一个例子来体验一下,首先是来看看最初的定义:

//! # 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-- // ANCHOR_END: here SecondaryColor::Orange // ANCHOR: here }}
复制代码

cargo doc 所生成的文档首页类似下图(来自官网):

我们可以看到 PrimaryColor 和 SecondaryColor 类型、以及 mix 函数都未在首页中列出。我们需要点击 kinds 或 utils 在下一层才能看到。如果我们需要使用 use 导入 art 中的公开类型或函数,需要包含其定义的模块结构,参看下面的例子:

use art::kinds::PrimaryColor;use art::utils::mix;
fn main() { let red = PrimaryColor::Red; let yellow = PrimaryColor::Yellow; mix(red, yellow);}
复制代码

如果要使用 art crate 我们就需要知道 PrimaryColor 在 kinds 模块中, mix 在 utils 模块中。art crate 的模块结构是从开发者的角度来组织的而不是使用者的角度。其用于内部组织的 kinds 模块和 utils 模块对想知道如何使用它的人没有提供任何有价值的信息。反而,需要弄清去哪查看所需的功能以及在使用时指定模块名,从而造成使用上的不便。为了消除这些影响,我们在结构的顶层使用 pub use 语句进行重导出,参考下面的例子:

//! # 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--}
pub mod utils { // --snip--}
复制代码

再次运行 cargo doc 生成 API 文档,我们会在首页看到重导出的项以及其链接,更易于查找,类似下图所示(来自官网):

现在我们可以在代码中更方便的使用 art crate 了(当然,我们仍然可以选择向之前那样使用):

use art::mix;use art::PrimaryColor;fn main() {    // --snip--}
复制代码

对于有多层嵌套模块的情况,使用 pub use 将进行重导出,对于使用 crate 的人来说将带来完全不同的体验。

创建易用的 API 结构更像是一门艺术而非科学,我们可以通过不断的迭代找出对使用者来说最友好的 API。pub use 让 crate 内部结构和呈现给使用者的 API 解耦。

创建 Crates.io 账号

在我们发布 crate 之前,需要在 crates.io 上注册账号并获取 API token。为此,我们需要访问 crates.io 的首页并使用 GitHub 账号登陆(目前只能使用 GitHub 账号登录,将来可能会支持其他创建账号的方式)。登陆之后,我们可以在账户设置页面( https://crates.io/me/  )获取 API token,我们可以使用 API token 运行 cargo login 命令:

$ cargo login abcdefghijklmnopqrstuvwxyz012345
复制代码

这个命令会告诉 Cargo 你的 API token 并将其储存在本地的 ~/.cargo/credentials 文件中。需要注意,API token 是私密的(secret),不要分享给任何人。不管什么原因别人如果得到了你的 API token,应该立即到 crates.io 吊销并重新生成它。

为新 crate 添加元数据

现在我们有了账号,假设我们现在希望发布一个 crate。在发布之前,我们需要 Cargo.toml 文件的 [package] 部分增加 crate 的元数据(metadata)。

首先我们需要一个唯一的 crate 名称。虽然在本地开发时,我们可以使用任何喜欢的名称,但是 crates.io 上的 crate 名称遵循先到先得的原则。一旦某个 crate 名称已经被使用,其他人就不能再发布这个名称的 crate。我们可以在 crates.io 上搜索我们希望使用的名称来确认它是否已被使用。如果没有,我们就可以修改 Cargo.toml 为我们的 crate 命名:

[package]name = "guessing_game"
复制代码

即使 crate 的名字是唯一的,但此时运行 cargo publish 进行发布的话,我们会得到类似下面的警告和错误:

$ cargo publish    Updating crates.io indexwarning: 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: api errors (status 200 OK): missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for how to upload metadata
复制代码

这是因为我们还缺少一些关键信息:关于 crate 的描述说明和许可协议(license)。为此,我们需要在 Cargo.toml 中加入这些信息。描述通常是一两句话,它会和 crate 一起出现在搜索结果中。对于许可协议,我们可以在 Linux Foundation’s Software Package Data Exchange (SPDX) 页面中进行选择。譬如,我们选择 MIT:

[package]name = "guessing_game"license = "MIT"
复制代码

如果我们希望使用不存在于 SPDX 中的许可协议,则需要将许可协议文本放入一个文件,并包含进项目中,然后使用 license-file 来指定文件名。

关于应该使用哪种许可协议已经超出了我们讨论的范畴。很多 Rust 社区成员选择使用与 Rust 相同的许可协议,即双重许可(允许用户选择使用哪一个) MIT OR Apache-2.0(注意,我们使用关键字 OR 分隔多个许可协议)。

一个包含名称、版本号、在 cargo new 时输入的作者信息、描述和许可协议的 Cargo.toml 文件看起来类似下面这样:

[package]name = "guessing_game"version = "0.1.0"authors = ["Your Name <you@example.com>"]edition = "2018"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 token,并指定了所需的元数据,已经准备好发布我们的 crate 了!发布 crate 要小心,因为发布是永久性的(permanent)。相同的版本无法覆盖,代码也无法删除。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 代码,并准备好再次发布时,可以通过修改 Cargo.toml 中 version 的值指定新的版本号。官方建议根据 Semantic Versioning rules 来指定版本(MAJOR.MINOR.PATCH,具体规则不在此说明了,大家可以自行搜索) 。最后运行 cargo publish 来就可以发布新的版本了。

“删除”版本

虽然我们不能删除已经发布的版本,但是可以阻止之后的项目将它们作为依赖。这在某个版本因为这样或那样的原因不建议使用的场景很有用。对于这种情况,Cargo 通过 yank 来解决。

yank 某个版本会阻止之后的项目依赖此版本,不过所有已经依赖的项目仍然能够正常使用。本质上说,yank 意味着所有已经包含 Cargo.lock 的项目不会被破坏,而任何新生成 Cargo.lock 的项目将不能使用被 yank 的版本。

我们可以通过运行 cargo yank 来“删除”指定的版本:

$ cargo yank --vers 1.0.1
复制代码

我们还可以通过在命令上增加 --undo 参数撤销 yank 操作,允许项目可以依赖某个曾经被 yank 的版本:

$ cargo yank --vers 1.0.1 --undo
复制代码

yank 并没有删除任何代码。因此,假如你不小心上传了你的密码,请立即进行修改或重置。

发布于: 2 小时前阅读数: 2
用户头像

关注

公众号"山 顽石"欢迎大家关注;) 2021.03.23 加入

IT老兵,关注Rust、Java、前端、架构、大数据、AI、算法等;平时喜欢美食、旅游、宠物、健身、篮球、乒乓球等。希望能和大家交流分享。

评论

发布
暂无评论
Rust从0到1-Cargo-发布到Crates.io