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)常在文檔注釋中使用的部分有:
panic!
? 的場景。并不希望程序崩潰的函數(shù)調(diào)用者應(yīng)該確保他們不會在這些情況下調(diào)用此函數(shù)。Result
?,此部分描述可能會出現(xiàn)何種錯誤以及什么情況會造成這些錯誤,這有助于調(diào)用者編寫代碼來采用不同的方式處理不同的錯誤。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
,你將會看到文檔測試捕獲到了例子與代碼不再同步!
還有另一種風(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 用戶理解你的代碼組織。
第七章介紹了如何使用 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。
在你可以發(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。
有了賬號之后,比如說你已經(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)和使用!
現(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)目的依賴。
當(dāng)你修改了 crate 并準(zhǔn)備好發(fā)布新版本時(shí),改變 Cargo.toml 中 version
所指定的值。請使用 語義化版本規(guī)則 來根據(jù)修改的類型決定下一個版本號。接著運(yùn)行 cargo publish
來上傳新版本。
雖然你不能刪除之前版本的 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è)置這些秘密信息。
更多建議: