You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rust_basic_code/md_file/14 关于 Cargo 和 Crates.io 的更多...

624 lines
23 KiB

# 14 关于 Cargo 和 Crates.io 的更多内容
[Cargo](https://doc.rust-lang.org/cargo/) 的一些更高级的功能:
- 通过发布配置文件自定义您的构建过程
- 在 crates.io 上发布库
- 使用工作区组织大型项目
- 从 crates.io 安装二进制文件
- 使用自定义命令扩展 Cargo
## 自定义release配置文件
在 Rust 中,发布配置是预定义的可自定义配置文件,具有不同的配置,允许程序员更好地控制编译代码的各种选项。每个配置文件都是独立配置的。
Cargo 有两个主要的配置文件:当运行 `cargo build` 时使用的 **dev** 配置文件和当运行 `cargo build --release` 时使用的 **release** 配置文件。dev 配置文件为开发环境提供了良好的默认设置,而 release 配置文件则为发布构建提供了良好的默认设置。
![image-20230421113935081](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230421113935081.png)
`dev``release` 是编译器使用的不同配置文件。Cargo 为每个配置文件都有默认设置,当项目的 `Cargo.toml` 文件中没有明确添加任何 `[profile.*]` 部分时,这些设置会**自动应用**。通过为您想要自定义的任何配置文件添加 `[profile.*]` 部分,可以覆盖默认设置的任何子集。例如,下面是 `dev``release` 配置文件中 `opt-level` 设置的默认值:
```rust
[profile.dev]
opt-level = 0
[profile.release]
opt-level = 3
```
覆盖`dev`默认设置:
![image-20230421161039703](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230421161039703.png)
### 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` 值,以平衡生成的代码的调试性能和运行速度。[相关文档。](https://doc.rust-lang.org/cargo/reference/profiles.html)
## 发布Crate到Crate.io
### 提供有用的文档注释
文档注释使用三个斜杠,///,而不是两个,并支持Markdown符号来格式化文本。将文档注释放在他们要记录的项目的前面。它将生成HTML文档。
![image-20230423094431419](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423094431419.png)
生成 HTML 文档命令:
```rust
cargo doc // 从文档注释生成HTML文档。 生成位置:target/doc
cargo doc --open // 构建HTML,并在web浏览器中打开结果
```
HTML 界面:
![image-20230423101523963](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423101523963.png)
#### 常用文档注释
- Panics:这个部分描述了函数在何种情况下会 panic。如果一个函数可能导致程序 panic,那么调用该函数的用户需要确保不会出现这些情况。
- Errors:如果一个函数返回一个 Result 类型,那么这个部分描述了可能发生的错误以及导致这些错误的条件。这有助于用户编写处理不同错误类型的代码。
- Safety:如果一个函数是不安全的,那么这个部分需要解释为什么这个函数是不安全的,并列出用户需要遵守的不变量。
这些部分不是必需的,但可以提醒编写者在文档注释中包含哪些信息,以便用户更好地理解代码。
另外,本段落提到在文档注释中添加示例代码块(Examples)的好处是可以帮助用户理解如何使用库,并且这些示例代码块也可以作为测试运行。在运行 `cargo test` 命令时,Rust 会在生成的文档中查找示例代码块,并将其作为测试运行,以确保示例代码的正确性。
```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
}
}
```
添加后的页面:
![image-20230423103424252](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423103424252.png)
#### 使用文档注释进行测试
![image-20230423104049806](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423104049806.png)
#### 为包含注释的项(即 crate 或 module)添加文档
`///` 注释不同,`//!` 注释不是针对特定项的,而是针对包含注释的项的。因此,`//!` 注释通常用于在 **crate****module** 级别上添加文档。在 crate root 文件(通常是 `src/lib.rs` 文件)或 module 内部使用 `//!` 注释,可以为整个 crate 或 module 添加一段**描述其功能、设计理念和使用方法**等信息的文档。
当使用 `cargo doc` 命令生成 Rust crate 的 API 文档时,它将解析源代码中的文档注释,并将其转换为 HTML 文档。在生成的文档中,`//!` 注释将出现在 crate 或 module 的文档页面上,提供关于 crate 或 module 的描述、使用示例和其他相关信息。
```rust
//! # 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
}
```
对比图:
![image-20230423113656778](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423113656778.png)
### 使用 `pub use` 来重新导出公共项
当我们在开发一个 crate 时,我们可能会组织它的结构以方便我们自己使用,但是对于其他人来说,这可能并不方便。他们可能需要通过复杂的路径才能访问我们的类型、函数等。这时,我们可以使用 `pub use` 来重新导出项,以**创建一个公共 API**,使其他人更容易访问我们的类型、函数等。
```rust
//! # 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,
}
}
}
```
文档页面图:
![image-20230423135736510](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423135736510.png)
目前PrimaryColor和SecondaryColor类型以及mix()没有在首页列出。下面是一个使用此crate的代码:
```rust
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 重新导出顶层的项目。
```rust
//! # 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,
}
}
}
```
文档页面图:
![image-20230423151005642](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423151005642.png)
使用 Art crate代码:
```rust
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](https://crates.io/)上创建一个帐户,并获得一个API令牌。创建[链接](https://crates.io/settings/tokens)
![image-20230423154227662](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423154227662.png)
[cargo-login](https://doc.rust-lang.org/cargo/commands/cargo-login.html) 可以将 API token 保存在本地,方便后续上传 crate 时进行身份验证。如果没有登录,上传 crate 时将会提示你登录。使用 `cargo login` 命令登录成功后,API token 将保存在 `$HOME/.cargo/credentials` 文件中。在执行 `cargo publish` 命令时,会自动使用该文件中保存的 API token 进行身份验证,以便将 crate 发布到 crates.io。
![image-20230423160145017](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423160145017.png)
token位置:
![image-20230423160319545](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423160319545.png)
### 发布crate
发布一个 crate 之前需要在 Cargo.toml 文件中的 [package] 部分添加元数据,并且 crate 必须拥有唯一的名称。在发布到 crates.io 上时需要**检查该名称是否已经被使用**,如果已被占用就需要选择另一个名称进行发布。当在本地开发时,可以随意命名。
```rust
cargo publish // 发布crate
```
![image-20230423163737104](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423163737104.png)
发布是永久性的。版本永远不能被覆盖,代码也不能被删除。
### 发布crate新版本
当你对你的 crate 进行了更改并准备发布一个新版本时,你需要修改 Cargo.toml 文件中指定的**版本值**并重新发布。使用语义化版本控制规则来决定下一个适当的版本号,根据你所做的更改的种类。然后运行 `cargo publish` 命令来上传新版本。
![image-20230423165714253](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423165714253.png)
### 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` 命令将该版本弃用,这些密钥仍然可能被泄露,因此需要立即重置这些密钥。
```rust
cargo yank --vers 0.1.0
Updating crates.io index
Yank zm-test@0.1.0
```
页面显示:
![image-20230424103251005](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424103251005.png)
```rust
cargo yank --vers 0.1.0 --undo
Updating crates.io index
Yank zm-test@0.1.0
```
页面显示:
![image-20230424103512067](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424103512067.png)
## Cargo 工作区
Cargo workspace,它是一个包含多个 Rust 包的集合。**这些包共享同一个 Cargo.lock 文件和输出目录**。在这个例子中,我们创建了一个包含一个二进制包和两个库包的 workspace。这个二进制包提供主要的功能,依赖于这两个库包。其中一个库包提供了 add_one 函数,另一个库包提供了 add_two 函数。所有这三个 crate(包)都是 workspace 的一部分。Cargo 通过使用单个顶层目录下的 target 目录来管理 workspace 中所有 crate 的编译输出。这样,当有 crate 依赖于另一个 crate 时,就可以避免不必要的重新编译。
首先新建一个工作区目录:
```rust
mkdir add
cd add
```
然后新建一个Cargo.toml文件配置整个工作区,只需有以下内容即可:
```rust
[workspace]
members = [
"adder",
]
```
在add目录中新建adder二进制crate。并使用cargo build构建工作区。现目录中文件如下所示:
```rust
├── 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(新包名称)路径。
```rust
[workspace]
members = [
"adder",
"add_one",
]
```
新建 add_one lib crate。并使用cargo build:
```rust
├── 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()
![image-20230424151704617](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424151704617.png)
接下来让adder使用add_one包。首先修改adder/Cargo.toml文件。
![image-20230424161041199](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424161041199.png)
Cargo并不假设工作区中的crate将相互依赖,因此我们需要明确依赖关系。在adder中使用add_one包方法,
![image-20230424163442014](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424163442014.png)
#### **[cargo参数](https://doc.rust-lang.org/cargo/commands/cargo-run.html) -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` 命令用于运行指定包中的特定二进制文件,而不管该包中是否包含其他二进制文件。
![image-20230424164918427](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424164918427.png)
#### 依赖工作区外的包
工作区有一个顶级的 `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依赖
![image-20230424163205318](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424163205318.png)
build后,工作区的 Cargo.lock 已经包含了 add_one 对rand的依赖的信息。
![image-20230424163124138](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424163124138.png)
但若在其他 crate 中直接使用rand,是不可以的:
![image-20230424165906881](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424165906881.png)
必须在 `Cargo.toml` 文件中也添加 `rand` 作为依赖项才可以使用:
![image-20230424170928013](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424170928013.png)
不会下载rand的其他副本。Cargo已经确保工作空间中使用rand包的每个包中的每个crate都使用相同的版本,从而节省了我们的空间,并确保工作空间中的crate彼此兼容。
#### 在工作区中添加测试
当在工作区最外层运行 cargo test 时,会测试工作区所有的crate。
![image-20230424172215377](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424172215377.png)
输出的第一部分显示通过了add_one crate中的it_works测试。下一节显示在adder crate中没有找到任何测试,最后一节显示在add_one crate中没有找到任何文档测试。
可通过 -p 指定要测试的crate:
![image-20230424172810426](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424172810426.png)
#### 发布工作区crate
如果想将 workspace 中的 crate 发布到 crates.io 上,每个 crate 都需要**单独发布**,可以使用 `-p` 标志和要发布的 crate 的名称来发布特定的 crate。如果有多个 crate 需要发布,需要分别指定每个 crate 的名称并分别执行 `cargo publish -p <crate-name>` 命令。
## 使用cargo install安装可执行文件
[cargo install](https://doc.rust-lang.org/cargo/commands/cargo-install.html) 是用于本地安装和使用二进制 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(非本地程序,修改不了):
![image-20230425111309861](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230425111309861.png)
install 本地crate:
![image-20230425152141433](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230425152141433.png)
cargo install 操作 cargo install --force xx 用于覆盖crate:
![image-20230425153534566](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230425153534566.png)
## 自定义命令扩展Cargo
Cargo的设计使得你可以扩展它的子命令而不必修改Cargo本身。Cargo 可以通过添加自定义子命令进行扩展,只需在 $PATH 中添加名为 cargo-xxx 的二进制文件,即可通过 cargo xxx 命令运行。自定义命令还会在运行 cargo --list 时列出。通过 cargo install 安装扩展,并像内置 Cargo 工具一样运行。
![image-20230425161238033](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230425161238033.png)
cargo --list:
![image-20230425161337132](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230425161337132.png)
### 为自定义命令添加描述
目前还没有找到合适方法办法添加,测试了两种方法都失败了。第一种是修改Cargo.toml(失败)
```rust
[package]
name = "cargo-xxx"
version = "0.1.0"
[package.metadata.'cargo-xxx']
description = "My custom Cargo command"
```
第二种方式使用clap添加依赖(失败):
```rust
fn zm() -> App {
App::new("zm")
.about("描述")
.arg(Arg::with_name("file")
.help("The file to read")
.required(true)
.index(1))
}
```