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 的更多...

23 KiB

14 关于 Cargo 和 Crates.io 的更多内容

Cargo 的一些更高级的功能:

  • 通过发布配置文件自定义您的构建过程
  • 在 crates.io 上发布库
  • 使用工作区组织大型项目
  • 从 crates.io 安装二进制文件
  • 使用自定义命令扩展 Cargo

自定义release配置文件

在 Rust 中,发布配置是预定义的可自定义配置文件,具有不同的配置,允许程序员更好地控制编译代码的各种选项。每个配置文件都是独立配置的。

Cargo 有两个主要的配置文件:当运行 cargo build 时使用的 dev 配置文件和当运行 cargo build --release 时使用的 release 配置文件。dev 配置文件为开发环境提供了良好的默认设置,而 release 配置文件则为发布构建提供了良好的默认设置。

image-20230421113935081

devrelease 是编译器使用的不同配置文件。Cargo 为每个配置文件都有默认设置,当项目的 Cargo.toml 文件中没有明确添加任何 [profile.*] 部分时,这些设置会自动应用。通过为您想要自定义的任何配置文件添加 [profile.*] 部分,可以覆盖默认设置的任何子集。例如,下面是 devrelease 配置文件中 opt-level 设置的默认值:

[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3

覆盖dev默认设置:

image-20230421161039703

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文档。

image-20230423094431419

生成 HTML 文档命令:

cargo doc  // 从文档注释生成HTML文档。   生成位置:target/doc
cargo doc --open // 构建HTML,并在web浏览器中打开结果

HTML 界面:

image-20230423101523963

常用文档注释

  • 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
    }
}

添加后的页面:

image-20230423103424252

使用文档注释进行测试

image-20230423104049806

为包含注释的项(即 crate 或 module)添加文档

/// 注释不同,//! 注释不是针对特定项的,而是针对包含注释的项的。因此,//! 注释通常用于在 cratemodule 级别上添加文档。在 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
}

对比图:

image-20230423113656778

使用 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,
         }
    }
}

文档页面图:

image-20230423135736510

目前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,
         }
    }
}

文档页面图:

image-20230423151005642

使用 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令牌。创建链接

image-20230423154227662

cargo-login 可以将 API token 保存在本地,方便后续上传 crate 时进行身份验证。如果没有登录,上传 crate 时将会提示你登录。使用 cargo login 命令登录成功后,API token 将保存在 $HOME/.cargo/credentials 文件中。在执行 cargo publish 命令时,会自动使用该文件中保存的 API token 进行身份验证,以便将 crate 发布到 crates.io。

image-20230423160145017

token位置:

image-20230423160319545

发布crate

发布一个 crate 之前需要在 Cargo.toml 文件中的 [package] 部分添加元数据,并且 crate 必须拥有唯一的名称。在发布到 crates.io 上时需要检查该名称是否已经被使用,如果已被占用就需要选择另一个名称进行发布。当在本地开发时,可以随意命名。

cargo publish // 发布crate

image-20230423163737104

发布是永久性的。版本永远不能被覆盖,代码也不能被删除。

发布crate新版本

当你对你的 crate 进行了更改并准备发布一个新版本时,你需要修改 Cargo.toml 文件中指定的版本值并重新发布。使用语义化版本控制规则来决定下一个适当的版本号,根据你所做的更改的种类。然后运行 cargo publish 命令来上传新版本。

image-20230423165714253

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

页面显示:

image-20230424103251005

cargo yank --vers 0.1.0 --undo
    Updating crates.io index
        Yank zm-test@0.1.0

页面显示:

image-20230424103512067

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

image-20230424151704617

接下来让adder使用add_one包。首先修改adder/Cargo.toml文件。

image-20230424161041199

Cargo并不假设工作区中的crate将相互依赖,因此我们需要明确依赖关系。在adder中使用add_one包方法,

image-20230424163442014

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 命令用于运行指定包中的特定二进制文件,而不管该包中是否包含其他二进制文件。

image-20230424164918427

依赖工作区外的包

工作区有一个顶级的 Cargo.lock 文件,而不是在每个 crate 的目录下都有一个 Cargo.lock 文件,这确保了所有 crate 都使用相同版本的所有依赖项。在 add_oneadder crate 的 Cargo.toml 文件中添加 rand 包作为依赖项,然后运行 cargo build 来构建整个 workspace,这样所有 crate 就可以使用相同版本的 rand 包了。虽然 rand 包已经在 workspace 中使用了,但是如果想在 workspace 中的其他 crate 中使用它,就必须在这些 crate 的 Cargo.toml 文件中也添加 rand 作为依赖项。

先在 add_one/Cargo.toml 添加 rand依赖

image-20230424163205318

build后,工作区的 Cargo.lock 已经包含了 add_one 对rand的依赖的信息。

image-20230424163124138

但若在其他 crate 中直接使用rand,是不可以的:

image-20230424165906881

必须在 Cargo.toml 文件中也添加 rand 作为依赖项才可以使用:

image-20230424170928013

不会下载rand的其他副本。Cargo已经确保工作空间中使用rand包的每个包中的每个crate都使用相同的版本,从而节省了我们的空间,并确保工作空间中的crate彼此兼容。

在工作区中添加测试

当在工作区最外层运行 cargo test 时,会测试工作区所有的crate。

image-20230424172215377

输出的第一部分显示通过了add_one crate中的it_works测试。下一节显示在adder crate中没有找到任何测试,最后一节显示在add_one crate中没有找到任何文档测试。

可通过 -p 指定要测试的crate:

image-20230424172810426

发布工作区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(非本地程序,修改不了):

image-20230425111309861

install 本地crate:

image-20230425152141433

cargo install 操作 cargo install --force xx 用于覆盖crate:

image-20230425153534566

自定义命令扩展Cargo

Cargo的设计使得你可以扩展它的子命令而不必修改Cargo本身。Cargo 可以通过添加自定义子命令进行扩展,只需在 $PATH 中添加名为 cargo-xxx 的二进制文件,即可通过 cargo xxx 命令运行。自定义命令还会在运行 cargo --list 时列出。通过 cargo install 安装扩展,并像内置 Cargo 工具一样运行。

image-20230425161238033

cargo --list:

image-20230425161337132

为自定义命令添加描述

目前还没有找到合适方法办法添加,测试了两种方法都失败了。第一种是修改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))

}