23 KiB
14 关于 Cargo 和 Crates.io 的更多内容
Cargo 的一些更高级的功能:
- 通过发布配置文件自定义您的构建过程
- 在 crates.io 上发布库
- 使用工作区组织大型项目
- 从 crates.io 安装二进制文件
- 使用自定义命令扩展 Cargo
自定义release配置文件
在 Rust 中,发布配置是预定义的可自定义配置文件,具有不同的配置,允许程序员更好地控制编译代码的各种选项。每个配置文件都是独立配置的。
Cargo 有两个主要的配置文件:当运行 cargo build
时使用的 dev 配置文件和当运行 cargo build --release
时使用的 release 配置文件。dev 配置文件为开发环境提供了良好的默认设置,而 release 配置文件则为发布构建提供了良好的默认设置。
dev
和 release
是编译器使用的不同配置文件。Cargo 为每个配置文件都有默认设置,当项目的 Cargo.toml
文件中没有明确添加任何 [profile.*]
部分时,这些设置会自动应用。通过为您想要自定义的任何配置文件添加 [profile.*]
部分,可以覆盖默认设置的任何子集。例如,下面是 dev
和 release
配置文件中 opt-level
设置的默认值:
[profile.dev]
opt-level = 0
[profile.release]
opt-level = 3
覆盖dev
默认设置:
opt-level
opt-level
是 Rust 编译器中一个重要的优化选项,用于控制生成的代码的优化级别。opt-level
的值可以是 0 到 3,分别代表不同的优化级别:
opt-level = 0
:不进行优化。生成的代码更容易调试,但运行速度会变慢。opt-level = 1
:启用一些基本的优化,例如删除不可达代码。生成的代码仍然可以很好地调试,但运行速度比opt-level = 0
更快。opt-level = 2
:启用更多的优化,例如内联函数和循环展开。生成的代码比opt-level = 1
更快,但可能更难以调试。opt-level = 3
:启用所有优化。生成的代码运行速度最快,但可能更难以调试。
在 dev
配置文件中,opt-level
的默认值为 0
,这意味着生成的代码更容易调试。而在 release
配置文件中,opt-level
的默认值为 3
,这意味着生成的代码运行速度最快,但可能更难以调试。可以根据具体情况,自定义不同的 opt-level
值,以平衡生成的代码的调试性能和运行速度。相关文档。
发布Crate到Crate.io
提供有用的文档注释
文档注释使用三个斜杠,///,而不是两个,并支持Markdown符号来格式化文本。将文档注释放在他们要记录的项目的前面。它将生成HTML文档。
生成 HTML 文档命令:
cargo doc // 从文档注释生成HTML文档。 生成位置:target/doc
cargo doc --open // 构建HTML,并在web浏览器中打开结果
HTML 界面:
常用文档注释
- Panics:这个部分描述了函数在何种情况下会 panic。如果一个函数可能导致程序 panic,那么调用该函数的用户需要确保不会出现这些情况。
- Errors:如果一个函数返回一个 Result 类型,那么这个部分描述了可能发生的错误以及导致这些错误的条件。这有助于用户编写处理不同错误类型的代码。
- Safety:如果一个函数是不安全的,那么这个部分需要解释为什么这个函数是不安全的,并列出用户需要遵守的不变量。
这些部分不是必需的,但可以提醒编写者在文档注释中包含哪些信息,以便用户更好地理解代码。
另外,本段落提到在文档注释中添加示例代码块(Examples)的好处是可以帮助用户理解如何使用库,并且这些示例代码块也可以作为测试运行。在运行 cargo test
命令时,Rust 会在生成的文档中查找示例代码块,并将其作为测试运行,以确保示例代码的正确性。
/// Increment an integer by one.
///
/// # Examples
///
/// ```
/// let x = 5;
/// let result = add_one(x);
///
/// assert_eq!(result, 6);
/// ```
///
/// # Panics
///
/// This function will panic if the input value is `std::i32::MAX`.
///
/// # Errors
///
/// This function does not return an error.
///
/// # Safety
///
/// This function is safe to call with any valid `i32` value.
///
/// However, calling this function with a value of `std::i32::MAX` will result in a panic.
pub fn add_one(x: i32) -> i32 {
if x == std::i32::MAX {
panic!("Attempted to add one to i32::MAX");
} else {
x + 1
}
}
添加后的页面:
使用文档注释进行测试
为包含注释的项(即 crate 或 module)添加文档
与 ///
注释不同,//!
注释不是针对特定项的,而是针对包含注释的项的。因此,//!
注释通常用于在 crate 或 module 级别上添加文档。在 crate root 文件(通常是 src/lib.rs
文件)或 module 内部使用 //!
注释,可以为整个 crate 或 module 添加一段描述其功能、设计理念和使用方法等信息的文档。
当使用 cargo doc
命令生成 Rust crate 的 API 文档时,它将解析源代码中的文档注释,并将其转换为 HTML 文档。在生成的文档中,//!
注释将出现在 crate 或 module 的文档页面上,提供关于 crate 或 module 的描述、使用示例和其他相关信息。
//! # examples
//!
//! `Examples` is a collection of utilities to make performing certain
//! calculations more convenient.
/// 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
}
对比图:
使用 pub use
来重新导出公共项
当我们在开发一个 crate 时,我们可能会组织它的结构以方便我们自己使用,但是对于其他人来说,这可能并不方便。他们可能需要通过复杂的路径才能访问我们的类型、函数等。这时,我们可以使用 pub use
来重新导出项,以创建一个公共 API,使其他人更容易访问我们的类型、函数等。
//! # 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{
match (c1, c2) {
(PrimaryColor::Red, PrimaryColor::Yellow) |
(PrimaryColor::Yellow, PrimaryColor::Red) => SecondaryColor::Orange,
(PrimaryColor::Red, PrimaryColor::Blue) |
(PrimaryColor::Blue, PrimaryColor::Red) => SecondaryColor::Purple,
(PrimaryColor::Yellow, PrimaryColor::Blue) |
(PrimaryColor::Blue, PrimaryColor::Yellow) => SecondaryColor::Green,
(_, _) => SecondaryColor::Purple,
}
}
}
文档页面图:
目前PrimaryColor和SecondaryColor类型以及mix()没有在首页列出。下面是一个使用此crate的代码:
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
根据代码可见,使用PrimaryColor和mix必须知道他们所处的模块。为解决此问题可使用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 {
/// 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{
match (c1, c2) {
(PrimaryColor::Red, PrimaryColor::Yellow) |
(PrimaryColor::Yellow, PrimaryColor::Red) => SecondaryColor::Orange,
(PrimaryColor::Red, PrimaryColor::Blue) |
(PrimaryColor::Blue, PrimaryColor::Red) => SecondaryColor::Purple,
(PrimaryColor::Yellow, PrimaryColor::Blue) |
(PrimaryColor::Blue, PrimaryColor::Yellow) => SecondaryColor::Green,
(_, _) => SecondaryColor::Purple,
}
}
}
文档页面图:
使用 Art crate代码:
use art::PrimaryColor;
use art::mix;
fn main() {
let blue = PrimaryColor::Blue;
let yellow = PrimaryColor::Yellow;
println!("{:#?}", mix(blue, yellow));
}
使用 pub use
可以将内部类型和函数重新导出到顶层,以提高代码的可读性和可用性。创建有用的公共 API 结构更多的是一种艺术而不是一门科学,需要进行多次迭代和测试以找到最佳的设计。
创建 crates.io 账号
在发布任何crate之前,您需要在crate上创建一个帐户,并获得一个API令牌。创建链接
cargo-login 可以将 API token 保存在本地,方便后续上传 crate 时进行身份验证。如果没有登录,上传 crate 时将会提示你登录。使用 cargo login
命令登录成功后,API token 将保存在 $HOME/.cargo/credentials
文件中。在执行 cargo publish
命令时,会自动使用该文件中保存的 API token 进行身份验证,以便将 crate 发布到 crates.io。
token位置:
发布crate
发布一个 crate 之前需要在 Cargo.toml 文件中的 [package] 部分添加元数据,并且 crate 必须拥有唯一的名称。在发布到 crates.io 上时需要检查该名称是否已经被使用,如果已被占用就需要选择另一个名称进行发布。当在本地开发时,可以随意命名。
cargo publish // 发布crate
发布是永久性的。版本永远不能被覆盖,代码也不能被删除。
发布crate新版本
当你对你的 crate 进行了更改并准备发布一个新版本时,你需要修改 Cargo.toml 文件中指定的版本值并重新发布。使用语义化版本控制规则来决定下一个适当的版本号,根据你所做的更改的种类。然后运行 cargo publish
命令来上传新版本。
cargo yank 弃用 crate 版本
cargo yank
命令来弃用某个 crate 版本。弃用版本意味着防止未来的项目使用这个版本作为依赖,但所有现有项目仍然可以继续使用该版本。这在某些情况下很有用,比如某个 crate 版本存在 bug 或安全问题。使用 cargo yank
命令可以更新 Crates.io 的索引,并将指定版本标记为弃用状态。
要使用 cargo yank
命令弃用某个版本,你需要在之前已发布的 crate 目录下运行该命令,并指定要弃用的版本号。例如,如果要弃用名为 zm-test
版本号为 0.1.0 的 crate,需要在该 crate 目录下运行 cargo yank --vers 0.1.0
命令。如果需要重新启用已经弃用的版本,可以运行 cargo yank --vers 0.1.0 --undo
命令来撤销弃用状态。
需要注意的是,弃用版本不会删除任何代码,也不会防止已经使用该版本的项目出现问题,例如,如果一个版本中包含了误上传的密钥,那么即使使用 cargo yank
命令将该版本弃用,这些密钥仍然可能被泄露,因此需要立即重置这些密钥。
cargo yank --vers 0.1.0
Updating crates.io index
Yank zm-test@0.1.0
页面显示:
cargo yank --vers 0.1.0 --undo
Updating crates.io index
Yank zm-test@0.1.0
页面显示:
Cargo 工作区
Cargo workspace,它是一个包含多个 Rust 包的集合。这些包共享同一个 Cargo.lock 文件和输出目录。在这个例子中,我们创建了一个包含一个二进制包和两个库包的 workspace。这个二进制包提供主要的功能,依赖于这两个库包。其中一个库包提供了 add_one 函数,另一个库包提供了 add_two 函数。所有这三个 crate(包)都是 workspace 的一部分。Cargo 通过使用单个顶层目录下的 target 目录来管理 workspace 中所有 crate 的编译输出。这样,当有 crate 依赖于另一个 crate 时,就可以避免不必要的重新编译。
首先新建一个工作区目录:
mkdir add
cd add
然后新建一个Cargo.toml文件配置整个工作区,只需有以下内容即可:
[workspace]
members = [
"adder",
]
在add目录中新建adder二进制crate。并使用cargo build构建工作区。现目录中文件如下所示:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
工作区在顶层有一个target目录,将编译后的文件放入其中;adder包没有自己的目标目录。即使我们从adder目录内部运行cargo build,编译后的构件仍将在add/target而不是add/adder/target中。Cargo在工作区中按照这种方式结构化目标目录,因为工作区中的包应该相互依赖。
在工作区创建第二个包
首先修改最外层的 Cargo.toml, 添加add_one(新包名称)路径。
[workspace]
members = [
"adder",
"add_one",
]
新建 add_one lib crate。并使用cargo build:
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
在 add_one/src/lib.rs 中添加 add_one()
接下来让adder使用add_one包。首先修改adder/Cargo.toml文件。
Cargo并不假设工作区中的crate将相互依赖,因此我们需要明确依赖关系。在adder中使用add_one包方法,
cargo参数 -p 与 -bin
cargo run -p
命令用于运行指定的包,而包通常包含一个或多个二进制文件。如果包只包含一个二进制文件,那么这个文件就是默认的二进制文件。cargo run -p
命令将运行指定包中的默认二进制文件。
如果包含多个二进制文件,可以通过 cargo run -p
命令的 --bin
选项来指定要运行的特定二进制文件。例如,cargo run -p my_package --bin my_binary
将运行 my_package
包中名为 my_binary
的二进制文件。
因此,cargo run -p
命令可以用于运行指定的包和该包中的默认或指定的二进制文件。
另一方面,cargo run --bin
命令用于运行指定包中的特定二进制文件。需要同时指定包名和要运行的二进制文件名。例如,cargo run --bin my_binary --package my_package
将运行名为 my_package
的包中名为 my_binary
的二进制文件。
因此,cargo run --bin
命令用于运行指定包中的特定二进制文件,而不管该包中是否包含其他二进制文件。
依赖工作区外的包
工作区有一个顶级的 Cargo.lock
文件,而不是在每个 crate 的目录下都有一个 Cargo.lock
文件,这确保了所有 crate 都使用相同版本的所有依赖项。在 add_one
和 adder
crate 的 Cargo.toml
文件中添加 rand
包作为依赖项,然后运行 cargo build
来构建整个 workspace,这样所有 crate 就可以使用相同版本的 rand
包了。虽然 rand
包已经在 workspace 中使用了,但是如果想在 workspace 中的其他 crate 中使用它,就必须在这些 crate 的 Cargo.toml
文件中也添加 rand
作为依赖项。
先在 add_one/Cargo.toml 添加 rand依赖
build后,工作区的 Cargo.lock 已经包含了 add_one 对rand的依赖的信息。
但若在其他 crate 中直接使用rand,是不可以的:
必须在 Cargo.toml
文件中也添加 rand
作为依赖项才可以使用:
不会下载rand的其他副本。Cargo已经确保工作空间中使用rand包的每个包中的每个crate都使用相同的版本,从而节省了我们的空间,并确保工作空间中的crate彼此兼容。
在工作区中添加测试
当在工作区最外层运行 cargo test 时,会测试工作区所有的crate。
输出的第一部分显示通过了add_one crate中的it_works测试。下一节显示在adder crate中没有找到任何测试,最后一节显示在add_one crate中没有找到任何文档测试。
可通过 -p 指定要测试的crate:
发布工作区crate
如果想将 workspace 中的 crate 发布到 crates.io 上,每个 crate 都需要单独发布,可以使用 -p
标志和要发布的 crate 的名称来发布特定的 crate。如果有多个 crate 需要发布,需要分别指定每个 crate 的名称并分别执行 cargo publish -p <crate-name>
命令。
使用cargo install安装可执行文件
cargo install 是用于本地安装和使用二进制 crate 的命令,不是用来替代系统包管理的。它适用于 Rust 开发者安装和使用别人在 crates.io 上分享的工具。注意,只有具有二进制目标的包才能被安装。二进制目标是指如果 crate 有 src/main.rs 文件或者指定了其他二进制文件,生成的可执行程序,而不是一个不能独立运行,但适合包含在其他程序中的库目标。
所有用 cargo install 安装的二进制程序都存储在安装根目录的 bin 文件夹中。如果你使用 rustup.rs 安装了 Rust 并且没有进行自定义配置,该目录将为 $HOME/.cargo/bin。确保该目录在 $PATH 中,以便能够运行使用 cargo install 安装的程序。
cargo install
用于将某个 crate 的二进制文件安装到系统的二进制目录下,以便在命令行中直接调用该程序。通常用于安装 Rust 生态系统中提供的工具或第三方应用程序。cargo run
用于在开发阶段直接运行项目中的二进制文件,通常用于测试、调试和验证代码。
简而言之,cargo run
用于开发阶段直接运行代码,而 cargo install
则用于在生产环境中安装可执行文件。
install 远端crate(非本地程序,修改不了):
install 本地crate:
cargo install 操作 cargo install --force xx 用于覆盖crate:
自定义命令扩展Cargo
Cargo的设计使得你可以扩展它的子命令而不必修改Cargo本身。Cargo 可以通过添加自定义子命令进行扩展,只需在 $PATH 中添加名为 cargo-xxx 的二进制文件,即可通过 cargo xxx 命令运行。自定义命令还会在运行 cargo --list 时列出。通过 cargo install 安装扩展,并像内置 Cargo 工具一样运行。
cargo --list:
为自定义命令添加描述
目前还没有找到合适方法办法添加,测试了两种方法都失败了。第一种是修改Cargo.toml(失败)
[package]
name = "cargo-xxx"
version = "0.1.0"
[package.metadata.'cargo-xxx']
description = "My custom Cargo command"
第二种方式使用clap添加依赖(失败):
fn zm() -> App {
App::new("zm")
.about("描述")
.arg(Arg::with_name("file")
.help("The file to read")
.required(true)
.index(1))
}