26_rust_cargo、crates.io

rust_cargo、crates.io

通过release profile自定义构建

release profile(发布配置):是预定义的,可自定义,可使用不用的配置,对代码编译拥有更多的控制。
每个profile的配置都独立于其它的profile。

cargo主要的两个profile:

  • dev profile:适用于开发,cargo build
  • release profile:适用于发布,cargo build -release

自定义profile

针对每个profile,cargo都提供了默认的配置,如果想自定义xxx profile配置,可在cargo.toml里添加[profile.xxx]区域,在里面覆盖默认配置的子集。

[profile.dev]
opt-level = 0 # 优化等级为0,O0优化

[profile.release]
opt-level = 3

对于每个配置的默认值和完整选项, 请参见: https://doc.rust-lang.org/cargo/

发布create到crates.io

crates.io是提供依赖库的,自己开发的crate也可通过发布包来共享代码。crate的注册表在https://crates.io/:
它会分发已注册的包的源代码,主要托管开源的代码。

创建并设置Crates.io账号

1)发布crate前,需在crates.io创建账号并获得API token,可用github账号登录,
2)运行命令:cargo login [你的API token],通知cargo,你的API token存储在本地~/.cargo/credentials
注:API token可在https://crates.io/进行撤销

为新的crate添加元数据

在发布crate前,需要在Cargo.toml的package]区域为crate添加一些元数据:

  • crate需要唯一的名称:name
  • description;一两句描述,后会出现在crate搜索的结果里
  • license:需提供许可标识值(可到http://spdx.org/licenses/ 查找),可指定多个license,用OR隔开
  • version
  • author

发布到Crates.io

然后发布:cargo publish命令
crate一旦发布,就是永久性的,该版本无法覆盖,代码无法删除,目的是依赖于该版本的项目可继续正常工作。

发布已存在的crate新版本

修改crate后,需要先修改Cargo.toml里的version值,再重新发布。参照http://semver.org/ 使用你的语义版本。再执行cargo publish命令发布。

使用cargo yank撤回版本

使用cargo yank从Crates.io撤回版本:

  • 不可删除crate之前的版本
  • 但可防止其它项目把它作为新的依赖,yank(撤回)一个crate版本后,可防止新项目依赖于该版本,已存在项目可继续使用和依赖(并可下载)
  • yank意味着所有已产生的Cargo.lock的项目都不会中断,任何将来生成的Cargo.lock文件都不会使用被yank的版本。
  • 命令:yank一个版本,不会删除任何代码,cargo yank --vers 1.0.1
  • 取消yank:cargo yank --vers 1.0.1 --undo

文档注释

在函数或方法前,添加文档注释,用于生成帮助文档:

  • 生成HTML文档
  • 显示公共API的文档注释,如何使用API
  • 使用///来注释
  • 支持Markdown语法
  • 放置在被说明条目之前

生成HTML文档的命令
cargo doc命令

  • 它会运行rustdoc工具(rust安装包时自带的工具)
  • 把生成的HTML文档放在target/doc目录下

cargo doc --open命令

  • 构建当前crate的文档(也包含crate依赖项的文档)
  • 并在浏览器中打开文档

例子:创建lib.rs文件,内容:

/// add function api
/// # Example
/// ```rust
/// let a = 5;
/// let b = 6;
/// let ret = hello_cargo::add_func(a, b);
/// assert_eq!(11, ret);
/// ```
pub fn add_func(a: i32, b: i32) -> i32 {
    a + b
}

执行cargo doc --open即可打开文档

常用章节
比如代码中的# Examples章节,其他常用章节:

  • Panics:函数可能发生panic的场景
  • Errors:如果函数返回Result,描述可能的错误种类,以及可导致错误的条件
  • Safety:如果函数处于unsafe调用,应解释函数unsafe的原因,以及调用者确保的使用前提

文档注释作为测试
示例代码块的附加值:运行cargo test的时候,会把文档注释中的示例代码作为测试来运行
如创建hello_cargo项目,创建src/lib.rs文件,内容:

/// add function api
/// # Example
/// ```
/// let a = 5;
/// let b = 6;
/// let ret = hello_cargo::add_func(a, b);
/// assert_eq!(11, ret);
/// ```
pub fn add_func(a: i32, b: i32) -> i32 {
    a + b
}

执行cargo test命令,打印:

   Doc-tests hello_cargo

running 1 test
test src\lib.rs - add_func (line 3) ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.32s

为包含注释的项添加文档注释
使用符号://!,这类注释通常用于描述crate和模块,crate root(按惯例src/lib.rs),或者一个模块内,将crate或模块作为一个整体进行记录。

//! # hello_cargo crate
//! 
//! `hello_cargo` is test crate
//! 
/// add function api
/// # Example
/// ```
/// let a = 5;
/// let b = 6;
/// let ret = hello_cargo::add_func(a, b);
/// assert_eq!(11, ret);
/// ```
pub fn add_func(a: i32, b: i32) -> i32 {
    a + b
}

再次执行cargo doc --open后,打开的文档最上方多出了内容:

hello_cargo crate
hello_cargo is test crate

pub use

使用pub use导出方便使用的公共API。
在实际项目中,会存在如下的问题:
crate的程序结构在开发时对于开发者很合理, 但对于它的使用者不够方便,开发者会把程序结构分为很多层,使用者想找到这种深层结构中的某个类型很费劲。
比如:开发的库中有定义my_crate:some_module::another_module:UsefulType这种层次关系,使用者要使用UsefulType就得访问很多层,而实际希望的使用方法是my_crate::UsefulType,能够直接使用。

解决办法: 不需要重新组织内部代码结构,使用pub use: 可以重新导出,创建一个与内部私有结构不同的对外公共结构。
比如例子hello_cargo:
main.rs内容:

use hello_cargo::modu1::EnumColor;
use hello_cargo::utils_modu::mix; // 使用者引入很深
fn main() {
    let r = EnumColor::Blue;
    mix(r);
}

lib.rs内容:

//! # test_crate
//! 
//! A test library
//! 
pub mod modu1 {
    /// test enum 1
    pub enum EnumColor {
        Red,
        Blue,
    }
    /// test enum 2
    pub enum Enum2 {
        Aaa,
        Bbb,
    }
}

pub mod utils_modu {
    use crate::modu1::*;
    /// a test function for pub use
    pub fn mix(c1: EnumColor) -> Enum2 {
        Enum2::Aaa
    }
}

使用cargo doc --open生成的文档如下图,也是比较深,不直观。只能看到两个muduler,点进去才能看到enum和函数。

Crate hello_cargoCopy item path
source · [−]
test_crate
A test library

Modules 只能看到两个muduler,点进去才能看到enum和函数
modu1
utils_modu

但将lib.rs改成如下导出模式:

//! # test_crate
//! 
//! A test library
//!

pub use self::modu1::EnumColor;
pub use self::modu1::Enum2;
pub use self::utils_modu::mix;

pub mod modu1 {
    /// test enum 1
    pub enum EnumColor {
        Red,
        Blue,
    }
    /// test enum 2
    pub enum Enum2 {
        Aaa,
        Bbb,
    }
}

pub mod utils_modu {
    use crate::modu1::*;
    /// a test function for pub use
    pub fn mix(c1: EnumColor) -> Enum2 {
        Enum2::Aaa
    }
}

再执行cargo doc --open,内容变成如下模式,用户能够直接看到导出的关键信息。

Crate hello_cargoCopy item path
source · [−]
test_crate
A test library

Re-exports
pub use self::modu1::EnumColor;
pub use self::modu1::Enum2;
pub use self::utils_modu::mix; 能直接看到函数接口
Modules
modu1
utils_modu

main.rs里的引入也可改成如下写法:

// use hello_cargo::modu1::EnumColor;
// use hello_cargo::utils_modu::mix; // 使用者引入很深
use hello_cargo::EnumColor;
use hello_cargo::mix; //直接引入
fn main() {
    let r = EnumColor::Blue;
    mix(r);
}

cargo的工作空间(Workspaces)

cargo工作空间,帮助管理多个相互关联且需要协同开发的crate,是一套共享同一个Cargo.lock和输出文件夹的包。

创建工作空间,例子:1二进制crate依赖于2个库crate
1)首先创建workspace目录,使用mkdir workspace_test,进入workspace_test目录后,创建Cargo.toml文件用于配置整个workspace,内容如下:

[workspace] # 用于配置整个工作空间

# 添加成员,比如二进制成员bin_member
members = [
    "bin_member"
]

2)创建出配置文件里的bin_member,在workspace_test目录下,执行cargo new bin_member命令,创建出常规二进制项目,含src/main.rs和Cargo.toml文件。

3)在workspace_test目录下,执行cargo build命令,可见能正常编译通过了,并产生了Cargo.lock和target目录,target目录用于存放所有成员的编译产出物。二进制成员bin_member则不再有target目录,即便进入bin_member目录执行cargo build也不会产生target目录。

4)继续添加lib成员,在workspace_test目录下,修改workspace_test目录下的Cargo.toml内容如下:

[workspace] # 用于配置整个工作空间

# 添加成员,比如二进制成员
members = [
    "bin_member",
    "adder_member",
    "logic_member",
]

5)创建出成员lib,

cargo new adder_member --lib
cargo new logic_member --lib

其中adder_member/src/lib.rs内容,含有一add函数:

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

logic_member/src/lib.rs内容,含一bitwise_or函数:

pub fn bitwise_or(left: usize, right: usize) -> usize {
    left | right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = bitwise_or(5, 2);
        assert_eq!(result, 7);
    }
}

此时在workspace_test目录下执行cargo test命令就可可执行两个库里的test用例了(跑所有用例),也可执行cargo test -p adder_member命令只执行一个crate的用例。

6)因为cargo不会隐式假设目录之间依赖,所以需要在bin_member里显式引入,修改bin_member/Cargo.toml引入这两个Lib:

[package]
name = "bin_member"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

adder_member = { path = "../adder_member" }
logic_member = { path = "../logic_member" }

再修改bin_member/src/main.rs文件内容,引入两个lib:

use adder_member;
use logic_member;

fn main() {
    let ret = adder_member::add(2, 3);
    let ret2 = logic_member::bitwise_or(5, 2);
    println!("{}, {}", ret, ret2);
    assert_eq!(5, ret);
    assert_eq!(7, ret2);
}

在workspace_test主目录下执行cargo build和cargo run命令可编译/运行项目。也可通过cargo run -p bin_member命令指定运行二进制名称运行对应crate。

7)可在工作空间中依赖外部crate,工作空间只有一个Cargo.lock文件,在顶层目录中,可保证工作空间内所有crate使用的依赖版本都相同,使得工作空间所有crate相互兼容。另外,如果要执行cargo pub use,则需要进入每个crate目录下进行发布。

从crate.io安装二进制crate

执行命令:cargo install, 安装来源:https://crates.io,只能安装具有二进制目标(binary target)的crate。
二进制目标(binary target)是一个可执行程序,当拥有src/main.rs或其它被指定为二进制文件的crate生成。
通常readme里有关于crate是否含有library target或binary target的描述。

cargo install安装的二进制存放在根目录的bin文件夹中,如果是rustup安装的rust,无任何自定义配置,二进制存放目录是$HOME/.cargo/bin,需确保该目录在环境变量$PATH中。

使用自定义命令扩展cargo

  • cargo 被设计成可以使用子命令来扩展
    例:如果 $PATH 中的某个二进制是cargo-something,就可以像子命令一样运行: cargo something。
  • 类似这样的自定义命令可以通过该命令列出:cargo --list
  • 优点: 可使用cargo install 来安装扩展, 像内置工具一样来运行
posted @ 2023-11-10 00:50  00lab  阅读(22)  评论(0编辑  收藏  举报