# 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 ` 命令。 ## 使用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)) } ```