Rust 將 crate 發(fā)布到 Crates.io

2023-03-22 15:12 更新
ch14-02-publishing-to-crates-io.md
commit 7ddc28cfe0bfa6c531a6475c7fa41dfa66e8943c

我們曾經(jīng)在項(xiàng)目中使用 crates.io 上的包作為依賴,不過你也可以通過發(fā)布自己的包來向他人分享代碼。crates.io 用來分發(fā)包的源代碼,所以它主要托管開源代碼。

Rust 和 Cargo 有一些幫助他人更方便找到和使用你發(fā)布的包的功能。我們將介紹一些這樣的功能,接著講到如何發(fā)布一個包。

編寫有用的文檔注釋

準(zhǔn)確的包文檔有助于其他用戶理解如何以及何時(shí)使用他們,所以花一些時(shí)間編寫文檔是值得的。第三章中我們討論了如何使用兩斜杠 // 注釋 Rust 代碼。Rust 也有特定的用于文檔的注釋類型,通常被稱為 文檔注釋documentation comments),他們會生成 HTML 文檔。這些 HTML 展示公有 API 文檔注釋的內(nèi)容,他們意在讓對庫感興趣的程序員理解如何 使用 這個 crate,而不是它是如何被 實(shí)現(xiàn) 的。

文檔注釋使用三斜杠 /// 而不是兩斜桿以支持 Markdown 注解來格式化文本。文檔注釋就位于需要文檔的項(xiàng)的之前。示例 14-1 展示了一個 my_crate crate 中 add_one 函數(shù)的文檔注釋,

文件名: 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:一個函數(shù)的文檔注釋

這里,我們提供了一個 add_one 函數(shù)工作的描述,接著開始了一個標(biāo)題為 Examples 的部分,和展示如何使用 add_one 函數(shù)的代碼??梢赃\(yùn)行 cargo doc 來生成這個文檔注釋的 HTML 文檔。這個命令運(yùn)行由 Rust 分發(fā)的工具 rustdoc 并將生成的 HTML 文檔放入 target/doc 目錄。

為了方便起見,運(yùn)行 cargo doc --open 會構(gòu)建當(dāng)前 crate 文檔(同時(shí)還有所有 crate 依賴的文檔)的 HTML 并在瀏覽器中打開。導(dǎo)航到 add_one 函數(shù)將會發(fā)現(xiàn)文檔注釋的文本是如何渲染的,如圖 14-1 所示:


圖 14-1:add_one 函數(shù)的文檔注釋 HTML

常用(文檔注釋)部分

示例 14-1 中使用了 # Examples Markdown 標(biāo)題在 HTML 中創(chuàng)建了一個以 “Examples” 為標(biāo)題的部分。其他一些 crate 作者經(jīng)常在文檔注釋中使用的部分有:

  • Panics:這個函數(shù)可能會 ?panic!? 的場景。并不希望程序崩潰的函數(shù)調(diào)用者應(yīng)該確保他們不會在這些情況下調(diào)用此函數(shù)。
  • Errors:如果這個函數(shù)返回 ?Result?,此部分描述可能會出現(xiàn)何種錯誤以及什么情況會造成這些錯誤,這有助于調(diào)用者編寫代碼來采用不同的方式處理不同的錯誤。
  • Safety:如果這個函數(shù)使用 ?unsafe? 代碼(這會在第十九章討論),這一部分應(yīng)該會涉及到期望函數(shù)調(diào)用者支持的確保 ?unsafe? 塊中代碼正常工作的不變條件(invariants)。

大部分文檔注釋不需要所有這些部分,不過這是一個提醒你檢查調(diào)用你代碼的人有興趣了解的內(nèi)容的列表。

文檔注釋作為測試

在文檔注釋中增加示例代碼塊是一個清楚的表明如何使用庫的方法,這么做還有一個額外的好處:cargo test 也會像測試那樣運(yùn)行文檔中的示例代碼!沒有什么比有例子的文檔更好的了,但最糟糕的莫過于寫完文檔后改動了代碼,而導(dǎo)致例子不能正常工作。嘗試 cargo test 運(yùn)行像示例 14-1 中 add_one 函數(shù)的文檔;應(yīng)該在測試結(jié)果中看到像這樣的部分:

   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

現(xiàn)在嘗試改變函數(shù)或例子來使例子中的 assert_eq! 產(chǎn)生 panic。再次運(yùn)行 cargo test,你將會看到文檔測試捕獲到了例子與代碼不再同步!

注釋包含項(xiàng)的結(jié)構(gòu)

還有另一種風(fēng)格的文檔注釋,//!,這為包含注釋的項(xiàng),而不是位于注釋之后的項(xiàng)增加文檔。這通常用于 crate 根文件(通常是 src/lib.rs)或模塊的根文件為 crate 或模塊整體提供文檔。

作為一個例子,如果我們希望增加描述包含 add_one 函數(shù)的 my_crate crate 目的的文檔,可以在 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--

示例 14-2:my_crate crate 整體的文檔

注意 //! 的最后一行之后沒有任何代碼。因?yàn)樗麄円?nbsp;//! 開頭而不是 ///,這是屬于包含此注釋的項(xiàng)而不是注釋之后項(xiàng)的文檔。在這個情況中,包含這個注釋的項(xiàng)是 src/lib.rs 文件,也就是 crate 根文件。這些注釋描述了整個 crate。

如果運(yùn)行 cargo doc --open,將會發(fā)現(xiàn)這些注釋顯示在 my_crate 文檔的首頁,位于 crate 中公有項(xiàng)列表之上,如圖 14-2 所示:


圖 14-2:包含 my_crate 整體描述的注釋所渲染的文檔

位于項(xiàng)之中的文檔注釋對于描述 crate 和模塊特別有用。使用他們描述其容器整體的目的來幫助 crate 用戶理解你的代碼組織。

使用 pub use 導(dǎo)出合適的公有 API

第七章介紹了如何使用 mod 關(guān)鍵字來將代碼組織進(jìn)模塊中,如何使用 pub 關(guān)鍵字將項(xiàng)變?yōu)楣?,和如何使?nbsp;use 關(guān)鍵字將項(xiàng)引入作用域。然而你開發(fā)時(shí)候使用的文件架構(gòu)可能并不方便用戶。你的結(jié)構(gòu)可能是一個包含多個層級的分層結(jié)構(gòu),不過這對于用戶來說并不方便。這是因?yàn)橄胍褂帽欢x在很深層級中的類型的人可能很難發(fā)現(xiàn)這些類型的存在。他們也可能會厭煩要使用 use my_crate::some_module::another_module::UsefulType; 而不是 use my_crate::UsefulType; 來使用類型。

公有 API 的結(jié)構(gòu)是你發(fā)布 crate 時(shí)主要需要考慮的。crate 用戶沒有你那么熟悉其結(jié)構(gòu),并且如果模塊層級過大他們可能會難以找到所需的部分。

好消息是,即使文件結(jié)構(gòu)對于用戶來說 不是 很方便,你也無需重新安排內(nèi)部組織:你可以選擇使用 pub use 重導(dǎo)出(re-export)項(xiàng)來使公有結(jié)構(gòu)不同于私有結(jié)構(gòu)。重導(dǎo)出獲取位于一個位置的公有項(xiàng)并將其公開到另一個位置,好像它就定義在這個新位置一樣。

例如,假設(shè)我們創(chuàng)建了一個描述美術(shù)信息的庫 art。這個庫中包含了一個有兩個枚舉 PrimaryColor 和 SecondaryColor 的模塊 kinds,以及一個包含函數(shù) mix 的模塊 utils,如示例 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--
    }
}

示例 14-3:一個庫 art 其組織包含 kinds 和 utils 模塊

cargo doc 所生成的 crate 文檔首頁如圖 14-3 所示:


圖 14-3:包含 kinds 和 utils 模塊的庫 art 的文檔首頁

注意 PrimaryColor 和 SecondaryColor 類型、以及 mix 函數(shù)都沒有在首頁中列出。我們必須點(diǎn)擊 kinds 或 utils 才能看到他們。

另一個依賴這個庫的 crate 需要 use 語句來導(dǎo)入 art 中的項(xiàng),這包含指定其當(dāng)前定義的模塊結(jié)構(gòu)。示例 14-4 展示了一個使用 art crate 中 PrimaryColor 和 mix 項(xiàng)的 crate 的例子:

文件名: 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:一個通過導(dǎo)出內(nèi)部結(jié)構(gòu)使用 art crate 中項(xiàng)的 crate

示例 14-4 中使用 art crate 代碼的作者不得不搞清楚 PrimaryColor 位于 kinds 模塊而 mix 位于 utils 模塊。art crate 的模塊結(jié)構(gòu)相比使用它的開發(fā)者來說對編寫它的開發(fā)者更有意義。其內(nèi)部的 kinds 模塊和 utils 模塊的組織結(jié)構(gòu)并沒有對嘗試?yán)斫馊绾问褂盟娜颂峁┤魏斡袃r(jià)值的信息。art crate 的模塊結(jié)構(gòu)因不得不搞清楚所需的內(nèi)容在何處和必須在 use 語句中指定模塊名稱而顯得混亂和不便。

為了從公有 API 中去掉 crate 的內(nèi)部組織,我們可以采用示例 14-3 中的 art crate 并增加 pub use 語句來重導(dǎo)出項(xiàng)到頂層結(jié)構(gòu),如示例 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--
}

pub mod utils {
    // --snip--
}

示例 14-5:增加 pub use 語句重導(dǎo)出項(xiàng)

現(xiàn)在此 crate 由 cargo doc 生成的 API 文檔會在首頁列出重導(dǎo)出的項(xiàng)以及其鏈接,如圖 14-4 所示,這使得 PrimaryColor 和 SecondaryColor 類型和 mix 函數(shù)更易于查找。


圖 14-10:art 文檔的首頁,這里列出了重導(dǎo)出的項(xiàng)

art crate 的用戶仍然可以看見和選擇使用示例 14-4 中的內(nèi)部結(jié)構(gòu),或者可以使用示例 14-5 中更為方便的結(jié)構(gòu),如示例 14-6 所示:

文件名: src/main.rs

use art::mix;
use art::PrimaryColor;

fn main() {
    // --snip--
}

示例 14-6:一個使用 art crate 中重導(dǎo)出項(xiàng)的程序

對于有很多嵌套模塊的情況,使用 pub use 將類型重導(dǎo)出到頂級結(jié)構(gòu)對于使用 crate 的人來說將會是大為不同的體驗(yàn)。

創(chuàng)建一個有用的公有 API 結(jié)構(gòu)更像是一門藝術(shù)而非科學(xué),你可以反復(fù)檢視他們來找出最適合用戶的 API。pub use 提供了解耦組織 crate 內(nèi)部結(jié)構(gòu)和與終端用戶體現(xiàn)的靈活性。觀察一些你所安裝的 crate 的代碼來看看其內(nèi)部結(jié)構(gòu)是否不同于公有 API。

創(chuàng)建 Crates.io 賬號

在你可以發(fā)布任何 crate 之前,需要在 crates.io 上注冊賬號并獲取一個 API token。為此,訪問位于 crates.io 的首頁并使用 GitHub 賬號登錄。(目前 GitHub 賬號是必須的,不過將來該網(wǎng)站可能會支持其他創(chuàng)建賬號的方法)一旦登錄之后,查看位于 https://crates.io/me/ 的賬戶設(shè)置頁面并獲取 API token。接著使用該 API token 運(yùn)行 cargo login 命令,像這樣:

$ cargo login abcdefghijklmnopqrstuvwxyz012345

這個命令會通知 Cargo 你的 API token 并將其儲存在本地的 ~/.cargo/credentials 文件中。注意這個 token 是一個 秘密secret)且不應(yīng)該與其他人共享。如果因?yàn)槿魏卧蚺c他人共享了這個信息,應(yīng)該立即到 crates.io 重新生成這個 token。

發(fā)布新 crate 之前

有了賬號之后,比如說你已經(jīng)有一個希望發(fā)布的 crate。在發(fā)布之前,你需要在 crate 的 Cargo.toml 文件的 [package] 部分增加一些本 crate 的元信息(metadata)。

首先 crate 需要一個唯一的名稱。雖然在本地開發(fā) crate 時(shí),可以使用任何你喜歡的名稱。不過 crates.io 上的 crate 名稱遵守先到先得的分配原則。一旦某個 crate 名稱被使用,其他人就不能再發(fā)布這個名稱的 crate 了。請?jiān)诰W(wǎng)站上搜索你希望使用的名稱來找出它是否已被使用。如果沒有,修改 Cargo.toml 中 [package] 里的名稱為你希望用于發(fā)布的名稱,像這樣:

文件名: Cargo.toml

[package]
name = "guessing_game"

即使你選擇了一個唯一的名稱,如果此時(shí)嘗試運(yùn)行 cargo publish 發(fā)布該 crate 的話,會得到一個警告接著是一個錯誤:

$ 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

這是因?yàn)槲覀內(nèi)鄙僖恍╆P(guān)鍵信息:關(guān)于該 crate 用途的描述和用戶可能在何種條款下使用該 crate 的 license。為了修正這個錯誤,需要在 Cargo.toml 中引入這些信息。

描述通常是一兩句話,因?yàn)樗鼤霈F(xiàn)在 crate 的搜索結(jié)果中和 crate 頁面里。對于 license 字段,你需要一個 license 標(biāo)識符值license identifier value)。Linux 基金會的 Software Package Data Exchange (SPDX) 列出了可以使用的標(biāo)識符。例如,為了指定 crate 使用 MIT License,增加 MIT 標(biāo)識符:

文件名: Cargo.toml

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

如果你希望使用不存在于 SPDX 的 license,則需要將 license 文本放入一個文件,將該文件包含進(jìn)項(xiàng)目中,接著使用 license-file 來指定文件名而不是使用 license 字段。

關(guān)于項(xiàng)目所適用的 license 指導(dǎo)超出了本書的范疇。很多 Rust 社區(qū)成員選擇與 Rust 自身相同的 license,這是一個雙許可的 MIT OR Apache-2.0。這個實(shí)踐展示了也可以通過 OR 分隔為項(xiàng)目指定多個 license 標(biāo)識符。

那么,有了唯一的名稱、版本號、由 cargo new 新建項(xiàng)目時(shí)增加的作者信息、描述和所選擇的 license,已經(jīng)準(zhǔn)備好發(fā)布的項(xiàng)目的 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 更容易被發(fā)現(xiàn)和使用!

發(fā)布到 Crates.io

現(xiàn)在我們創(chuàng)建了一個賬號,保存了 API token,為 crate 選擇了一個名字,并指定了所需的元數(shù)據(jù),你已經(jīng)準(zhǔn)備好發(fā)布了!發(fā)布 crate 會上傳特定版本的 crate 到 crates.io 以供他人使用。

發(fā)布 crate 時(shí)請多加小心,因?yàn)榘l(fā)布是 永久性的permanent)。對應(yīng)版本不可能被覆蓋,其代碼也不可能被刪除。crates.io 的一個主要目標(biāo)是作為一個存儲代碼的永久文檔服務(wù)器,這樣所有依賴 crates.io 中的 crate 的項(xiàng)目都能一直正常工作。而允許刪除版本沒辦法達(dá)成這個目標(biāo)。然而,可以被發(fā)布的版本號卻沒有限制。

再次運(yùn)行 cargo publish 命令。這次它應(yīng)該會成功:

$ 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)

恭喜!你現(xiàn)在向 Rust 社區(qū)分享了代碼,而且任何人都可以輕松的將你的 crate 加入他們項(xiàng)目的依賴。

發(fā)布現(xiàn)存 crate 的新版本

當(dāng)你修改了 crate 并準(zhǔn)備好發(fā)布新版本時(shí),改變 Cargo.toml 中 version 所指定的值。請使用 語義化版本規(guī)則 來根據(jù)修改的類型決定下一個版本號。接著運(yùn)行 cargo publish 來上傳新版本。

使用 cargo yank 從 Crates.io 撤回版本

雖然你不能刪除之前版本的 crate,但是可以阻止任何將來的項(xiàng)目將他們加入到依賴中。這在某個版本因?yàn)檫@樣或那樣的原因被破壞的情況很有用。對于這種情況,Cargo 支持 撤回yanking)某個版本。

撤回某個版本會阻止新項(xiàng)目開始依賴此版本,不過所有現(xiàn)存此依賴的項(xiàng)目仍然能夠下載和依賴這個版本。從本質(zhì)上說,撤回意味著所有帶有 Cargo.lock 的項(xiàng)目的依賴不會被破壞,同時(shí)任何新生成的 Cargo.lock 將不能使用被撤回的版本。

為了撤回一個 crate,運(yùn)行 cargo yank 并指定希望撤回的版本:

$ cargo yank --vers 1.0.1

也可以撤銷撤回操作,并允許項(xiàng)目可以再次開始依賴某個版本,通過在命令上增加 --undo

$ cargo yank --vers 1.0.1 --undo

撤回 并沒有 刪除任何代碼。舉例來說,撤回功能并不意在刪除不小心上傳的秘密信息。如果出現(xiàn)了這種情況,請立即重新設(shè)置這些秘密信息。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號