Update(cargo): unified format 6

pull/509/head
Allan Downey 3 years ago
parent 1724c7683f
commit b5b54bbe59

@ -1,4 +1,5 @@
# 上手使用
Cargo 会在安装 Rust 的时候一并进行安装,无需我们手动的操作执行,安装 Rust 参见[这里](https://course.rs/first-try/installation.html)。
在开始之前,先来明确一个名词: `Package`,由于 `Crate` 被翻译成包,因此 `Package` 再被翻译成包就很不合适,经过斟酌,我们决定翻译成项目,你也可以理解为工程、软件包,总之,在本书中`Package` 意味着项目,而项目也意味着 `Package`
@ -12,6 +13,7 @@ $ cargo new hello_world
这里我们使用 `cargo new` 创建一个新的项目 ,事实上该命令等价于 `cargo new hello_world --bin``bin` 是 `binary` 的简写,代表着二进制程序,由于 `--bin` 是默认参数,因此可以对其进行省略。
创建成功后,先来看看项目的基本目录结构长啥样:
```shell
$ cd hello_world
$ tree .
@ -24,6 +26,7 @@ $ tree .
```
这里有一个很显眼的文件 `Cargo.toml`,一看就知道它是 `Cargo` 使用的配置文件,这个关系类似于: `package.json``npm` 的配置文件。
```toml
[package]
name = "hello_world"
@ -36,6 +39,7 @@ edition = "2021"
以上就是 `Cargo.toml` 的全部内容,它被称之为清单( manifest ),包含了 `Cargo` 编译程序所需的所有元数据。
下面是 `src/main.rs` 的内容
```rust
fn main() {
println!("Hello, world!");
@ -43,12 +47,14 @@ fn main() {
```
可以看出 `Cargo` 还为我们自动生成了一个 `hello world` 程序,或者说[二进制包](https://course.rs/basic/crate-module/crate.html),对程序进行编译构建:
```shell
$ cargo build
Compiling hello_world v0.1.0 (file:///path/to/package/hello_world)
```
然后再运行编译出的二进制可执行文件:
```shell
$ ./target/debug/hello_world
Hello, world!
@ -57,6 +63,7 @@ Hello, world!
注意到路径中的 `debug` 了吗?它说明我们刚才的编译是 `Debug` 模式,该模式主要用于测试目的,如果想要进行生产编译,我们需要使用 `Release` 模式 `cargo build --release`,然后通过 `./target/release/hello_world` 运行。
除了上面的编译 + 运行方式外,在日常开发中,我们还可以使用一个简单的命令直接运行:
```shell
$ cargo run
Fresh hello_world v0.1.0 (file:///path/to/package/hello_world)
@ -68,4 +75,5 @@ Hello, world!
> 如果你的程序在跑性能测试 benchmark一定要使用 `Relase` 模式,因为该模式下,程序会做大量性能优化
在快速了解 `Cargo` 的使用方式后,下面,我们将正式进入 Cargo 的学习之旅。
在快速了解 `Cargo` 的使用方式后,下面,我们将正式进入 Cargo 的学习之旅。

@ -1,19 +1,22 @@
# 构建( Build )缓存
`cargo build` 的结果会被放入项目根目录下的 `target` 文件夹中,当然,这个位置可以三种方式更改:设置 `CARGO_TARGET_DIR` [环境变量](https://doc.rust-lang.org/stable/cargo/reference/environment-variables.html)、[`build.target-dir`](https://course.rs/cargo/reference/configuration.html#配置文件概览) 配置项以及 `--target-dir` 命令行参数。
## target 目录结构
`target` 目录的结构取决于是否使用 `--target` 标志为特定的平台构建。
#### 不使用 --target
`--target` 标志没有指定,`Cargo` 会根据宿主机架构进行构建,构建结果会放入项目根目录下的 `target` 目录中,`target` 下每个子目录中包含了相应的 [`发布配置profile`](https://course.rs/cargo/reference/profiles.html) 的构建结果,例如 `release`、`debug` 是自带的`profile`,前者往往用于生产环境,因为会做大量的性能优化,而后者则用于开发环境,此时的编译效率和报错信息是最好的。
除此之外我们还可以定义自己想要的 `profile` ,例如用于测试环境的 `profile` `test`,用于预发环境的 `profile` `pre-prod` 等。
| 目录 | 描述 |
| --- | --- |
| `target/debug/` | 包含了 `dev` profile 的构建输出(`cargo build` 或 `cargo build --debug`) |
| `target/release` | `release` profile 的构建输出,`cargo build --release` |
| `target/foo/` | 自定义 `foo` profile 的构建输出,`cargo build --profile=foo`|
| 目录 | 描述 |
| ---------------- | ----------------------------------------------------------------------- |
| `target/debug/` | 包含了 `dev` profile 的构建输出(`cargo build` 或 `cargo build --debug`) |
| `target/release` | `release` profile 的构建输出,`cargo build --release` |
| `target/foo/` | 自定义 `foo` profile 的构建输出,`cargo build --profile=foo` |
出于历史原因:
@ -22,42 +25,44 @@
- 用户定义的 profile 存在同名的目录下
#### 使用 --target
当使用 `--target XXX` 为特定的平台编译后,输出会放在 `target/XXX/` 目录下:
| 目录 | 示例 |
| --- | --- |
| `target/<triple>/debug` | `target/thumbv7em-none-eabihf/debug/` |
| 目录 | 示例 |
| -------------------------- | --------------------------------------- |
| `target/<triple>/debug` | `target/thumbv7em-none-eabihf/debug/` |
| `target/<triple>/release/` | `target/thumbv7em-none-eabihf/release/` |
> **注意:**,当没有使用 `--target` 时,`Cargo` 会与构建脚本和过程宏一起共享你的依赖包,对于每个 `rustc` 命令调用而言,[`RUSTFLAGS`](https://course.rs/cargo/reference/configuration.html#配置文件概览) 也将被共享。
>
> 而使用 `--target`构建脚本、过程宏会针对宿主机的CPU架构进行各自构建且不会共享 `RUSTFLAGS`
> 而使用 `--target` 后,构建脚本、过程宏会针对宿主机的 CPU 架构进行各自构建,且不会共享 `RUSTFLAGS`
#### target 子目录说明
#### target子目录说明
在 profile 文件夹中(例如 `debug``release`),包含编译后的最终成果:
| 目录 | 描述 |
| --- | --- |
| `target/debug/` | 包含编译后的输出,例如二进制可执行文件、[库对象( library target )](https://course.rs/cargo/reference/cargo-target.html#库对象library) |
| `target/debug/examples/` | 包含[示例对象( example target )](https://course.rs/cargo/reference/cargo-target.html#示例对象examples) |
| 目录 | 描述 |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| `target/debug/` | 包含编译后的输出,例如二进制可执行文件、[库对象( library target )](https://course.rs/cargo/reference/cargo-target.html#库对象library) |
| `target/debug/examples/` | 包含[示例对象( example target )](https://course.rs/cargo/reference/cargo-target.html#示例对象examples) |
还有一些命令会在 `target` 下生成自己的独立目录:
| 目录 | 描述 |
| --- | --- |
| `target/doc/` | 包含通过 `cargo doc` 生成的文档 |
| `target/package/` | 包含 `cargo package``cargo publish` 生成的输出 |
| 目录 | 描述 |
| ----------------- | -------------------------------------------------- |
| `target/doc/` | 包含通过 `cargo doc` 生成的文档 |
| `target/package/` | 包含 `cargo package``cargo publish` 生成的输出 |
Cargo 还会创建几个用于构建过程的其它类型目录,它们的目录结构只应该被 Cargo 自身使用,因此可能会在未来发生变化:
| 目录 | 描述 |
| --- | --- |
| `target/debug/deps` | 依赖和其它输出成果 |
| `target/debug/incremental` | `rustc` [增量编译](https://course.rs/cargo/reference/profiles.html#incremental)的输出,该缓存可以用于提升后续的编译速度 |
| `target/debug/build/` | [构建脚本](https://course.rs/cargo/reference/build-script/intro.html)的输出 |
| 目录 | 描述 |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `target/debug/deps` | 依赖和其它输出成果 |
| `target/debug/incremental` | `rustc` [增量编译](https://course.rs/cargo/reference/profiles.html#incremental)的输出,该缓存可以用于提升后续的编译速度 |
| `target/debug/build/` | [构建脚本](https://course.rs/cargo/reference/build-script/intro.html)的输出 |
## 依赖信息文件
在每一个编译成果的旁边,都有一个依赖信息文件,文件后缀是 `.d`。该文件的语法类似于 `Makefile`,用于说明构建编译成果所需的所有依赖包。
该文件往往用于提供给外部的构建系统,这样它们就可以判断 `Cargo` 命令是否需要再次被执行。
@ -70,10 +75,10 @@ Cargo 还会创建几个用于构建过程的其它类型目录,它们的目
```
## 共享缓存
[sccache](https://github.com/mozilla/sccache) 是一个三方工具,可以用于在不同的工作空间中共享已经构建好的依赖包。
为了设置 `sccache`,首先需要使用 `cargo install sccache` 进行安装,然后在调用 `Cargo` 之前将 `RUSTC_WRAPPER` 环境变量设置为 `sccache`
- 如果用的 `bash`,可以将 `export RUSTC_WRAPPER=sccache` 添加到 `.bashrc`
- 也可以使用 [`build.rustc-wrapper`](https://course.rs/cargo/reference/configuration.html#配置文件概览) 配置项

@ -1,8 +1,11 @@
# Cargo缓存
# Cargo 缓存
Cargo 使用了缓存的方式提升构建效率当构建时Cargo 会将已下载的依赖包放在 `CARGO_HOME` 目录下,下面一起来看看。
## Cargo Home
默认情况下Cargo Home 所在的目录是 `$HOME/.cargo/`,例如在 `macos` ,对应的目录是:
```shell
$ echo $HOME/.cargo/
/Users/sunfei/.cargo/
@ -12,7 +15,6 @@ $ echo $HOME/.cargo/
> 注意! Cargo Home 目录的内部结构并没有稳定化,在未来可能会发生变化
## 文件
- `config.toml` 是 Cargo 的全局配置文件,具体请查看[这里](https://course.rs/cargo/reference/configuration.html)
@ -31,6 +33,7 @@ $ echo $HOME/.cargo/
- `registry/src`,若一个已下载的 `.crate` 档案被一个 `package` 所需要,该档案会被解压缩到 `registry/src` 文件夹下,最终 `rustc` 可以在其中找到所需的 `.rs` 文件
## 在 CI 时缓存 Cargo Home
为了避免持续集成时重复下载所有的包依赖,我们可以将 `$CARGO_HOME` 目录进行缓存,但缓存整个目录是效率低下的,原因是源文件可能会被缓存两次。
例如我们依赖一个包 `serde 1.0.92`,如果将整个 `$CACHE_HOME` 目录缓存,那么`serde` 的源文件就会被缓存两次:在 `registry/cache` 中的 `serde-1.0.92.crate` 以及 `registry/src` 下被解压缩的 `.rs` 文件。
@ -43,6 +46,7 @@ $ echo $HOME/.cargo/
- `git/db/`
## 清除缓存
理论上,我们可以手动移除缓存中的任何一部分,当后续有包需要时 `Cargo` 会尽可能去恢复这些资源:
- 解压缩 `registry/cache` 下的 `.crate` 档案
@ -52,7 +56,8 @@ $ echo $HOME/.cargo/
你也可以使用 [cargo-cache](https://crates.io/crates/cargo-cache) 包来选择性的清除 `cache` 中指定的部分,当然,它还可以用来查看缓存中的组件大小。
## 构建时卡住Blocking waiting for file lock ..
在开发过程中,或多或少我们都会碰到这种问题,例如你同时打开了 VSCode IDE 和终端,然后在 `Cargo.toml` 中刚添加了一个新的依赖。
在开发过程中,或多或少我们都会碰到这种问题,例如你同时打开了 VSCode IDE 和终端,然后在 `Cargo.toml` 中刚添加了一个新的依赖。
此时 IDE 会捕捉到这个修改然后自动去重新下载依赖(这个过程可能还会更新 `crates.io` 使用的索引列表),在此过程中, Cargo 会将相关信息写入到 `$HOME/.cargo/.package_cache` 下,并将其锁住。
@ -62,4 +67,5 @@ $ echo $HOME/.cargo/
- 既然下载慢,那就使用[国内的注册服务](https://course.rs/cargo/reference/specify-deps.html#从其它注册服务引入依赖包),不再使用 crates.io
- 耐心等待持有锁的用户构建完成
- 强行停止正在构建的进程,例如杀掉 IDE 使用的 rust-analyer 插件进程,然后删除 `$HOME/.cargo/.package_cache` 目录
- 强行停止正在构建的进程,例如杀掉 IDE 使用的 rust-analyer 插件进程,然后删除 `$HOME/.cargo/.package_cache` 目录

@ -1,4 +1,5 @@
# Cargo.toml vs Cargo.lock
`Cargo.toml``Cargo.lock``Cargo` 的两个元配置文件,但是它们拥有不同的目的:
- 前者从用户的角度出发来描述项目信息和依赖管理,因此它是由用户来编写
@ -7,6 +8,7 @@
它们的关系跟 `package.json``package-lock.json` 非常相似,从 JavaScript 过来的同学应该会比较好理解。
## 是否上传本地的 `Cargo.lock`
当本地开发时,`Cargo.lock` 自然是非常重要的,但是当你要把项目上传到 `Git` 时,例如 `Github`,那是否上传 `Cargo.lock` 就成了一个问题。
关于是否上传,有如下经验准则:
@ -25,7 +27,9 @@
还有个原因,在项目中,可能会有几个依赖库引用同一个三方库的同一个版本,那如果该三方库使用了 `Cargo.lock` 文件,那可能三方库的多个版本会被引入使用,这时就会造成版本冲突。换句话说,通过指定版本的方式引用一个依赖库是无法看到该依赖库的完整情况的,而只有终端的产品才会看到这些完整的情况。
## 假设没有 `Cargo.lock`
`Cargo.toml` 是一个清单文件( `manifest` )包含了我们 `package` 的描述元数据。例如,通过以下内容可以说明对另一个 `package` 的依赖 :
```rust
[package]
name = "hello_world"
@ -39,7 +43,8 @@ regex = { git = "https://github.com/rust-lang/regex.git" }
这种使用方式,其实就错失了包管理工具的最大的优点:版本管理。例如你在今天构建使用了版本 `A`,然后过了一段时间后,由于依赖包的升级,新的构建却使用了大更新版本 `B`,结果因为版本不兼容,导致了构建失败。
可以看出,确保依赖版本的确定性是非常重要的:
可以看出,确保依赖版本的确定性是非常重要的:
```rust
[dependencies]
regex = { git = "https://github.com/rust-lang/regex.git", rev = "9f9f693" }
@ -50,7 +55,9 @@ regex = { git = "https://github.com/rust-lang/regex.git", rev = "9f9f693" }
但是,这里还有一个问题:`rev` 需要手动的管理,你需要在每次更新包的时候都思考下 `SHA-1`,这显然非常麻烦。
## 当有了 `Cargo.lock`
当有了 `Cargo.lock` 后,我们无需手动追踪依赖库的 `rev``Cargo` 会自动帮我们完成,还是之前的清单:
```rust
[package]
name = "hello_world"
@ -61,6 +68,7 @@ regex = { git = "https://github.com/rust-lang/regex.git" }
```
第一次构建时,`Cargo` 依然会拉取最新的 `master commit`,然后将以下信息写到 `Cargo.lock` 文件中:
```rust
[[package]]
name = "hello_world"
@ -78,10 +86,12 @@ source = "git+https://github.com/rust-lang/regex.git#9f9f693768c584971a4d53bc3c5
可以看出,其中包含了依赖库的准确 `rev` 信息。当未来再次构建时,只要项目中还有该 `Cargo.lock` 文件,那构建依然会拉取同一个版本的依赖库,并且再也无需我们手动去管理 `rev``SHA` 信息!
## 更新依赖
由于 `Cargo.lock` 会锁住依赖的版本,你需要通过手动的方式将依赖更新到新的版本:
```rust
$ cargo update # 更新所有依赖
$ cargo update -p regex # 只更新 “regex”
```
以上命令将使用新的版本信息重新生成 `Cargo.lock` ,需要注意的是 `cargo update -p regex` 传递的参数实际上是一个 `Package ID` `regex` 只是一个简写形式。
以上命令将使用新的版本信息重新生成 `Cargo.lock` ,需要注意的是 `cargo update -p regex` 传递的参数实际上是一个 `Package ID` `regex` 只是一个简写形式。

@ -1,7 +1,9 @@
# 添加依赖
[`crates.io`](https://crates.io) 是 Rust 社区维护的中心化注册服务,用户可以在其中寻找和下载所需的包。对于 `cargo` 来说,默认就是从这里下载依赖。
下面我们来添加一个 `time` 依赖包,若你的 `Cargo.toml` 文件中没有 `[dependencies]` 部分,就手动添加一个,并添加目标包名和版本号:
```toml
[dependencies]
time = "0.1.12"
@ -10,6 +12,7 @@ time = "0.1.12"
可以看到我们指定了 `time` 包的版本号 "0.1.12",关于版本号,实际上还有其它的指定方式,具体参见[指定依赖项](https://course.rs/cargo/reference/specify-deps.html)章节。
如果想继续添加 `regexp` 包,只需在 `time` 包后面添加即可 :
```toml
[package]
name = "hello_world"
@ -22,6 +25,7 @@ regex = "0.1.41"
```
此时,再通过运行 `cargo build` 来重新构建,首先 `Cargo` 会获取新的依赖以及依赖的依赖, 接着对它们进行编译并更新 `Cargo.lock`:
```shell
$ cargo build
Updating crates.io index
@ -43,6 +47,7 @@ $ cargo build
`Cargo.lock` 中包含了我们项目使用的所有依赖的准确版本信息。这个非常重要,未来就算 `regexp` 的作者升级了该包,我们依然会下载 `Cargo.lock` 中的版本,而不是最新的版本,只有这样,才能保证项目依赖包不会莫名其妙的因为更新升级导致无法编译。 当然,你还可以使用 `cargo update` 来手动更新包的版本。
此时,就可以在 `src/main.rs` 中使用新引入的 `regexp` 包:
```rust
use regex::Regex;
@ -53,6 +58,7 @@ fn main() {
```
运行后输出:
```shell
$ cargo run
Running `target/hello_world`

@ -1,4 +1,5 @@
# 下载并构建Package
# 下载并构建 Package
如果看中 `Github` 上的某个开源 Rust 项目,那下载并构建它将是非常简单的。
```shell
@ -7,6 +8,7 @@ $ cd regex
```
如上所示,直接从 `github` 上克隆下来想要的项目,然后使用 `cargo build` 进行构建即可:
```shell
$ cargo build
Compiling regex v1.5.0 (file:///path/to/package/regex)
@ -14,4 +16,5 @@ $ cargo build
该命令将下载相关的依赖库,等下载成功后,再对 `package` 和下载的依赖进行一同的编译构建。
这就是包管理工具的强大之处,`cargo build` 搞定一切,而背后隐藏的复杂配置、参数你都无需关心。
这就是包管理工具的强大之处,`cargo build` 搞定一切,而背后隐藏的复杂配置、参数你都无需关心。

@ -1,2 +1,4 @@
# 使用手册
在本章中,我们将学习 `Cargo` 的详细使用方式,例如 `Package` 的创建与管理、依赖拉取、`Package` 结构描述等。
在本章中,我们将学习 `Cargo` 的详细使用方式,例如 `Package` 的创建与管理、依赖拉取、`Package` 结构描述等。

@ -1,5 +1,7 @@
# 标准的Package目录结构
# 标准的 Package 目录结构
一个典型的 `Package` 目录结构如下:
```shell
.
├── Cargo.lock
@ -35,13 +37,13 @@
- `Cargo.toml``Cargo.lock` 保存在 `package` 根目录下
- 源代码放在 `src` 目录下
- 默认的 `lib` 包根是 `src/lib.rs`
- 默认的二进制包根是 `src/main.rs`
- 默认的二进制包根是 `src/main.rs`
- 其它二进制包根放在 `src/bin/` 目录下
- 基准测试 benchmark 放在 `benches` 目录下
- 示例代码放在 `examples` 目录下
- 集成测试代码放在 `tests` 目录下
关于 Rust 中的包和模块,[之前的章节](https://course.rs/basic/crate-module/intro.html)有更详细的解释。
此外,`bin`、`tests`、`examples` 等目录路径都可以通过配置文件进行配置,它们被统一称之为 [Cargo Target](https://course.rs/cargo/reference/cargo-target.html)。
此外,`bin`、`tests`、`examples` 等目录路径都可以通过配置文件进行配置,它们被统一称之为 [Cargo Target](https://course.rs/cargo/reference/cargo-target.html)。

@ -1,4 +1,5 @@
# 测试和CI
# 测试和 CI
Cargo 可以通过 `cargo test` 命令运行项目中的测试文件:它会在 `src/` 底下的文件寻找单元测试,也会在 `tests/` 目录下寻找集成测试。
```rust
@ -19,6 +20,7 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
如果希望深入学习如何在 Rust 编写及运行测试,请查阅[该章节](https://course.rs/test/intro.html)。
## CI
持续集成是软件开发中异常重要的一环,大家应该都听说过 Jenkins它就是一个拥有悠久历史的持续集成工具。简单来说持续集成会定期拉取同一个项目中所有成员的相关代码对其进行自动化构建。
在没有持续集成前,首先开发者需要手动编译代码并运行单元测试、集成测试等基础测试,然后启动项目相关的所有服务,接着测试人员开始介入对整个项目进行回归测试、黑盒测试等系统化的测试,当测试通过后,最后再手动发布到指定的环境中运行,这个过程是非常冗长,且所有成员都需要同时参与的。
@ -26,10 +28,13 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
在有了持续集成后,只要编写好相应的编译、测试、发布配置文件,那持续集成平台会自动帮助我们完成整个相关的流程,期间无需任何人介入,高效且可靠。
#### Github Actions
关于如何使用 `Github Actions` 进行持续集成,在[之前的章节](https://course.rs/test/ci.html)已经有过详细的介绍,这里就不再赘述。
#### Travis CI
以下是 `Travis CI` 需要的一个简单的示例配置文件:
```yml
language: rust
rust:
@ -44,7 +49,9 @@ matrix:
以上配置将测试所有的 [Rust 发布版本](https://course.rs/appendix/rust-version.html),但是 `nightly` 版本的构建失败不会导致全局测试的失败,可以查看 [Travis CI Rust 文档](https://docs.travis-ci.com/user/languages/rust/) 获取更详细的说明。
#### Gitlab CI
以下是一个示例 `.gitlab-ci.yml` 文件:
```yml
stages:
- build

@ -1,7 +1,9 @@
# 为何会有Cargo
# 为何会有 Cargo
根据之前学习的知识Rust 有两种类型的包: 库包和二进制包,前者是我们经常使用的依赖包,用于被其它包所引入,而后者是一个应用服务,可以编译成二进制可执行文件进行运行。
包是通过 Rust 编译器 `rustc` 进行编译的:
```rust
$ rustc hello.rs
$ ./hello
@ -19,7 +21,8 @@ Hello, world!
正是因为这些原因,与其使用 `rustc` ,我们可以使用一个强大的包管理工具来解决问题:欢迎 `Cargo` 闪亮登场。
## Cargo
## Cargo
`Cargo` 解决了之前描述的所有问题,同时它保证了每次重复的构建都不会改变上一次构建的结果,这背后是通过完善且强大的依赖包版本管理来实现的。
总之,`Cargo` 为了实现目标,做了四件事:
@ -29,4 +32,5 @@ Hello, world!
- 调用 `rustc` (或其它编译器) 并使用的正确的参数来构建项目,例如 `cargo build`
- 引入一些惯例,让项目的使用更加简单
毫不夸张的说,得益于 `Cargo` 的标准化,只要你使用它构建过一个项目,那构建其它使用 `Cargo` 的项目,也将不存在任何困难。
毫不夸张的说,得益于 `Cargo` 的标准化,只要你使用它构建过一个项目,那构建其它使用 `Cargo` 的项目,也将不存在任何困难。

@ -1,8 +1,10 @@
# Cargo 使用指南
Rust 语言的名气之所以这么大,保守估计 `Cargo` 的贡献就占了三分之一。
`Cargo` 是包管理工具,可以用于依赖包的下载、编译、更新、分发等,与 `Cargo` 一样有名的还有 [`crates.io`](https://crates.io),它是社区提供的包注册中心:用户可以将自己的包发布到该注册中心,然后其它用户通过注册中心引入该包。
> 本章内容是基于 [Cargo Book](https://doc.rust-lang.org/stable/cargo/index.html) 翻译,并做了一些内容优化和目录组织上的调整
<img src="https://doc.rust-lang.org/stable/cargo/images/Cargo-Logo-Small.png" />
<img src="https://doc.rust-lang.org/stable/cargo/images/Cargo-Logo-Small.png" />

@ -1,4 +1,5 @@
# 构建脚本示例
下面我们通过一些例子来说明构建脚本该如何使用。社区中也提供了一些构建脚本的[常用功能](https://crates.io/keywords/build-dependencies),例如:
- [bindgen](https://crates.io/crates/bindgen), 自动生成 Rust -> C 的 FFI 绑定
@ -7,11 +8,12 @@
- [cmake](https://crates.io/crates/cmake), 运行 `cmake` 来构建一个本地库
- [autocfg](https://crates.io/crates/autocfg), [rustc_version](https://crates.io/crates/rustc_version), [version_check](https://crates.io/crates/version_check),这些包提供基于 `rustc` 的当前版本来实现条件编译的方法
## 代码生成
一些项目需要在编译开始前先生成一些代码,下面我们来看看如何在构建脚本中生成一个库调用。
先来看看项目的目录结构:
```shell
.
├── Cargo.toml
@ -23,6 +25,7 @@
```
`Cargo.toml` 内容如下:
```toml
# Cargo.toml
@ -32,6 +35,7 @@ version = "0.1.0"
```
接下来,再来看看构建脚本的内容:
```rust
// build.rs
@ -61,6 +65,7 @@ fn main() {
- `return-if-changed` 指令告诉 Cargo 只有在脚本内容发生变化时,才能重新编译和运行构建脚本。如果没有这一行,项目的任何文件发生变化都会导致 Cargo 重新编译运行该构建脚本
下面,我们来看看 `main.rs`
```rust
// src/main.rs
@ -76,9 +81,11 @@ fn main() {
例子虽然很简单,但是它清晰地告诉了我们该如何生成代码文件以及将这些代码文件纳入到编译中来,大家以后有需要只要回头看看即可。
## 构建本地库
有时,我们需要在项目中使用基于 C 或 C++ 的本地库,而这种使用场景恰恰是构建脚本非常擅长的。
例如,下面来看看该如何在 Rust 中调用 C 并打印 `Hello, World`。首先,来看看项目结构和 `Cargo.toml`:
```shell
.
├── Cargo.toml
@ -100,6 +107,7 @@ edition = "2021"
```
现在,我们还不会使用任何构建依赖,先来看看构建脚本:
```rust
// build.rs
@ -131,12 +139,14 @@ fn main() {
- 这些命令往往不会考虑交叉编译的问题,如果我们要为 Android 平台进行交叉编译,那么 `gcc` 很可能无法输出一个 ARM 的可执行文件
但是别怕,构建依赖 `[build-dependencies]` 解君忧:社区中已经有现成的解决方案,可以让这种任务得到更容易的解决。例如文章开头提到的 [`cc`](https://crates.io/crates/cc) 包。首先在 `Cargo.toml` 中为构建脚本引入 `cc` 依赖:
```toml
[build-dependencies]
cc = "1.0"
```
然后重写构建脚本使用 `cc` :
```rust
// build.rs
@ -157,10 +167,10 @@ fn main() {
- 其它环境变量,例如 `OPT_LEVEL`、`DEBUG` 等会自动帮我们处理
- 标准输出和 `OUT_DIR` 的位置也会被 `cc` 所处理
如上所示,与其在每个构建脚本中复制粘贴相同的代码,将尽可能多的功能通过构建依赖来完成是好得多的选择。
再回到例子中,我们来看看 `src` 下的项目文件:
```c
// src/hello.c
@ -185,14 +195,15 @@ fn main() {
至此,这个简单的例子已经完成,我们学到了该如何使用构建脚本来构建 C 代码,当然又一次被构建脚本和构建依赖的强大所震撼!但控制下情绪,因为构建脚本还能做到更多。
## 链接系统库
当一个 Rust 包想要链接一个本地系统库时,如何实现平台透明化,就成了一个难题。
例如,我们想使用在 Unix 系统中的 `zlib` 库,用于数据压缩的目的。实际上,社区中的 [`libz-sys`](https://crates.io/crates/libz-sys) 包已经这么做了,但是出于演示的目的,我们来看看该如何手动完成,当然,这里只是简化版的,想要看完整代码,见[这里](https://github.com/rust-lang/libz-sys)。
为了更简单的定位到目标库的位置,可以使用 [`pkg-config`](https://crates.io/crates/pkg-config) 包,该包使用系统提供的 `pkg-config` 工具来查询库的信息。它会自动告诉 Cargo 该如何链接到目标库。
先修改 `Cargo.toml`
```toml
# Cargo.toml
@ -209,6 +220,7 @@ pkg-config = "0.3.16"
这里的 `links = "z"` 用于告诉 Cargo 我们想要链接到 `libz` 库,在[下文](#使用其它-sys-包)还有更多的示例。
构建脚本也很简单:
```rust
// build.rs
@ -219,6 +231,7 @@ fn main() {
```
下面再在代码中使用:
```rust
// src/lib.rs
@ -238,6 +251,7 @@ fn test_crc32() {
```
代码很清晰,也很简洁,这里就不再过多介绍,运行 [`cargo build --vv`](https://course.rs/cargo/reference/build-script/intro.html#构建脚本的输出) 来看看部分结果( 系统中需要已经安装 `libz` 库)
```shell
[libz-sys 0.1.0] cargo:rustc-link-search=native=/usr/lib
[libz-sys 0.1.0] cargo:rustc-link-lib=z
@ -248,8 +262,8 @@ fn test_crc32() {
实际使用中,我们需要做的比上面的代码更多,例如 [`libz-sys`](https://github.com/rust-lang/libz-sys) 包会先检查环境变量 `LIBZ_SYS_STATIC` 或者 `static` feature然后基于源码去构建 `libz`,而不是直接去使用系统库。
## 使用其它 sys 包
本例中,一起来看看该如何使用 `libz-sys` 包中的 `zlib` 来创建一个 C 依赖库。
若你有一个依赖于 `zlib` 的库,那可以使用 `libz-sys` 来自动发现或构建该库。这个功能对于交叉编译非常有用,例如 Windows 下往往不会安装 `zlib`
@ -272,6 +286,7 @@ cc = "1.0.46"
```
通过包含 `libz-sys`,确保了最终只会使用一个 `libz` 库,并且给了我们在构建脚本中使用的途径:
```rust
// build.rs
@ -287,6 +302,7 @@ fn main() {
```
由于 `libz-sys` 帮我们完成了繁重的相关任务C 代码只需要包含 `zlib` 的头文件即可,甚至于它还能在没有安装 `zlib` 的系统上找到头文件:
```c
// src/zuser.c
@ -296,14 +312,17 @@ fn main() {
```
## 条件编译
构建脚本可以通过发出 `rustc-cfg` 指令来开启编译时的条件检查。在本例中,一起来看看 [openssl](https://crates.io/crates/openssl) 包是如何支持多版本的 OpenSSL 库的。
`openssl-sys` 包对 OpenSSL 库进行了构建和链接,支持多个不同的实现(例如 LibreSSL )和多个不同的版本。它也使用了 `links` 配置,这样就可以给**其它构建脚本**传递所需的信息。例如 `version_number` ,包含了检测到的 OpenSSL 库的版本号信息。`openssl-sys` 自己的[构建脚本中](https://github.com/sfackler/rust-openssl/blob/dc72a8e2c429e46c275e528b61a733a66e7877fc/openssl-sys/build/main.rs#L216)有类似于如下的代码:
```rust
println!("cargo:version_number={:x}", openssl_version);
```
该指令将 `version_number` 的信息通过环境变量 `DEP_OPENSSL_VERSION_NUMBER` 的方式传递给直接使用 `openssl-sys` 的项目。例如 `openssl` 包提供了更高级的抽象接口,并且它使用了 `openssl-sys` 作为依赖。`openssl` 的构建脚本会通过环境变量读取 `openssl-sys` 提供的版本号的信息,然后使用该版本号来生成一些 [`cfg`](https://github.com/sfackler/rust-openssl/blob/dc72a8e2c429e46c275e528b61a733a66e7877fc/openssl/build.rs#L18-L36):
```rust
// (portion of build.rs)
@ -329,6 +348,7 @@ if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") {
```
这些 `cfg` 可以跟 [`cfg` 属性]() 或 [`cfg` 宏]()一起使用以实现条件编译。例如,在 OpenSSL 1.1 中引入了 SHA3 的支持,那么我们就可以指定只有当版本号为 1.1 时,才[包含并编译相关的代码](https://github.com/sfackler/rust-openssl/blob/dc72a8e2c429e46c275e528b61a733a66e7877fc/openssl/src/hash.rs#L67-L85):
```rust
// (portion of openssl crate)

@ -1,12 +1,15 @@
# 构建脚本( Build Scripts)
一些项目希望编译第三方的非 Rust 代码,例如 C 依赖库;一些希望链接本地或者基于源码构建的 C 依赖库;还有一些项目需要功能性的工具,例如在构建之间执行一些代码生成的工作等。
对于这些目标社区已经提供了一些工具来很好的解决Cargo 并不想替代它们但是为了给用户带来一些便利Cargo 提供了自定义构建脚本的方式,来帮助用户更好的解决类似的问题。
## build.rs
若要创建构建脚本,我们只需在项目的根目录下添加一个 `build.rs` 文件即可。这样一来, Cargo 就会先编译和执行该构建脚本,然后再去构建整个项目。
以下是一个非常简单的脚本示例:
```rust
fn main() {
// 以下代码告诉 Cargo ,一旦指定的文件 `src/hello.c` 发生了改变,就重新运行当前的构建脚本
@ -30,6 +33,7 @@ fn main() {
> Note: [`package.build`](https://course.rs/cargo/reference/manifest.html#build) 可以用于改变构建脚本的名称,或者直接禁用该功能
#### 构建脚本的生命期
在项目被构建之前Cargo 会将构建脚本编译成一个可执行文件,然后运行该文件并执行相应的任务。
在运行的过程中,**脚本可以使用之前 `println` 的方式跟 Cargo 进行通信**:通信内容是以 `cargo:` 开头的格式化字符串。
@ -39,22 +43,25 @@ fn main() {
在构建成本成功执行后,我们的项目就会开始进行编译。如果构建脚本的运行过程中发生错误,脚本应该通过返回一个非 0 码来立刻退出,在这种情况下,构建脚本的输出会被打印到终端中。
#### 构建脚本的输入
我们可以通过[环境变量](https://doc.rust-lang.org/stable/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts)的方式给构建脚本提供一些输入值,除此之外,构建脚本所在的当前目录也可以。
我们可以通过[环境变量](https://doc.rust-lang.org/stable/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts)的方式给构建脚本提供一些输入值,除此之外,构建脚本所在的当前目录也可以。
## 构建脚本的输出
构建脚本如果会产出文件,那么这些文件需要放在统一的目录中,该目录可以通过 [`OUT_DIR` 环境变量](https://doc.rust-lang.org/stable/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts)来指定,**构建脚本不应该修改该目录之外的任何文件!**
在之前提到过,构建脚本可以通过 `println!` 输出内容跟 Cargo 进行通信Cargo 会将每一行带有 `cargo:` 前缀的输出解析为一条指令,其它的输出内容会自动被忽略。
通过 `println!` 输出的内容在构建过程中默认是隐藏的,如果大家想要在终端中看到这些内容,你可以使用 `-vv` 来调用,以下 `build.rs`
```rust
fn main() {
println!("hello, build.rs");
}
```
将输出:
将输出:
```shell
$ cargo run -vv
[study_cargo 0.1.0] hello, build.rs
@ -64,26 +71,27 @@ $ cargo run -vv
以下是 Cargo 能识别的通信指令以及简介,如果大家希望深入了解每个命令,可以点击具体的链接查看官方文档的说明。
* [`cargo:rerun-if-changed=PATH`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rerun-if-changed) — 当指定路径的文件发生变化时Cargo 会重新运行脚本
* [`cargo:rerun-if-env-changed=VAR`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rerun-if-env-changed) — 当指定的环境变量发生变化时Cargo 会重新运行脚本告诉
* [`cargo:rustc-link-arg=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-arg) 将自定义的 flags 传给 linker用于后续的基准性能测试 benchmark、 可执行文件 binary,、`cdylib` 包、示例 和测试。
* [`cargo:rustc-link-arg-bin=BIN=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-arg-bin) 自定义的 flags 传给 linker用于可执行文件 `BIN`
* [`cargo:rustc-link-arg-bins=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-arg-bins) 自定义的 flags 传给 linker用于可执行文件
* [`cargo:rustc-link-arg-tests=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-arg-tests) 自定义的 flags 传给 linker用于测试
* [`cargo:rustc-link-arg-examples=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-arg-examples) 自定义的 flags 传给 linker用于示例
* [`cargo:rustc-link-arg-benches=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-arg-benches) 自定义的 flags 传给 linker用于基准性能测试 benchmark
* [`cargo:rustc-cdylib-link-arg=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-cdylib-link-arg) — 自定义的 flags 传给 linker用于 `cdylib`
* [`cargo:rustc-link-lib=[KIND=]NAME`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-lib) — 告知 Cargo 通过 `-l` 去链接一个指定的库,往往用于链接一个本地库,通过 FFI
* [`cargo:rustc-link-search=[KIND=]PATH`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-search) — 告知 Cargo 通过 `-L` 将一个目录添加到依赖库的搜索路径中
* [`cargo:rustc-flags=FLAGS`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-flags) — 将特定的 flags 传给编译器
* [`cargo:rustc-cfg=KEY[="VALUE"]`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-cfg) — 开启编译时 `cfg` 设置
* [`cargo:rustc-env=VAR=VALUE`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-env) — 设置一个环境变量
* [`cargo:warning=MESSAGE`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#cargo-warning) — 在终端打印一条 warning 信息
* [`cargo:KEY=VALUE`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#the-links-manifest-key) — `links` 脚本使用的元数据
- [`cargo:rerun-if-changed=PATH`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rerun-if-changed) — 当指定路径的文件发生变化时Cargo 会重新运行脚本
- [`cargo:rerun-if-env-changed=VAR`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rerun-if-env-changed) — 当指定的环境变量发生变化时Cargo 会重新运行脚本告诉
- [`cargo:rustc-link-arg=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-arg) 将自定义的 flags 传给 linker用于后续的基准性能测试 benchmark、 可执行文件 binary,、`cdylib` 包、示例 和测试。
- [`cargo:rustc-link-arg-bin=BIN=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-arg-bin) 自定义的 flags 传给 linker用于可执行文件 `BIN`
- [`cargo:rustc-link-arg-bins=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-arg-bins) 自定义的 flags 传给 linker用于可执行文件
- [`cargo:rustc-link-arg-tests=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-arg-tests) 自定义的 flags 传给 linker用于测试
- [`cargo:rustc-link-arg-examples=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-arg-examples) 自定义的 flags 传给 linker用于示例
- [`cargo:rustc-link-arg-benches=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-arg-benches) 自定义的 flags 传给 linker用于基准性能测试 benchmark
- [`cargo:rustc-cdylib-link-arg=FLAG`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-cdylib-link-arg) — 自定义的 flags 传给 linker用于 `cdylib`
- [`cargo:rustc-link-lib=[KIND=]NAME`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-lib) — 告知 Cargo 通过 `-l` 去链接一个指定的库,往往用于链接一个本地库,通过 FFI
- [`cargo:rustc-link-search=[KIND=]PATH`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-link-search) — 告知 Cargo 通过 `-L` 将一个目录添加到依赖库的搜索路径中
- [`cargo:rustc-flags=FLAGS`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-flags) — 将特定的 flags 传给编译器
- [`cargo:rustc-cfg=KEY[="VALUE"]`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-cfg) — 开启编译时 `cfg` 设置
- [`cargo:rustc-env=VAR=VALUE`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#rustc-env) — 设置一个环境变量
- [`cargo:warning=MESSAGE`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#cargo-warning) — 在终端打印一条 warning 信息
- [`cargo:KEY=VALUE`](https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#the-links-manifest-key) — `links` 脚本使用的元数据
## 构建脚本的依赖
构建脚本也可以引入其它基于 Cargo 的依赖包,只需要修改在 `Cargo.toml` 中添加以下内容:
```toml
[build-dependencies]
cc = "1.0.46"
@ -93,8 +101,8 @@ cc = "1.0.46"
**大家在引入依赖的时候,需要仔细考虑它会给编译时间、开源协议和维护性等方面带来什么样的影响**。如果你在 `[build-dependencies]``[dependencies]` 引入了同样的包,这种情况下 Cargo 也许会对依赖进行复用,也许不会,例如在交叉编译时,如果不会,那编译速度自然会受到不小的影响。
## links
`Cargo.toml` 中可以配置 `package.links` 选项,它的目的是告诉 Cargo 当前项目所链接的本地库,同时提供了一种方式可以在项目构建脚本之间传递元信息。
```toml
@ -112,6 +120,7 @@ Cargo 要求一个本地库最多只能被一个项目所链接,换而言之
需要注意的是,该元数据只能传给直接相关者,对于间接的,例如依赖的依赖,就无能为力了。
## 覆盖构建脚本
`Cargo.toml` 设置了 `links` 时, Cargo 就允许我们使用自定义库对现有的构建脚本进行覆盖。在 [Cargo 使用的配置文件](https://course.rs/cargo/reference/configuration.html)中添加以下内容:
```toml
@ -128,4 +137,4 @@ metadata_key2 = "value"
增加这个配置后,在未来,一旦我们的某个项目声明了它链接到 `foo` ,那项目的构建脚本将不会被编译和运行,替代的是这里的配置将被使用。
`warning`, `rerun-if-changed``rerun-if-env-changed` 这三个 key 在这里不应该被使用,就算用了也会被忽略。
`warning`, `rerun-if-changed``rerun-if-env-changed` 这三个 key 在这里不应该被使用,就算用了也会被忽略。

@ -1,15 +1,19 @@
# Cargo Target
**Cargo 项目中包含有一些对象,它们包含的源代码文件可以被编译成相应的包,这些对象被称之为 Cargo Target**。例如[之前章节](https://course.rs/cargo/guide/package-layout.html)提到的库对象 `Library` 、二进制对象 `Binary`、示例对象 `Examples`、测试对象 `Tests` 和 基准性能对象 `Benches` 都是 Cargo Target。
本章节我们一起来看看该如何在 `Cargo.toml` 清单中配置这些对象,当然,大部分时候都无需手动配置,因为默认的配置通常由项目目录的布局自动推断出来。
## 对象介绍
在开始讲解如何配置对象前,我们先来看看这些对象究竟是什么,估计还有些同学对此有些迷糊 :)
#### 库对象(Library)
库对象用于定义一个库,该库可以被其它的库或者可执行文件所链接。**该对象包含的默认文件名是 `src/lib.rs`,且默认情况下,库对象的名称[跟项目名是一致的](https://course.rs/basic/crate-module/crate.html#package)**
一个工程只能有一个库对象,因此也只能有一个 `src/lib.rs` 文件,以下是一种自定义配置:
```shell
# 一个简单的例子:在 Cargo.toml 中定制化库对象
[lib]
@ -18,6 +22,7 @@ bench = false
```
#### 二进制对象(Binaries)
二进制对象在被编译后可以生成可执行的文件,默认的文件名是 `src/main.rs`,二进制对象的名称跟项目名也是相同的。
大家应该还记得,一个项目拥有多个二进制文件,因此一个项目可以拥有多个二进制对象。当拥有多个对象时,对象的文件默认会被放在 `src/bin/` 目录下。
@ -25,6 +30,7 @@ bench = false
二进制对象可以使用库对象提供的公共 API也可以通过 `[dependencies]` 来引入外部的依赖库。
我们可以使用 `cargo run --bin <bin-name>` 的方式来运行指定的二进制对象,以下是二进制对象的配置示例:
```toml
# Example of customizing binaries in Cargo.toml.
[[bin]]
@ -38,11 +44,13 @@ required-features = ["frobnicate"]
```
#### 示例对象(Examples)
示例对象的文件在根目录下的 `examples` 目录中。既然是示例,自然是使用项目中的库对象的功能进行演示。示例对象编译后的文件会存储在 `target/debug/examples` 目录下。
如上所示,示例对象可以使用库对象的公共 API也可以通过 `[dependencies]` 来引入外部的依赖库。
默认情况下,示例对象都是可执行的二进制文件( 带有 `fn main()` 函数入口),毕竟例子是用来测试和演示我们的库对象,是用来运行的。而你完全可以将示例对象改成库的类型:
默认情况下,示例对象都是可执行的二进制文件( 带有 `fn main()` 函数入口),毕竟例子是用来测试和演示我们的库对象,是用来运行的。而你完全可以将示例对象改成库的类型:
```toml
[[example]]
name = "foo"
@ -56,6 +64,7 @@ crate-type = ["staticlib"]
最后,`cargo test` 命令默认会对示例对象进行编译,以防止示例代码因为长久没运行,导致严重过期以至于无法运行。
#### 测试对象(Tests)
测试对象的文件位于根目录下的 `tests` 目录中,如果大家还有印象的话,就知道该目录是[集成测试](https://course.rs/test/unit-integration-test.html#集成测试)所使用的。
当运行 `cargo test` 时,里面的每个文件都会被编译成独立的包,然后被执行。
@ -63,17 +72,19 @@ crate-type = ["staticlib"]
测试对象可以使用库对象提供的公共 API也可以通过 `[dependencies]` 来引入外部的依赖库。
#### 基准性能对象(Benches)
该对象的文件位于 `benches` 目录下,可以通过 `cargo bench` 命令来运行,关于基准测试,可以通过[这篇文章](https://course.rs/test/benchmark.html)了解更多。
## 配置一个对象
我们可以通过 `Cargo.toml` 中的 `[lib]`、`[[bin]]`、`[[example]]`、`[[test]]` 和 `[[bench]]` 部分对以上对象进行配置。
> 大家可能会疑惑 `[lib]``[[bin]]` 的写法为何不一致,原因是这种语法是 `TOML` 提供的[数组特性](https://toml.io/en/v1.0.0-rc.3#array-of-tables) `[[bin]]` 这种写法意味着我们可以在 Cargo.toml 中创建多个 `[[bin]]` ,每一个对应一个二进制文件
>
> 上文提到过,我们只能指定一个库对象,因此这里只能使用 `[lib]` 形式
由于它们的配置内容都是相似的,因此我们以 `[lib]` 为例来说明相应的配置项:
```toml
[lib]
name = "foo" # 对象名称: 库对象、`src/main.rs` 二进制对象的名称默认是项目名
@ -91,6 +102,7 @@ required-features = [] # 构建对象所需的 Cargo Features (N/A for lib).
```
#### name
对于库对象和默认的二进制对象( `src/main.rs `),默认的名称是项目的名称( `package.name` )。
对于其它类型的对象,默认是目录或文件名。
@ -98,29 +110,33 @@ required-features = [] # 构建对象所需的 Cargo Features (N/A for lib).
除了 `[lib]` 外,`name` 字段对于其他对象都是必须的。
#### proc-macro
该字段的使用方式在[过程宏章节](https://course.rs/advance/macro.html#定义过程宏)有详细的介绍。
#### edition
对使用的 Rust Edition 版本进行设置。
如果没有设置,则默认使用 `[package]` 中配置的 `package.edition`,通常来说,这个字段不应该被单独设置,只有在一些特殊场景中才可能用到:例如将一个大型项目逐步升级为新的 edition 版本。
#### crate-type
该字段定义了对象生成的[包类型](https://doc.rust-lang.org/stable/reference/linkage.html)。它是一个数组,因此为同一个对象指定多个包类型。
需要注意的是,只有库对象和示例对象可以被指定,因为其他的二进制、测试和基准测试对象只能是 `bin` 这个包类型。
默认的包类型如下:
| 对象 | 包类型 |
| --- | --- |
| 正常的库对象 | "lib" |
| 对象 | 包类型 |
| -------------- | ------------ |
| 正常的库对象 | "lib" |
| 过程宏的库对象 | "proc-macro" |
| 示例对象 | "bin" |
| 示例对象 | "bin" |
可用的选项包括 `bin`、`lib`、`rlib`、`dylib`、`cdylib`、`staticlib` 和 `proc-macro` ,如果大家想了解更多,可以看下官方的[参考手册](https://doc.rust-lang.org/stable/reference/linkage.html)。
#### required-features
该字段用于指定在构建对象时所需的 [`features`](https://course.rs/cargo/reference/features.html) 列表。
该字段只对 `[[bin]]``[[bench]]``[[test]]``[[example]]` 有效,对于 `[lib]` 没有任何效果。
@ -138,9 +154,11 @@ required-features = ["postgres", "tools"]
```
## 对象自动发现
默认情况下,`Cargo` 会基于项目的[目录文件布局](https://course.rs/cargo/guide/package-layout.html)自动发现和确定对象,而之前的配置项则允许我们对其进行手动的配置修改(若项目布局跟标准的不一样时)。
而这种自动发现对象的设定可以通过以下配置来禁用:
```toml
[package]
# ...
@ -151,6 +169,7 @@ autobenches = false
```
只有在特定场景下才应该禁用自动对象发现。例如,你有一个模块想要命名为 `bin`,目录结构如下:
```shell
├── Cargo.toml
└── src
@ -162,6 +181,7 @@ autobenches = false
这在默认情况下会导致问题,因为 `Cargo` 会使用 `src/bin` 作为存放二进制对象的地方。
为了阻止这一点,可以设置 `autobins = false` :
```toml
├── Cargo.toml
└── src
@ -169,4 +189,3 @@ autobenches = false
   └── bin
      └── mod.rs
```

@ -1,9 +1,11 @@
# 通过config.toml对Cargo进行配置
Cargo 相关的配置有两种,第一种是对自身进行配置,第二种是对指定的项目进行配置,关于后者请查看 [Cargo.toml清单](https://course.rs/cargo/reference/manifest.html)。对于普通用户而言第二种才是我们最常使用的。
# 通过 config.toml 对 Cargo 进行配置
Cargo 相关的配置有两种,第一种是对自身进行配置,第二种是对指定的项目进行配置,关于后者请查看 [Cargo.toml 清单](https://course.rs/cargo/reference/manifest.html)。对于普通用户而言第二种才是我们最常使用的。
本文讲述的是如何对 Cargo 相关的工具进行配置,该配置中的部分内容可能会覆盖掉 `Cargo.toml` 中对应的部分,例如关于 `profile` 的内容。
## 层级结构
在前面我们已经见识过如何为 Cargo 进行全局配置:`$HOME/.cargo/config.toml`,事实上,还支持在一个 `package` 内对它进行配置。
总体原则是:`Cargo` 会顺着当前目录往上查找,直到找到目标配置文件。例如我们在目录 `/projects/foo/bar/baz` 下调用 Cargo 命令,那查找路径如下所示:
@ -14,8 +16,8 @@ Cargo 相关的配置有两种,第一种是对自身进行配置,第二种
- `/projects/.cargo/config.toml`
- `/.cargo/config.toml`
- `$CARGO_HOME/config.toml` 默认是 :
- Windows: `%USERPROFILE%\.cargo\config.toml`
- Unix: `$HOME/.cargo/config.toml`
- Windows: `%USERPROFILE%\.cargo\config.toml`
- Unix: `$HOME/.cargo/config.toml`
有了这种机制,我们既可以在全局中设置默认的配置,又可以每个包都设定独立的配置,甚至还能做版本控制。
@ -26,7 +28,9 @@ Cargo 相关的配置有两种,第一种是对自身进行配置,第二种
> 注意Cargo 还支持没有 `.toml` 后缀的 `.cargo/config` 文件。对于 `.toml` 的支持是从 Rust 1.39 版本开始,同时也是目前最推荐的方式。**但若同时存在有后缀和无后缀的文件Cargo 将使用无后缀的!**
## 配置文件概览
下面是一个完整的配置文件,并对**常用的选项**进行了翻译,大家可以参考下:
```toml
paths = ["/path/to/override"] # 覆盖 `Cargo.toml` 中通过 path 引入的本地依赖
@ -92,26 +96,26 @@ offline = true # 不能访问网络
# Same keys as for [patch] in Cargo.toml
[profile.<name>] # profile 配置,详情见"如何在 Cargo.toml 中配置 profile" : https://course.rs/cargo/reference/profiles.html#profile设置
opt-level = 0
debug = true
split-debuginfo = '...'
debug-assertions = true
overflow-checks = true
lto = false
panic = 'unwind'
incremental = true
codegen-units = 16
rpath = false
[profile.<name>.build-override]
[profile.<name>.package.<name>]
opt-level = 0
debug = true
split-debuginfo = '...'
debug-assertions = true
overflow-checks = true
lto = false
panic = 'unwind'
incremental = true
codegen-units = 16
rpath = false
[profile.<name>.build-override]
[profile.<name>.package.<name>]
[registries.<name>] # 设置其它的注册服务: https://course.rs/cargo/reference/specify-deps.html#从其它注册服务引入依赖包
index = "…" # 注册服务索引列表的 URL
index = "…" # 注册服务索引列表的 URL
token = "…" # 连接注册服务所需的鉴权 token
[registry]
default = "…" # 默认的注册服务名称: crates.io
token = "…"
token = "…"
[source.<name>] # 注册服务源和替换source definition and replacement
replace-with = "…" # 使用给定的 source 来替换当前的 source例如使用科大源来替换crates.io源以提升国内的下载速度[source.crates-io] replace-with = 'ustc'
@ -150,12 +154,11 @@ progress.width = 80 # width of progress bar
```
## 环境变量
除了 `config.toml` 配置文件,我们还可以使用环境变量的方式对 Cargo 进行配置。
配置文件的中的 key `foo.bar` 对应的环境变量形式为 `CARGO_FOO_BAR`,其中的`.`、`-` 被转换成 `_`,且字母都变成大写的。例如,`target.x86_64-unknown-linux-gnu.runner` key 转换成环境变量后变成 `CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER`
就优先级而言环境变量是比配置文件更高的。除了上面的机制Cargo 还支持一些[预定义的环境变量](https://doc.rust-lang.org/stable/cargo/reference/environment-variables.html)。
> 官方 Cargo Book 中本文的内容还有[很多](https://doc.rust-lang.org/stable/cargo/reference/config.html#configuration-keys),但是剩余内容对于绝大多数用户都用不到,因此我们并没有涵盖其中。

@ -1,4 +1,5 @@
# 依赖覆盖
依赖覆盖对于本地开发来说,是很常见的,大部分原因都是我们希望在某个包发布到 `crates.io` 之前使用它,例如:
- 你正在同时开发一个包和一个项目,而后者依赖于前者,你希望能在能项目中对正在开发的包进行测试
@ -11,9 +12,11 @@
> 上一章节中我们讲了如果通过[多种引用方式](https://course.rs/cargo/reference/specify-deps/intro.html#多引用方式混合)来引入一个包,其实这也是一种依赖覆盖。
## 测试 bugfix 版本
假设我们有一个项目正在使用 [`uuid`](https://crates.io/crates/uuid) 依赖包,但是却不幸地发现了一个 bug由于这个 bug 影响了使用,没办法等到官方提交新版本,因此还是自己修复为好。
我们项目的 `Cargo.toml` 内容如下:
```toml
[package]
name = "my-library"
@ -24,11 +27,13 @@ uuid = "0.8.2"
```
为了修复 `bug`,首先需要将 `uuid` 的源码克隆到本地,笔者是克隆到和项目同级的目录下:
```shell
git clone https://github.com/uuid-rs/uuid
```
下面,修改项目的 `Cargo.toml` 添加以下内容以引入本地克隆的版本:
```toml
[patch.crates-io]
uuid = { path = "../uuid" }
@ -37,6 +42,7 @@ uuid = { path = "../uuid" }
这里我们使用自己修改过的 `patch` 来覆盖来自 `crates.io` 的版本,由于克隆下来的 `uuid` 目录和我们的项目同级,因此通过相对路径 "../uuid" 即可定位到。
在成功为 `uuuid` 打了本地补丁后,现在尝试在项目下运行 `cargo build`,但是却报错了,而且报错内容有一些看不太懂:
```shell
$ cargo build
Updating crates.io index
@ -49,22 +55,26 @@ version. This may also occur with an optional dependency that is not enabled.
具体原因比较复杂,但是仔细观察,会发现克隆下来的 `uuid` 的版本是 `v1.0.0-alpha.1` (在 `"../uuid/Cargo.toml"` 中可以查看),然后我们本地引入的 `uuid` 版本是 `0.8.2`,根据之前讲过的 `crates.io` 的[版本规则](https://course.rs/cargo/reference/specify-deps/intro.html#从-cratesio-引入依赖包),这两者是不兼容的,`0.8.2` 只能升级到 `0.8.z`,例如 `0.8.3`
既然如此,我们先将 "../uuid/Cargo.toml" 中的 `version = "1.0.0-alpha.1"` 修改为 `version = "0.8.3"` ,然后看看结果先:
既然如此,我们先将 "../uuid/Cargo.toml" 中的 `version = "1.0.0-alpha.1"` 修改为 `version = "0.8.3"` ,然后看看结果先:
```shell
$ cargo build
Updating crates.io index
Compiling uuid v0.8.3 (/Users/sunfei/development/rust/demos/uuid)
```
大家注意到最后一行了吗?我们成功使用本地的 `0.8.3` 版本的 `uuid` 作为最新的依赖,因此也侧面证明了,补丁 `patch` 的版本也必须遵循相应的版本兼容规则!
如果修改后还是有问题,大家可以试试以下命令,指定版本进行更新:
如果修改后还是有问题,大家可以试试以下命令,指定版本进行更新:
```shell
% cargo update -p uuid --precise 0.8.3
% cargo update -p uuid --precise 0.8.3
Updating crates.io index
Updating uuid v0.8.3 (/Users/sunfei/development/rust/demos/uuid) -> v0.8.3
```
修复 bug 后,我们可以提交 pr 给 `uuid`,一旦 pr 被合并到了 `master` 分支,你可以直接通过以下方式来使用补丁:
```shell
[patch.crates-io]
uuid = { git = 'https://github.com/uuid-rs/uuid' }
@ -73,11 +83,13 @@ uuid = { git = 'https://github.com/uuid-rs/uuid' }
等未来新的内容更新到 `crates.io` 后,大家就可以移除这个补丁,直接更新 `[dependencies]` 中的 `uuid` 版本即可!
## 使用未发布的小版本
还是 `uuid` 包,这次假设我们要为它新增一个特性,同时我们已经修改完毕,在本地测试过,并提交了相应的 pr下面一起来看看该如何在它发布到 `crates.io` 之前继续使用。
再做一个假设,对于 `uuid` 来说,目前 `crates.io` 上的版本是 `1.0.0`,在我们提交了 pr 并合并到 `master` 分支后,`master` 上的版本变成了 `1.0.1`,这意味着未来 `crates.io` 上的版本也将变成 `1.0.1`
为了使用新加的特性,同时当该包在未来发布到 `crates.io` 后,我们可以自动使用 `crates.io` 上的新版本,而无需再使用 `patch` 补丁,可以这样修改 `Cargo.toml`
```toml
[package]
name = "my-library"
@ -95,7 +107,9 @@ uuid = { git = 'https://github.com/uuid-rs/uuid' }
现在,我们的项目是基于 `patch` 版本的 `uuid` 来构建,也就是从 `gihtub``master` 分支中拉取最新的 `commit` 来构建。一旦未来 `crates.io` 上有了 `1.0.1` 版本,那项目就会继续基于 `crates.io` 来构建,此时,`patch` 就可以删除了。
#### 间接使用 `patch`
现在假设项目 `A` 的依赖是 `B``uuid`,而 `B` 的依赖也是 `uuid`,此时我们可以让 `A``B` 都使用来自 `github``patch` 版本,配置如下:
```toml
[package]
name = "my-binary"
@ -111,8 +125,10 @@ uuid = { git = 'https://github.com/uuid-rs/uuid' }
如上所示,`patch` 不仅仅对于 `my-binary` 项目有用,对于 `my-binary` 的依赖 `my-library` 来说,一样可以间接生效。
#### 非crates.io的patch
#### 非 crates.io 的 patch
若我们想要覆盖的依赖并不是来自 `crates.io` ,就需要对 `[patch]` 做一些修改。例如依赖是 `git` 仓库,然后使用本地路径来覆盖它:
```shell
[patch."https://github.com/your/repository"]
my-library = { path = "../my-library/path" }
@ -120,9 +136,10 @@ my-library = { path = "../my-library/path" }
easy轻松搞定!
## 使用未发布的大版本
现在假设我们要发布一个大版本 `2.0.0`,与之前类似,可以将 `Cargo.toml` 修改如下:
```toml
[dependencies]
uuid = "2.0"
@ -134,7 +151,9 @@ uuid = { git = "https://github.com/uuid-rs/uuid", branch = "2.0.0" }
此时 `2.0` 版本在 `crates.io` 上还不存在,因此我们使用了 `patch` 版本且指定了 `branch = "2.0.0"`
#### 间接使用 `patch`
这里需要注意,**与之前的小版本不同,大版本的 `patch` 不会发生间接的传递!**,例如:
```shell
[package]
name = "my-binary"
@ -153,14 +172,16 @@ uuid = { git = 'https://github.com/uuid-rs/uuid', branch = '2.0.0' }
原因是大版本更新往往会带来破坏性的功能Rust 为了让我们平稳的升级,采用了滚动的方式:在依赖图中逐步推进更新,而不是一次性全部更新。
## 多版本[patch]
在之前章节,我们介绍过如何使用 `package key` 来[重命名依赖包](https://course.rs/cargo/reference/specify-deps/intro.html#在-cargotoml-中重命名依赖),现在来看看如何使用它同时引入多个 `patch`
假设,我们对 `serde` 有两个新的 `patch` 需求:
假设,我们对 `serde` 有两个新的 `patch` 需求:
- `serde` 官方解决了一个 `bug` 但是还没发布到 `crates.io`,我们想直接从 `git` 仓库的最新 `commit` 拉取版本 `1.*`
- `serde` 官方解决了一个 `bug` 但是还没发布到 `crates.io`,我们想直接从 `git` 仓库的最新 `commit` 拉取版本 `1.*`
- 我们自己为 `serde` 添加了新的功能,命名为 `2.0.0` 版本,并将该版本上传到自己的 `git` 仓库中
为了满足这两个 `patch`,可以使用如下内容的 `Cargo.toml`
```toml
[patch.crates-io]
serde = { git = 'https://github.com/serde-rs/serde' }
@ -172,21 +193,23 @@ serde2 = { git = 'https://github.com/example/serde', package = 'serde', branch =
这样,在代码中就可以分别通过 `serde``serde2` 引用不同版本的依赖库了。
## 通过[path]来覆盖依赖
有时我们只是临时性地对一个项目进行处理,因此并不想去修改它的 `Cargo.toml`。此时可以使用 `Cargo` 提供的路径覆盖方法: **注意,这个方法限制较多,如果可以,还是要使用 [patch]**。
`[patch]` 修改 `Cargo.toml` 不同,路径覆盖修改的是 `Cargo` 自身的[配置文件](https://course.rs/cargo/guide/cargo-cache.html#cargo-home) `$Home/.cargo/config.toml`:
`[patch]` 修改 `Cargo.toml` 不同,路径覆盖修改的是 `Cargo` 自身的[配置文件](https://course.rs/cargo/guide/cargo-cache.html#cargo-home) `$Home/.cargo/config.toml`:
```toml
paths = ["/path/to/uuid"]
```
`paths` 数组中的元素是一个包含 `Cargo.toml` 的目录(依赖包),在当前例子中,由于我们只有一个 `uuid`,因此只需要覆盖它即可。目标路径可以是相对的,也是绝对的,需要注意,如果是相对路径,那是相对包含 `.cargo``$Home` 来说的。
## 不推荐的[replace]
> `[replace]` 已经被标记为 `deprecated`,并将在未来被移除,请使用 `[patch]` 替代
虽然不建议使用,但是如果大家阅读其它项目时依然可能会碰到这种用法:
```toml
[replace]
"foo:0.1.0" = { git = 'https://github.com/example/foo' }
@ -194,4 +217,3 @@ paths = ["/path/to/uuid"]
```
语法看上去还是很清晰的,`[replace]` 中的每一个 `key` 都是 `Package ID` 格式,通过这种写法可以在依赖图中任意挑选一个节点进行覆盖。

@ -1,7 +1,9 @@
# Features示例
# Features 示例
以下我们一起来看看一些来自真实世界的示例。
### 最小化构建时间和文件大小
如果一些包的部分特性不再启用,就可以减少该包占用的大小以及编译时间:
- [`syn`](https://crates.io/crates/syn) 包可以用来解析 Rust 代码,由于它很受欢迎,大量的项目都在引用,因此它给出了[非常清晰的文档](https://docs.rs/syn/1.0.54/syn/#optional-features)关于如何最小化使用它包含的 `features`
@ -9,41 +11,48 @@
- [`winapi`](https://crates.io/crates/winapi) 拥有[众多 features](https://github.com/retep998/winapi-rs/blob/0.3.9/Cargo.toml#L25-L431),这些 `feature` 对用了各种 Windows API你可以只引入代码中用到的 API 所对应的 feature.
### 行为扩展
[`serde_json`](https://crates.io/crates/serde_json) 拥有一个 [`preserve_order` feature](https://github.com/serde-rs/json/blob/v1.0.60/Cargo.toml#L53-L56),可以用于在序列化时保留 JSON 键值队的顺序。同时,该 feature 还会启用一个可选依赖 [indexmap](https://crates.io/crates/indexmap)。
当这么做时,一定要小心不要破坏了 SemVer 的版本兼容性,也就是说:启用 feature 后,代码依然要能正常工作。
### no_std支持
### no_std 支持
一些包希望能同时支持 [`no_std`](https://doc.rust-lang.org/stable/reference/names/preludes.html#the-no_std-attribute) 和 `std` 环境,例如该包希望支持嵌入式系统或资源紧张的系统,且又希望能支持其它的平台,此时这种做法是非常有用的,因为标准库 `std` 会大幅增加编译出来的文件的大小,对于资源紧张的系统来说,`no_std` 才是最合适的。
[wasm-bindgen](https://crates.io/crates/wasm-bindgen) 定义了一个 [std feature](https://github.com/rustwasm/wasm-bindgen/blob/0.2.69/Cargo.toml#L25),它是[默认启用的](https://github.com/rustwasm/wasm-bindgen/blob/0.2.69/Cargo.toml#L25)。首先,在库的顶部,它[无条件的启用了 `no_std` 属性](https://github.com/rustwasm/wasm-bindgen/blob/0.2.69/src/lib.rs#L8),它可以确保 `std` 和 [`std prelude`](https://doc.rust-lang.org/stable/std/prelude/index.html) 不会自动引入到作用域中来。其次,在不同的地方([示例1](https://doc.rust-lang.org/stable/std/prelude/index.html)[示例2](https://github.com/rustwasm/wasm-bindgen/blob/0.2.69/src/lib.rs#L67-L75)),它通过 `#[cfg(feature = "std")]` 启用 `std` feature 来添加 `std` 标准库支持。
[wasm-bindgen](https://crates.io/crates/wasm-bindgen) 定义了一个 [std feature](https://github.com/rustwasm/wasm-bindgen/blob/0.2.69/Cargo.toml#L25),它是[默认启用的](https://github.com/rustwasm/wasm-bindgen/blob/0.2.69/Cargo.toml#L25)。首先,在库的顶部,它[无条件的启用了 `no_std` 属性](https://github.com/rustwasm/wasm-bindgen/blob/0.2.69/src/lib.rs#L8),它可以确保 `std` 和 [`std prelude`](https://doc.rust-lang.org/stable/std/prelude/index.html) 不会自动引入到作用域中来。其次,在不同的地方([示例 1](https://doc.rust-lang.org/stable/std/prelude/index.html)[示例 2](https://github.com/rustwasm/wasm-bindgen/blob/0.2.69/src/lib.rs#L67-L75)),它通过 `#[cfg(feature = "std")]` 启用 `std` feature 来添加 `std` 标准库支持。
## 对依赖库的 features 进行再导出
## 对依赖库的features进行再导出
从依赖库再导出 features 在有些场景中会相当有用,这样用户就可以通过依赖包的 features 来控制功能而不是自己去手动定义。
例如 [`regex`](https://crates.io/crates/regex) 将 [`regex_syntax`](https://github.com/rust-lang/regex/blob/1.4.2/regex-syntax/Cargo.toml#L17-L32) 包的 features 进行了[再导出](https://github.com/rust-lang/regex/blob/1.4.2/Cargo.toml#L65-L89),这样 `regex` 的用户无需知道 `regex_syntax` 包,但是依然可以访问后者包含的 features。
## feature优先级
## feature 优先级
一些包可能会拥有彼此互斥的 features(无法共存,上一章节中有讲到),其中一个办法就是为 feature 定义优先级,这样其中一个就会优于另一个被启用。
例如 [`log`](https://crates.io/crates/log) 包,它有[几个 features](https://github.com/rust-lang/log/blob/0.4.11/Cargo.toml#L29-L42) 可以用于在编译期选择最大的[日志级别](https://docs.rs/log/0.4.11/log/#compile-time-filters),这里,它就使用了 [`cfg-if`](https://crates.io/crates/cfg-if) 的方式来[设置优先级](https://github.com/rust-lang/log/blob/0.4.11/src/lib.rs#L1422-L1448)。一旦多个 `features` 被启用,那更高优先级的就会优先被启用。
## 过程宏包
一些包拥有过程宏,这些宏必须定义在一个独立的包中。但是不是所有的用户都需要过程宏的,因此也无需引入该包。
在这种情况下,将过程宏所在的包定义为可选依赖,是很不错的选择。这样做还有一个好处:有时过程宏的版本必须要跟父包进行同步,但是我们又不希望所有的用户都进行同步。
其中给一个例子就是 [serde](https://crates.io/crates/serde) ,它有一个 [derive](https://github.com/serde-rs/serde/blob/v1.0.118/serde/Cargo.toml#L34-L35) feature 可以启用 [serde_derive](https://crates.io/crates/serde_derive) 过程宏。由于 `serde_derive` 包跟 `serde` 的关系非常紧密,因此它使用了[版本相同的需求](https://github.com/serde-rs/serde/blob/v1.0.118/serde/Cargo.toml#L17)来保证两者的版本同步性。
## 只能用于nightly的feature
## 只能用于 nightly 的 feature
Rust 有些实验性的 API 或语言特性只能在 nightly 版本下使用,但某些使用了这些 API 的包并不想强制他们的用户也使用 `nightly` 版本,因此他们会通过 feature 的方式来控制。
若用户希望使用这些 API 时,需要启用相应的 feature ,而这些 feature 只能在 nightly 下使用。若用户不需要使用这些 API就无需开启 相应的 feature自然也不需要使用 nightly 版本。
例如 [`rand`](https://crates.io/crates/rand) 包有一个 [simd_support](https://github.com/rust-random/rand/blob/0.7.3/Cargo.toml#L40) feature 就只能在 nightly 下使用,若我们不使用该 feature则在 stable 下依然可以使用 `rand`
## 实验性feature
## 实验性 feature
有一些包会提前将一些实验性的 API 放出去,既然是实验性的,自然无法保证其稳定性。在这种情况下,通过会在文档中将相应的 features 标记为实验性,意味着它们在未来可能会发生大的改变(甚至 minor 版本都可能发生)。
其中一个例子是 [async-std](https://crates.io/crates/async-std) 包,它拥有一个 [unstable feature](https://github.com/async-rs/async-std/blob/v1.8.0/Cargo.toml#L38-L42),用来[标记一些新的 API](https://github.com/async-rs/async-std/blob/v1.8.0/src/macros.rs#L46),表示人们已经可以选择性的使用但是还没有准备好去依赖它。
其中一个例子是 [async-std](https://crates.io/crates/async-std) 包,它拥有一个 [unstable feature](https://github.com/async-rs/async-std/blob/v1.8.0/Cargo.toml#L38-L42),用来[标记一些新的 API](https://github.com/async-rs/async-std/blob/v1.8.0/src/macros.rs#L46),表示人们已经可以选择性的使用但是还没有准备好去依赖它。

@ -1,10 +1,13 @@
# 条件编译Features
# 条件编译 Features
`Cargo Feature` 是非常强大的机制,可以为大家提供[条件编译](https://doc.rust-lang.org/stable/reference/conditional-compilation.html)和可选依赖的高级特性。
## [features]
`Featuure` 可以通过 `Cargo.toml` 中的 `[features]` 部分来定义:其中每个 `feature` 通过列表的方式指定了它所能启用的其他 `feature` 或可选依赖。
假设我们有一个 2D 图像处理库,然后该库所支持的图片格式可以通过以下方式启用:
```toml
[features]
# 定义一个 feature : webp, 但它并没有启用其它 feature
@ -12,6 +15,7 @@ webp = []
```
当定义了 `webp` 后,我们就可以在代码中通过 [`cfg` 表达式](https://doc.rust-lang.org/stable/reference/conditional-compilation.html)来进行条件编译。例如项目中的 `lib.rs` 可以使用以下代码对 `webp` 模块进行条件引入:
```toml
#[cfg(feature = "webp")]
pub mod webp;
@ -22,6 +26,7 @@ pub mod webp;
`Cargo.toml` 中定义的 `feature` 会被 `Cargo` 通过命令行参数 `--cfg` 传给 `rustc`,最终由后者完成编译:`rustc --cfg ...`。若项目中的代码想要测试 `feature` 是否存在,可以使用 [`cfg` 属性](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#the-cfg-attribute)或 [`cfg` 宏](https://doc.rust-lang.org/stable/std/macro.cfg.html)。
之前我们提到了一个 `feature` 还可以开启其他 `feature`,举个例子,例如 `ICO` 图片格式包含 `BMP``PNG`,因此当 `ICO` 图片格式被启用后,它还得确保启用 `BMP``PNG` 格式:
```toml
[features]
bmp = []
@ -37,7 +42,9 @@ Feature 名称可以包含来自 [Unicode XID standard]() 定义的字母,允
但是我们**还是推荐按照 crates.io 的方式来设置 Feature 名称** : `crate.io` 要求名称只能由 ASCII 字母数字、`_`、`-` 或 `+` 组成。
## default feature
默认情况下,所有的 `feature` 都会被自动禁用,可以通过 `default` 来启用它们:
```toml
[features]
default = ["ico", "webp"]
@ -55,17 +62,20 @@ webp = []
> 当你要去改变某个依赖库的 `default` 启用的 feature 列表时(例如觉得该库引入的 feature 过多,导致最终编译出的文件过大),需要格外的小心,因为这可能会导致某些功能的缺失
## 可选依赖
当依赖被标记为 "可选 optional" 时,意味着它默认不会被编译。假设我们的 2D 图片处理库需要用到一个外部的包来处理 GIF 图片:
```toml
[dependencies]
gif = { version = "0.11.1", optional = true }
```
**这种可选依赖的写法会自动定义一个与依赖同名的 feature也就是 `gif` feature**,这样一来,当我们启用 `gif` feautre时该依赖库也会被自动引入并启用例如通过 `--feature gif` 的方式启用 feauture。
**这种可选依赖的写法会自动定义一个与依赖同名的 feature也就是 `gif` feature**,这样一来,当我们启用 `gif` feautre 时,该依赖库也会被自动引入并启用:例如通过 `--feature gif` 的方式启用 feauture。
> 注意:目前来说,`[fetuare]` 中定义的 feature 还不能与已引入的依赖库同名。但是在 `nightly` 中已经提供了实验性的功能用于改变这一点: [namespaced features](https://doc.rust-lang.org/stable/cargo/reference/unstable.html#namespaced-features)
当然,**我们还可以通过显式定义 feature 的方式来启用这些可选依赖库**,例如为了支持 `AVIF` 图片格式,我们需要引入两个依赖包,由于 `AVIF` 是通过 feature 引入的可选格式,因此它依赖的两个包也必须声明为可选的:
```toml
[dependencies]
ravif = { version = "0.6.3", optional = true }
@ -80,13 +90,16 @@ avif = ["ravif", "rgb"]
> 注意:我们之前也讲过条件引入依赖的方法,那就是使用[平台相关的依赖](https://course.rs/cargo/reference/specify-deps.html#根据平台引入依赖),与基于 feature 的可选依赖不同,它们是基于特定平台的可选依赖
## 依赖库自身的 feature
就像我们的项目可以定义 `feature` 一样,依赖库也可以定义它自己的 feature、也有需要启用的 feature 列表,当引入该依赖库时,我们可以通过以下方式为其启用相关的 features :
```toml
[dependencies]
serde = { version = "1.0.118", features = ["derive"] }
```
以上配置为 `serde` 依赖开启了 `derive` feature还可以通过 `default-features = false` 来禁用依赖库的 `default` feature :
```toml
[dependencies]
flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }
@ -96,9 +109,10 @@ flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }
> 注意:这种方式未必能成功禁用 `default`,原因是可能会有其它依赖也引入了 `flate2`,并且没有对 `default` 进行禁用,那此时 `default` 依然会被启用。
>
> 查看下文的 [feature同一化](#feature同一化) 获取更多信息
> 查看下文的 [feature 同一化](#feature同一化) 获取更多信息
除此之外,还能通过下面的方式来间接开启依赖库的 feature :
```toml
[dependencies]
jpeg-decoder = { version = "0.1.20", default-features = false }
@ -114,14 +128,16 @@ parallel = ["jpeg-decoder/rayon"]
>
> 在 `nightly` 版本中,可以对这种行为进行禁用:[weak dependency features]("package-name/feature-name")
## 通过命令行参数启用feature
## 通过命令行参数启用 feature
以下的命令行参数可以启用指定的 `feature` :
- `--features FEATURES`: 启用给出的 feature 列表,可以使用逗号或空格进行分隔,若你是在终端中使用,还需要加上双引号,例如 `--features "foo bar"`。 若在工作空间中构建多个 `package`,可以使用 `package-name/feature-name` 为特定的成员启用 features
- `--all-features`: 启用命令行上所选择的所有包的所有 features
- `--no-default-features`: 对选择的包禁用 `default` featue
## feature同一化
## feature 同一化
`feature` 只有在定义的包中才是唯一的,不同包之间的 `feature` 允许同名。因此,在一个包上启用 `feature` 不会导致另一个包的同名 `feature` 被误启用。
**当一个依赖被多个包所使用时,这些包对该依赖所设置的 `feature` 将被进行合并,这样才能确保该依赖只有一个拷贝存在,这个过程就被称之为同一化**。大家可以查看[这里](https://doc.rust-lang.org/stable/cargo/reference/resolver.html#features)了解下解析器如何对 feature 进行解析处理。
@ -130,7 +146,7 @@ parallel = ["jpeg-decoder/rayon"]
<img src="https://pic2.zhimg.com/80/v2-251973b0cc83f35cd6858bf21dd00ed6_1440w.png" />
由于这种不可控性,我们需要让 `启用feature = 添加特性` 这个等式成立,换而言之,**启用一个 feature 不应该导致某个功能被禁止**。这样才能的让多个包启用同一个依赖的不同features。
由于这种不可控性,我们需要让 `启用feature = 添加特性` 这个等式成立,换而言之,**启用一个 feature 不应该导致某个功能被禁止**。这样才能的让多个包启用同一个依赖的不同 features。
例如,如果我们想可选的支持 `no_std` 环境(不使用标准库),那么有两种做法:
@ -138,6 +154,7 @@ parallel = ["jpeg-decoder/rayon"]
- 默认代码使用非标准库的,当 `std` feature 启用时,才使用标准库的代码
前者就是功能削减,与之相对,后者是功能添加,根据之前的内容,我们应该选择后者的做法:
```rust
#![no_std]
@ -150,10 +167,12 @@ pub fn function_that_requires_std() {
}
```
#### 彼此互斥的feature
#### 彼此互斥的 feature
某极少数情况下features 之间可能会互相不兼容。我们应该避免这种设计,因为如果一旦这么设计了,那你可能需要修改依赖图的很多地方才能避免两个不兼容 feature 的同时启用。
如果实在没有办法,可以考虑增加一个编译错误来让报错更清晰:
```toml
#[cfg(all(feature = "foo", feature = "bar"))]
compile_error!("feature \"foo\" and feature \"bar\" cannot be enabled at the same time");
@ -166,10 +185,12 @@ compile_error!("feature \"foo\" and feature \"bar\" cannot be enabled at the sam
- 将某个功能分割到多个包中
- 当冲突时,设置 feature 优先级,[cfg-if](https://crates.io/crates/cfg-if) 包可以帮助我们写出更复杂的 `cfg` 表达式
#### 检视已解析的features
#### 检视已解析的 features
在复杂的依赖图中,如果想要了解不同的 features 是如何被多个包多启用的,这是相当困难的。好在 `cargo tree` 命令提供了几个选项可以帮组我们更好的检视哪些 features 被启用了:
`cargo tree -e features` ,该命令以依赖图的方式来展示已启用的 features包含了每个依赖包所启用的特性
```shell
$ cargo tree -e features
test_cargo v0.1.0 (/Users/sunfei/development/rust/demos/test_cargo)
@ -180,13 +201,15 @@ test_cargo v0.1.0 (/Users/sunfei/development/rust/demos/test_cargo)
```
`cargo tree -f "{p} {f}"` 命令会提供一个更加紧凑的视图:
```shell
% cargo tree -f "{p} {f}"
test_cargo v0.1.0 (/Users/sunfei/development/rust/demos/test_cargo)
test_cargo v0.1.0 (/Users/sunfei/development/rust/demos/test_cargo)
└── uuid v0.8.2 default,std
```
`cargo tree -e features -i foo`,该命令会显示 `features` 会如何"流入"指定的包 `foo` 中:
```shell
cargo tree -e features -i uuid
uuid v0.8.2
@ -201,8 +224,10 @@ uuid v0.8.2
大家可以查看官方的 `cargo tree` [文档](https://doc.rust-lang.org/stable/cargo/commands/cargo-tree.html)获取更加详细的使用信息。
## Feature解析器V2版本
## Feature 解析器 V2 版本
我们还能通过以下配置指定使用 V2 版本的解析器( [resolver](https://doc.rust-lang.org/stable/cargo/reference/resolver.html#resolver-versions) ):
```toml
[package]
name = "my-package"
@ -223,13 +248,15 @@ V2 版本的解析器可以在某些情况下避免 feature 同一化的发生
> 由于此部分内容可能只有极少数的用户需要,因此我们并没有对其进行扩展,如果大家希望了解更多关于 V2 的内容,可以查看[官方文档](https://doc.rust-lang.org/stable/cargo/reference/features.html#feature-resolver-version-2)
## 构建脚本
[构建脚本](https://course.rs/cargo/reference/build-script/intro.html)可以通过 `CARGO_FEATURE_<name>` 环境变量获取启用的 `feauture` 列表,其中 `<name>` 是 feature 的名称,该名称被转换成大全写字母,且 `-` 被转换为 `_`
[构建脚本](https://course.rs/cargo/reference/build-script/intro.html)可以通过 `CARGO_FEATURE_<name>` 环境变量获取启用的 `feauture` 列表,其中 `<name>` 是 feature 的名称,该名称被转换成大全写字母,且 `-` 被转换为 `_`
## required-features
该字段可以用于禁用特定的 Cargo Target当某个 feature 没有被启用时,查看[这里](https://course.rs/cargo/reference/cargo-target.html#required-features)获取更多信息。
## SemVer兼容性
## SemVer 兼容性
启用一个 feautre 不应该引入一个不兼容 SemVer 的改变。例如,启用的 feature 不应该改变现有的 API因为这会给用户造成不兼容的破坏性变更。 如果大家想知道哪些变化是兼容的,可以参见[官方文档](https://doc.rust-lang.org/stable/cargo/reference/semver.html)。
总之,在新增/移除 feature 或可选依赖时,你需要小心,因此这些可能会造成向后不兼容性。更多信息参见[这里](https://doc.rust-lang.org/stable/cargo/reference/semver.html#cargo),简单总结如下:
@ -242,8 +269,8 @@ V2 版本的解析器可以在某些情况下避免 feature 同一化的发生
- [将现有的公有代码放在某个 feature 之后](https://doc.rust-lang.org/stable/cargo/reference/semver.html#cargo-remove-opt-dep)
- [从 feature 列表中移除一个 feature](https://doc.rust-lang.org/stable/cargo/reference/semver.html#cargo-feature-remove-another)
## feature 文档和发现
## feature文档和发现
将你的项目支持的 feature 信息写入到文档中是非常好的选择:
- 我们可以通过在 `lib.rs` 的顶部添加[文档注释](https://course.rs/basic/comment.html#文档注释)的方式来实现。例如 `regex` 就是[这么做的](https://github.com/rust-lang/regex/blob/1.4.2/src/lib.rs#L488-L583)。
@ -254,8 +281,8 @@ V2 版本的解析器可以在某些情况下避免 feature 同一化的发生
当构建发布到 `docs.rs` 上的文档时,会使用 `Cargo.toml` 中的元数据来控制哪些 features 会被启用。查看 [docs.rs 文档](https://docs.rs/about/metadata)获取更多信息。
#### 如何发现features
#### 如何发现 features
若依赖库的文档中对其使用的 `features` 做了详细描述,那你会更容易知道他们使用了哪些 `features` 以及该如何使用。
当依赖库的文档没有相关信息时,你也可以通过源码仓库的 `Cargo.toml` 文件来获取,但是有些时候,使用这种方式来跟踪并获取全部相关的信息是相当困难的。

@ -1,2 +1,4 @@
# 进阶指南
进阶指南包含了 Cargo 的参考级内容,大家可以先看一遍了解下大概有什么,然后在后面需要时,再回来查询如何使用。
进阶指南包含了 Cargo 的参考级内容,大家可以先看一遍了解下大概有什么,然后在后面需要时,再回来查询如何使用。

@ -1,57 +1,60 @@
# Cargo.toml格式讲解
# Cargo.toml 格式讲解
`Cargo.toml` 又被称为清单( `manifest` ),文件格式是 `TOML`,每一个清单文件都由以下部分组成:
* [`cargo-features`](unstable.md) — 只能用于 `nightly`版本的 `feature`
* [`[package]`](#package) — 定义项目( `package` )的元信息
* [`name`](#name) — 名称
* [`version`](#version) — 版本
* [`authors`](#authors) — 开发作者
* [`edition`](#edition) — Rust edition.
* [`rust-version`](#rust-version) — 支持的最小化 Rust 版本
* [`description`](#description) — 描述
* [`documentation`](#documentation) — 文档 URL
* [`readme`](#readme) — README 文件的路径
* [`homepage`](#homepage) - 主页 URL
* [`repository`](#repository) — 源代码仓库的 URL
* [`license`](#license和license-file) — 开源协议 License.
* [`license-file`](#license和license-file) — License 文件的路径.
* [`keywords`](#keywords) — 项目的关键词
* [`categories`](#categories) — 项目分类
* [`workspace`](#workspace) — 工作空间 workspace 的路径
* [`build`](#build) — 构建脚本的路径
* [`links`](#links) — 本地链接库的名称
* [`exclude`](#exclude和include) — 发布时排除的文件
* [`include`](#exclude和include) — 发布时包含的文件
* [`publish`](#the-publish-field) — 用于阻止项目的发布
* [`metadata`](#metadata) — 额外的配置信息,用于提供给外部工具
* [`default-run`](#default-run) — [`cargo run`] 所使用的默认可执行文件( binary )
* [`autobins`](cargo-target.md#对象自动发现) — 禁止可执行文件的自动发现
* [`autoexamples`](cargo-target.md#对象自动发现) — 禁止示例文件的自动发现
* [`autotests`](cargo-target.md#对象自动发现) — 禁止测试文件的自动发现
* [`autobenches`](cargo-target.md#对象自动发现) — 禁止 bench 文件的自动发现
* [`resolver`](resolver.md#resolver-versions) — 设置依赖解析器( dependency resolver)
* Cargo Target 列表: (查看 [Target 配置](cargo-target.md#Target配置) 获取详细设置)
* [`[lib]`](./cargo-target.md#库对象library) — Library target 设置.
* [`[[bin]]`](cargo-target.md#二进制对象binaries) — Binary target 设置.
* [`[[example]]`](cargo-target.md#示例对象examples) — Example target 设置.
* [`[[test]]`](cargo-target.md#测试对象tests) — Test target 设置.
* [`[[bench]]`](cargo-target.md#基准性能对象benches) — Benchmark target 设置.
* Dependency tables:
* [`[dependencies]`](specify-deps.md) — 项目依赖包
* [`[dev-dependencies]`](specify-deps.md#dev-dependencies) — 用于 examples、tests 和 benchmarks 的依赖包
* [`[build-dependencies]`](specify-deps.md#build-dependencies) — 用于构建脚本的依赖包
* [`[target]`](specify-deps.md#根据平台引入依赖) — 平台特定的依赖包
* [`[badges]`](#badges) — 用于在注册服务(例如 crates.io ) 上显示项目的一些状态信息例如当前的维护状态活跃中、寻找维护者、deprecated
* [`[features]`](features.md) — `features` 可以用于条件编译
* [`[patch]`](deps-overriding.md) — 推荐使用的依赖覆盖方式
* [`[replace]`](deps-overriding.md#不推荐的replace) — 不推荐使用的依赖覆盖方式 (deprecated).
* [`[profile]`](profiles.md) — 编译器设置和优化
* [`[workspace]`](workspaces.md) — 工作空间的定义
- [`cargo-features`](unstable.md) — 只能用于 `nightly`版本的 `feature`
- [`[package]`](#package) — 定义项目( `package` )的元信息
- [`name`](#name) — 名称
- [`version`](#version) — 版本
- [`authors`](#authors) — 开发作者
- [`edition`](#edition) — Rust edition.
- [`rust-version`](#rust-version) — 支持的最小化 Rust 版本
- [`description`](#description) — 描述
- [`documentation`](#documentation) — 文档 URL
- [`readme`](#readme) — README 文件的路径
- [`homepage`](#homepage) - 主页 URL
- [`repository`](#repository) — 源代码仓库的 URL
- [`license`](#license和license-file) — 开源协议 License.
- [`license-file`](#license和license-file) — License 文件的路径.
- [`keywords`](#keywords) — 项目的关键词
- [`categories`](#categories) — 项目分类
- [`workspace`](#workspace) — 工作空间 workspace 的路径
- [`build`](#build) — 构建脚本的路径
- [`links`](#links) — 本地链接库的名称
- [`exclude`](#exclude和include) — 发布时排除的文件
- [`include`](#exclude和include) — 发布时包含的文件
- [`publish`](#the-publish-field) — 用于阻止项目的发布
- [`metadata`](#metadata) — 额外的配置信息,用于提供给外部工具
- [`default-run`](#default-run) — [`cargo run`] 所使用的默认可执行文件( binary )
- [`autobins`](cargo-target.md#对象自动发现) — 禁止可执行文件的自动发现
- [`autoexamples`](cargo-target.md#对象自动发现) — 禁止示例文件的自动发现
- [`autotests`](cargo-target.md#对象自动发现) — 禁止测试文件的自动发现
- [`autobenches`](cargo-target.md#对象自动发现) — 禁止 bench 文件的自动发现
- [`resolver`](resolver.md#resolver-versions) — 设置依赖解析器( dependency resolver)
- Cargo Target 列表: (查看 [Target 配置](cargo-target.md#Target配置) 获取详细设置)
- [`[lib]`](./cargo-target.md#库对象library) — Library target 设置.
- [`[[bin]]`](cargo-target.md#二进制对象binaries) — Binary target 设置.
- [`[[example]]`](cargo-target.md#示例对象examples) — Example target 设置.
- [`[[test]]`](cargo-target.md#测试对象tests) — Test target 设置.
- [`[[bench]]`](cargo-target.md#基准性能对象benches) — Benchmark target 设置.
- Dependency tables:
- [`[dependencies]`](specify-deps.md) — 项目依赖包
- [`[dev-dependencies]`](specify-deps.md#dev-dependencies) — 用于 examples、tests 和 benchmarks 的依赖包
- [`[build-dependencies]`](specify-deps.md#build-dependencies) — 用于构建脚本的依赖包
- [`[target]`](specify-deps.md#根据平台引入依赖) — 平台特定的依赖包
- [`[badges]`](#badges) — 用于在注册服务(例如 crates.io ) 上显示项目的一些状态信息例如当前的维护状态活跃中、寻找维护者、deprecated
- [`[features]`](features.md) — `features` 可以用于条件编译
- [`[patch]`](deps-overriding.md) — 推荐使用的依赖覆盖方式
- [`[replace]`](deps-overriding.md#不推荐的replace) — 不推荐使用的依赖覆盖方式 (deprecated).
- [`[profile]`](profiles.md) — 编译器设置和优化
- [`[workspace]`](workspaces.md) — 工作空间的定义
下面,我们将对其中一些部分进行详细讲解。
## [package]
`Cargo.toml` 中第一个部分就是 `package`,用于设置项目的相关信息:
```toml
[package]
name = "hello_world" # the name of the package
@ -59,9 +62,10 @@ version = "0.1.0" # the current version, obeying semver
authors = ["Alice <a@example.com>", "Bob <b@example.com>"]
```
其中,只有 `name``version` 字段是**必须填写的**。当发布到注册服务时,可能会有额外的字段要求,具体参见[发布到crates.io](publishing-on-crates.io.md)。
其中,只有 `name``version` 字段是**必须填写的**。当发布到注册服务时,可能会有额外的字段要求,具体参见[发布到 crates.io](publishing-on-crates.io.md)。
#### name
项目名用于引用一个项目( `package` ),它有几个用途:
- 其它项目引用我们的 `package` 时,会使用该 `name`
@ -71,10 +75,11 @@ authors = ["Alice <a@example.com>", "Bob <b@example.com>"]
事实上,`name` 的限制不止如此,例如:
- **当使用 `cargo new``cargo init` 创建时**`name` 还会被施加额外的限制例如不能使用Rust 关键字名称作为 `name`
- **当使用 `cargo new``cargo init` 创建时**`name` 还会被施加额外的限制,例如不能使用 Rust 关键字名称作为 `name`
- **如果要发布到 `crates.io` ,那还有更多的限制**: `name` 使用 `ASCII` 码,不能使用已经被使用的名称,例如 `uuid` 已经在 `crates.io` 上被使用,因此我们只能使用类如 `uuid_v1` 的名称,才能将项目发布到 `crates.io`
#### version
Cargo 使用了[语义化版本控制](https://semver.org)的概念,例如字符串 `"0.1.12"` 是一个 `semver` 格式的版本号,符合 `"x.y.z"` 的形式,其中 `x` 被称为主版本(major), `y` 被称为小版本 `minor` ,而 `z` 被称为 补丁 `patch`,可以看出从左到右,版本的影响范围逐步降低,补丁的更新是无关痛痒的,并不会造成 API 的兼容性被破坏。
使用该规则,你还需要遵循一些基本规则:
@ -99,6 +104,7 @@ authors = ["Sunfei <contact@im.dev>"]
> 警告:清单中的 `[package]` 部分一旦发布到 `crates.io` 就无法进行更改,因此对于已发布的包来说,`authors` 字段是无法修改的
#### edition
可选字段,用于指定项目所使用的 [Rust Edition](https://course.rs/appendix/rust-version.html)。
该配置将影响项目中的所有 `Cargo Target` 和包前者包含测试用例、benchmark、可执行文件、示例等。
@ -112,6 +118,7 @@ edition = '2021'
大多数时候,我们都无需手动指定,因为 `cargo new` 的时候,会自动帮我们添加。若 `edition` 配置不存在,那 `2015 Edition` 会被默认使用。
#### rust-version
可选字段,用于说明你的项目支持的最低 Rust 版本(编译器能顺利完成编译)。一旦你使用的 Rust 版本比这个字段设置的要低,`Cargo` 就会报错,然后告诉用户所需的最低版本。
该字段是在 Rust 1.56 引入的,若大家使用的 Rust 版本低于该版本,则该字段会被自动忽略时。
@ -130,6 +137,7 @@ rust-version = "1.56"
该字段将影响项目中的所有 `Cargo Target` 和包前者包含测试用例、benchmark、可执行文件、示例等。
## description
该字段是项目的简介,`crates.io` 会在项目首页使用该字段包含的内容,**不支持 `Markdown` 格式**。
```toml
@ -141,6 +149,7 @@ description = "A short description of my package"
> 注意: 若发布 `crates.io` ,则该字段是必须的
## documentation
该字段用于说明项目文档的地址,若没有设置,`crates.io` 会自动链接到 `docs.rs` 上的相应页面。
```toml
@ -150,6 +159,7 @@ documentation = "https://docs.rs/bitflags"
```
#### readme
`readme` 字段指向项目的 `Readme.md` 文件,该文件应该存在项目的根目录下(跟 `Cargo.toml` 同级),用于向用户描述项目的详细信息,支持 `Markdown` 格式。大家看到的 `crates.io` 上的项目首页就是基于该文件的内容进行渲染的。
```toml
@ -163,7 +173,9 @@ readme = "README.md"
你也可以通过将 `readme` 设置为 `false` 来禁止该功能,若设置为 `true` ,则默认值 `README.md` 将被使用。
#### homepage
该字段用于设置项目主页的 URL:
```toml
[package]
# ...
@ -171,19 +183,23 @@ homepage = "https://serde.rs/"
```
#### repository
设置项目的源代码仓库地址,例如 `github` 链接:
```toml
[package]
# ...
repository = "https://github.com/rust-lang/cargo/"
```
#### license和license-file
#### license 和 license-file
`license` 字段用于描述项目所遵循的开源协议。而 `license-file` 则用于指定包含开源协议的文件所在的路径(相对于 `Cargo.toml`)。
如果要发布到 `crates.io` ,则该协议必须是 [SPDX2.1 协议表达式](https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-badges-section)。同时 `license` 名称必须是来自于 [SPDX 协议列表 3.11](https://github.com/spdx/license-list-data/tree/v3.11)。
SPDX 只支持使用 `AND` 、`OR` 来组合多个开源协议:
```toml
[package]
# ...
@ -196,8 +212,8 @@ license = "MIT OR Apache-2.0"
- `LGPL-2.1-only AND MIT AND BSD-2-Clause`
- `GPL-2.0-or-later WITH Bison-exception-2.2`
**若项目使用了非标准的协议**,你可以通过指定 `license-file` 字段来替代 `license` 的使用:
```toml
[package]
# ...
@ -207,6 +223,7 @@ license-file = "LICENSE.txt"
> 注意crates.io 要求必须设置 `license``license-file`
#### keywords
该字段使用字符串数组的方式来指定项目的关键字列表,当用户在 `crates.io` 上搜索时,这些关键字可以提供索引的功能。
```toml
@ -218,15 +235,17 @@ keywords = ["gamedev", "graphics"]
> 注意:`crates.io` 最多只支持 5 个关键字,每个关键字都必须是合法的 `ASCII` 文本,且需要使用字母作为开头,只能包含字母、数字、`_` 和 `-`,最多支持 20 个字符长度
#### categories
`categories` 用于描述项目所属的类别:
```toml
categories = ["command-line-utilities", "development-tools::cargo-plugins"]
```
> 注意:`crates.io` 最多只支持 5 个类别,目前不支持用户随意自定义类别,你所使用的类别需要跟 [https://crates.io/category_slugs](https://crates.io/category_slugs) 上的类别**精准匹配**。
#### workspace
该字段用于配置当前项目所属的工作空间。
若没有设置,则将沿着文件目录向上寻找,直至找到第一个 设置了 `[workspace]` 的`Cargo.toml`。因此,当一个成员不在工作空间的子目录时,设置该字段将非常有用。
@ -245,7 +264,8 @@ workspace = "path/to/workspace/root"
若要了解工作空间的更多信息,请参见[这里](https://course.rs/cargo/reference/workspaces.html)。
#### build
`build` 用于指定位于项目根目录中的构建脚本,关于构建脚本的更多信息,可以阅读 [构建脚本](https://course.rs/cargo/reference/build-script/intro.html) 一章。
`build` 用于指定位于项目根目录中的构建脚本,关于构建脚本的更多信息,可以阅读 [构建脚本](https://course.rs/cargo/reference/build-script/intro.html) 一章。
```toml
[package]
@ -256,6 +276,7 @@ build = "build.rs"
还可以使用 `build = false` 来禁止构建脚本的自动检测。
#### links
用于指定项目链接的本地库的名称,更多的信息请看构建脚本章节的 [links](https://course.rs/cargo/reference/build-script/intro.html#links)
```toml
@ -264,7 +285,8 @@ build = "build.rs"
links = "foo"
```
#### exclude和include
#### exclude 和 include
这两个字段可以用于显式地指定想要包含在外或在内的文件列表,往往用于发布到注册服务时。你可以使用 `cargo package --list` 来检查哪些文件被包含在项目中。
```toml
@ -281,7 +303,7 @@ include = ["/src", "COPYRIGHT", "/examples", "!/examples/big_example"]
尽管大家可能没有指定 `include``exclude`,但是任然会有些规则自动被应用,一起来看看。
`include` 没有被指定,则以下文件将被排除在外:
`include` 没有被指定,则以下文件将被排除在外:
- 项目不是 git 仓库,则所有以 `.` 开头的隐藏文件会被排除
- 项目是 git 仓库,通过 `.gitignore` 配置的文件会被排除
@ -299,9 +321,10 @@ include = ["/src", "COPYRIGHT", "/examples", "!/examples/big_example"]
> 这两个字段很强大,但是对于生产实践而言,我们还是推荐通过 `.gitignore` 来控制,因为这样协作者更容易看懂。如果大家希望更深入的了解 `include/exclude`,可以参考下官方的 `Cargo` [文档](https://doc.rust-lang.org/stable/cargo/reference/manifest.html?search=#the-exclude-and-include-fields)
#### publish
该字段常常用于防止项目因为失误被发布到 `crates.io` 等注册服务上,例如如果希望项目在公司内部私有化,你应该设置:
```toml
[package]
# ...
@ -309,6 +332,7 @@ publish = false
```
也可以通过字符串数组的方式来指定允许发布到的注册服务名称:
```toml
[package]
# ...
@ -318,7 +342,9 @@ publish = ["some-registry-name"]
`publish` 数组中包含了一个注册服务名称,则 `cargo publish` 命令会使用该注册服务,除非你通过 `--registry` 来设定额外的规则。
#### metadata
Cargo 默认情况下会对 `Cargo.toml` 中未使用的 `key` 进行警告,以帮助大家提前发现风险。但是 `package.metadata` 并不在其中,因为它是由用户自定义的提供给外部工具的配置文件。例如:
```toml
[package]
name = "..."
@ -333,16 +359,20 @@ assets = "path/to/static"
与其相似的还有 `[workspace.metadata]`,都可以作为外部工具的配置信息来使用。
#### default-run
当大家使用 `cargo run` 来运行项目时,该命令会使用默认的二进制可执行文件作为程序启动入口。
我们可以通过 `default-run` 来修改默认的入口,例如现在有两个二进制文件 `src/bin/a.rs``src/bin/b.rs`,通过以下配置可以将入口设置为前者:
```toml
[package]
default-run = "a"
```
## [badges]
该部分用于指定项目当前的状态,该状态会展示在 `crates.io` 的项目主页中,例如以下配置可以设置项目的维护状态:
```toml
[badges]
# `maintenance` 是项目的当前维护状态,它可能会被其它注册服务所使用,但是目前还没有被 `crates.io` 使用: https://github.com/rust-lang/crates.io/issues/2437
@ -359,7 +389,10 @@ maintenance = { status = "..." }
```
## [dependencies]
在[之前章节](http://course.rs/cargo/reference/specify-deps.html)中,我们已经详细介绍过 `[dependencies]``[dev-dependencies]``[build-dependencies]`,这里就不再赘述。
## [profile.*]
该部分可以对编译器进行配置,例如 debug 和优化,在后续的[编译器优化](http://course.rs/cargo/reference/profiles.html)章节有详细介绍。
该部分可以对编译器进行配置,例如 debug 和优化,在后续的[编译器优化](http://course.rs/cargo/reference/profiles.html)章节有详细介绍。

@ -1 +1 @@
# 发布配置profile todo
# 发布配置 profile todo

@ -1,11 +1,13 @@
# 发布配置Profile
# 发布配置 Profile
细心的同学可能发现了迄今为止我们已经为 Cargo 引入了不少新的名词,而且这些名词有一个共同的特点,不容易或不适合翻译成中文,因为难以表达的很准确,例如 Cargo Target, Feature 等,这不现在又多了一个 Profile。
## 默认的 profile
Profile 其实是一种发布配置,例如它默认包含四种: `dev``release``test``bench`,正常情况下,我们无需去指定,`Cargo` 会根据我们使用的命令来自动进行选择
- 例如 `cargo build` 自动选择 `dev` profile`cargo test` 则是 `test` profile, 出于历史原因,这两个 profile 输出的结果都存放在项目根目录下的 `target/debug` 目录中,结果往往用于开发/测试环境
- 而 `cargo build --release` 自动选择 `release` profile并将输出结果存放在 `target/release` 目录中,结果往往用于生产环境
- 而 `cargo build --release` 自动选择 `release` profile并将输出结果存放在 `target/release` 目录中,结果往往用于生产环境
可以看出 Profile 跟 Nodejs 的 `dev``prod` 很像,都是通过不同的配置来为目标环境构建最终编译后的结果: `dev` 编译输出的结果用于开发环境,`prod` 则用于生产环境。
@ -14,6 +16,7 @@ Profile 其实是一种发布配置,例如它默认包含四种: `dev`、 `rel
> 初学者一个常见的错误,就是使用非 `release` profile 去测试性能,例如 `cargo run`,这种方式显然无法得到正确的结果,我们应该使用 `cargo run --release` 的方式测试性能
profile 可以通过 `Cargo.toml` 中的 `[profile]` 部分进行设置和改变:
```toml
[profile.dev]
opt-level = 1 # 使用稍高一些的优化级别最低是0最高是3
@ -26,12 +29,14 @@ overflow-checks = false # 关闭整数溢出检查
另外profile 还能在 Cargo 自身的配置文件中进行覆盖,总之,通过 `.cargo/config.toml` 或环境变量的方式所指定的 `profile` 配置会覆盖项目的 `Cargo.toml` 中相应的配置。
## 自定义profile
## 自定义 profile
除了默认的四种 profile我们还可以定义自己的。对于大公司来说这个可能会非常有用自定义的 profile 可以帮助我们建立更灵活的工作发布流和构建模型。
当定义 profile 时,你必须指定 `inherits` 用于说明当配置缺失时,该 profile 要从哪个 profile 那里继承配置。
例如,我们想在 release profile 的基础上增加 [LTO](#lto) 优化,那么可以在 `Cargo.toml` 中添加如下内容:
```toml
[profile.release-lto]
inherits = "release"
@ -39,6 +44,7 @@ lto = true
```
然后在构建时使用 `--profile` 来指定想要选择的自定义 profile
```shell
cargo build --profile release-lto
```
@ -53,11 +59,12 @@ cargo build --profile release-lto
- 默认使用 `release` `cargo install`, `cargo build --release`, `cargo run --release`
- 使用自定义 profile: `cargo build --profile release-lto`
## profile 设置
## profile设置
下面我们来看看 profile 中可以进行哪些优化设置。
#### opt-level
该字段用于控制 [`-C opt-level`](https://doc.rust-lang.org/stable/rustc/codegen-options/index.html#opt-level) 标志的优化级别。更高的优化级别往往意味着运行更快的代码,但是也意味着更慢的编译速度。
同时,更高的编译级别甚至会造成编译代码的改变和再排列,这会为 debug 带来更高的复杂度。
@ -78,6 +85,7 @@ cargo build --profile release-lto
如果想要了解更多,可以参考 [rustc 文档](https://doc.rust-lang.org/stable/rustc/profile-guided-optimization.html),这里有更高级的优化技巧。
#### debug
`debug` 控制 [`-C debuginfo`](https://doc.rust-lang.org/stable/rustc/codegen-options/index.html#debuginfo) 标志,而后者用于控制最终二进制文件输出的 `debug` 信息量。
支持的选项包括:
@ -87,22 +95,24 @@ cargo build --profile release-lto
- `2`: 完整的 debug 信息
#### split-debuginfo
`split-debuginfo` 控制 [-C split-debuginfo](https://doc.rust-lang.org/stable/rustc/codegen-options/index.html#split-debuginfo) 标志,用于决定输出的 debug 信息是存放在二进制可执行文件里还是邻近的文件中。
#### debug-assertions
该字段控制 [-C debug-assertions](https://doc.rust-lang.org/stable/rustc/codegen-options/index.html#debug-assertions) 标志,可以开启或关闭其中一个[条件编译](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#debug_assertions)选项: `cfg(debug_assertions)`
`debug-assertion` 会提供运行时的检查,该检查只能用于 `debug` 模式,原因是对于 `release` 来说,这种检查的成本较为高昂。
大家熟悉的 [`debug_assert!`](https://course.rs/test/assertion.html#debug_assert-系列) 宏也是通过该标志开启的。
支持的选项包括 :
- `true`: 开启
- `false`: 关闭
#### overflow-checks
用于控制 [-C overflow-checks](https://doc.rust-lang.org/stable/rustc/codegen-options/index.html#overflow-checks) 标志,该标志可以控制运行时的整数溢出行为。**当开启后,整数溢出会导致 `panic`**。
支持的选项包括 :
@ -111,18 +121,20 @@ cargo build --profile release-lto
- `false`: 关闭
#### lto
`lto` 用于控制 [`-C lto`](https://doc.rust-lang.org/stable/rustc/codegen-options/index.html#lto) 标志,而后者可以控制 LLVM 的[链接时优化( link time optimizations )](https://llvm.org/docs/LinkTimeOptimization.html)。通过对整个程序进行分析并以增加链接时间为代价LTO 可以生成更加优化的代码。
支持的选项包括:
- `false`: 只会对代码生成单元中的本地包进行 `thin LTO` 优化,若代码生成单元数为 1 或者 `opt-level` 为 0则不会进行任何 LTO 优化
- `true``fat`:对依赖图中的所有包进行 `fat LTO` 优化
- `thin`:对依赖图的所有包进行 [`thin LTO`](http://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html),相比 `fat` 来说,它仅牺牲了一点性能,但是换来了链接时间的可观减少
- `thin`:对依赖图的所有包进行 [`thin LTO`](http://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html),相比 `fat` 来说,它仅牺牲了一点性能,但是换来了链接时间的可观减少
- `off` 禁用 LTO
如果大家想了解跨语言 LTO可以看下 [-C linker-plugin-lto](https://doc.rust-lang.org/stable/rustc/codegen-options/index.html#linker-plugin-lto) 标志。
#### panic
`panic` 控制 [-C panic](https://doc.rust-lang.org/stable/cargo/reference/profiles.html#codegen-units) 标志,它可以控制 `panic` 策略的选择。
支持的选项包括:
@ -137,6 +149,7 @@ cargo build --profile release-lto
另外,当你使用 `"abort"` 策略且在执行测试时,由于上述的要求,除了测试代码外,所有的依赖库也会忽略该 `"abort"` 设置而使用 `"unwind"` 策略。
#### incremental
`incremental` 控制 [-C incremental](https://doc.rust-lang.org/stable/rustc/codegen-options/index.html#incremental) 标志,用于开启或关闭增量编译。开启增量编译时,`rustc` 会将必要的信息存放到硬盘中( `target` 目录中 ),当下次编译时,这些信息可以被复用以改善编译时间。
支持的选项包括:
@ -149,23 +162,27 @@ cargo build --profile release-lto
大家还可以通过[环境变量](https://doc.rust-lang.org/stable/cargo/reference/environment-variables.html) `CARGO_INCREMENTAL` 或 Cargo 配置 [build.incremental](https://doc.rust-lang.org/stable/cargo/reference/config.html#buildincremental) 在全局对 `incremental` 进行覆盖。
#### codegen-units
`codegen-units` 控制 [-C codegen-units](https://doc.rust-lang.org/stable/rustc/codegen-options/index.html#codegen-units) 标志,可以指定一个包会被分隔为多少个代码生成单元。**更多的代码生成单元会提升代码的并行编译速度,但是可能会降低运行速度。**
对于增量编译,默认值是 256非增量编译是 16。
#### r-path
用于控制 [-C rpath](https://doc.rust-lang.org/stable/rustc/codegen-options/index.html#rpath)标志,可以控制 [`rpath`](https://en.wikipedia.org/wiki/Rpath) 的启用与关闭。
`rpath` 代表硬编码到二进制可执行文件或库文件中的**运行时代码搜索(runtime search path)**,动态链接库的加载器就通过它来搜索所需的库。
## 默认 profile
## 默认profile
#### dev
`dev` profile 往往用于开发和 debug`cargo build` 或 `cargo run` 默认使用的就是 `dev` profile`cargo build --debug` 也是。
> 注意:`dev` profile 的结果并没有输出到 `target/dev` 同名目录下,而是 `target/debug` ,这是历史遗留问题
默认的 `dev` profile 设置如下:
```toml
[profile.dev]
opt-level = 0
@ -181,6 +198,7 @@ rpath = false
```
#### release
`release` 往往用于预发/生产环境或性能测试,以下命令使用的就是 `release` profile:
- `cargo build --release`
@ -188,6 +206,7 @@ rpath = false
- `cargo install`
默认的 `release` profile 设置如下:
```toml
[profile.release]
opt-level = 3
@ -203,15 +222,19 @@ rpath = false
```
#### test
该 profile 用于构建测试,它的设置是继承自 `dev`
#### bench
`bench` profile 用于构建基准测试 benchmark它的设计默认继承自 `release`
#### 构建本身依赖
默认情况下,所有的 profile 都不会对构建过程本身所需的依赖进行优化,构建过程本身包括构建脚本、过程宏。
默认的设置是:
```toml
[profile.dev.build-override]
opt-level = 0
@ -224,9 +247,10 @@ codegen-units = 256
如果是自定义 profile那它会自动从当前正在使用的 profile 继承相应的设置,但不会修改。
## 重写 profile
## 重写profile
我们还可以对特定的包使用的 profile 进行重写(override):
```toml
# `foo` package 将使用 -Copt-level=3 标志.
[profile.dev.package.foo]
@ -236,12 +260,14 @@ opt-level = 3
这里的 `package` 名称实际上是一个 [`Package ID`](https://course.rs/cargo/reference/package-id.html),因此我们还可以通过版本号来选择: `[profile.dev.package."foo:2.1.0"]`
如果要为所有依赖包重写(不包括工作空间的成员):
```toml
[profile.dev.package."*"]
opt-level = 2
```
为构建脚本、过程宏和它们的依赖重写:
```toml
[profile.dev.build-override]
opt-level = 3
@ -260,5 +286,3 @@ opt-level = 3
- Cargo 内置的默认值
重写无法使用 `panic`、`lto` 或 `rpath` 设置。

@ -1,10 +1,13 @@
# 发布到 crates.io
如果你想要把自己的开源项目分享给全世界,那最好的办法自然是 github。但如果是 Rust 的库,那除了发布到 github 外,我们还可以将其发布到 [crates.io](https://crates.io) 上,然后其它用户就可以很简单的对其进行引用。
> 注意:发布包到 `crates.io` 后,特定的版本无法被覆盖,要发布就必须使用新的版本号,代码也无法被删除!
## 首次发布之前
**首先,我们需要一个账号**:访问 crates.io 的[主页](https://crates.io),然后在右上角使用 Github 账户登陆,接着访问你的[账户设置](https://crates.io/settings/profile)页面,进入到 API Tokens 标签页下,生成新的 Token并使用该 Token 在终端中进行登录:
```shell
$ cargo login abcdefghijklmnopqrstuvwxyz012345
```
@ -14,6 +17,7 @@ $ cargo login abcdefghijklmnopqrstuvwxyz012345
> 注意:你需要妥善保管好 API Token并且不要告诉任何人一旦泄漏请撤销( Revoke )并重新生成。
## 发布包之前
`crates.io` 上的**包名遵循先到先得**的方式:一旦你想要的包名已经被使用,那么你就得换一个不同的包名。
在发布之前,**确保** `Cargo.toml` 中以下字段已经被设置:
@ -30,6 +34,7 @@ $ cargo login abcdefghijklmnopqrstuvwxyz012345
如果你发布的是一个依赖库,那么你可能需要遵循相关的[命名规范](https://course.rs/practice/naming.html)和 [API Guidlines](https://rust-lang.github.io/api-guidelines/).
## 打包
下一步就是将你的项目进行打包,然后上传到 `crates.io`。为了实现这个目的,我们可以使用 `cargo publish` 命令,该命令执行了以下步骤:
1. 对项目进行一些验证
@ -45,11 +50,13 @@ $ cargo publish --dry-run
```
你可以在 `target/package` 目录下观察生成的 `.crate` 文件。例如,目前 `crates.io` 要求该文件的大小不能超过 10MB你可以通过手动检查该文件的大小来确保不会无意间打包进一些较大的资源文件比如测试数据、网站文档或生成的代码等。我们还可以使用以下命令来检查其中包含的文件:
```shell
$cargo package --list
```
当打包时Cargo 会自动根据版本控制系统的配置来忽略指定的文件,例如 `.gitignore`。除此之外,你还可以通过 [`exclude`](https://course.rs/cargo/reference/manifest.html#exclude和include) 来排除指定的文件:
```toml
[package]
# ...
@ -60,6 +67,7 @@ exclude = [
```
如果想要显式地将某些文件包含其中,可以使用 `include`,但是需要注意的是,这个 key 一旦设置,那 `exclude` 就将失效:
```toml
[package]
# ...
@ -70,7 +78,9 @@ include = [
```
## 上传包
准备好后,我们就可以正式来上传指定的包了,在根目录中运行:
```shell
$ cargo pulish
```
@ -78,6 +88,7 @@ $ cargo pulish
就是这么简单,恭喜你,完成了第一个包的发布!
## 发布已上传包的新版本
绝大多数时候,我们并不是在发布新包,而是发布已经上传过的包的新版本。
为了实现这一点,只需修改 `Cargo.toml` 中的 [`version`](https://course.rs/cargo/reference/manifest.html#version) 字段 ,但需要注意:**版本号需要遵循 `semver` 规则**。
@ -85,10 +96,13 @@ $ cargo pulish
然后再次使用 `cargo publish` 就可以上传新的版本了。
## 管理 crates.io 上的包
目前来说,管理包更多地是通过 `cargo` 命令而不是在线管理,下面是一些你可以使用的命令。
#### cargo yank
有的时候你会遇到发布的包版本实际上并不可用(例如语法错误,或者忘记包含一个文件等)对于这种情况Cargo 提供了 yank 命令:
```shell
$ cargo yank --vers 1.0.1
$ cargo yank --vers 1.0.1 --undo
@ -99,13 +113,16 @@ $ cargo yank --vers 1.0.1 --undo
`yank` 能做到的就是让其它人不能再使用这个版本作为依赖,但是现存的依赖依然可以继续工作。`crates.io` 的一个主要目标就是作为一个不会随着时间变化的永久性包存档,但**删除某个版本显然违背了这个目标**。
#### cargo owner
一个包可能会有多个主要开发者,甚至维护者 maintainer 都会发生变更。目前来说,只有包的 owner 才能发布新的版本,但是一个 owner 可以指定其它的用户为 owner:
```shell
$ cargo owner --add github-handle
$ cargo owner --remove github-handle
$ cargo owner --add github:rust-lang:owners
$ cargo owner --remove github:rust-lang:owners
```
命令中使用的 ownerID 必须是 Github 用户名或 Team 名。
一旦一个用户 `B` 通过 `--add` 被加入到 `owner` 列表中他将拥有该包相关的所有权利。例如发布新版本、yank 一个版本,还能增加和移除 owner包含添加 `B` 为 owner 的 `A` 都可以被移除!
@ -115,4 +132,3 @@ $ cargo owner --remove github:rust-lang:owners
但是对于 Team 又有所不同,通过 `-add` 添加的 Github Team owner只拥有受限的权利。它们可以发布或 yank 某个版本,但是他们**不能添加或移除** owner总之Team 除了可以很方便的管理所有者分组的同时,还能防止一些未知的恶意。
如果大家在添加 team 时遇到问题,可以看看官方的[相关文档](https://doc.rust-lang.org/stable/cargo/reference/publishing.html#github-permissions),由于绝大多数人都无需此功能,因此这里不再详细展开。

@ -1,13 +1,15 @@
# 指定依赖项
我们的项目可以引用在 `crates.io``github` 上的依赖包,也可以引用存放在本地文件系统中的依赖包。
大家可能会想,直接从前两个引用即可,为何还提供了本地方式?可以设想下,如果你要有一个正处于开发中的包,然后需要在本地的另一个项目中引用测试,那是将该包先传到网上,然后再引用简单,还是直接从本地路径的方式引用简单呢?答案显然不言而喻。
本章节,我们一起来看看有哪些方式可以指定和引用三方依赖包。
## 从 `crates.io` 引入依赖包
默认设置下,`Cargo` 就从 [crates.io](https://crates.io) 上下载依赖包,只需要一个包名和版本号即可:
```toml
[dependencies]
time = "0.1.12"
@ -21,8 +23,8 @@ time = "0.1.12"
> npm 使用的就是 `semver` 版本号,从 JS 过来的同学应该非常熟悉。
#### `^` 指定版本
#### `^` 指定版本
与之前的 `"0.1.12"` 不同, `^` 可以指定一个版本号范围,**然后会使用该范围内的最大版本号来引用对应的包**。
只要新的版本号没有修改最左边的非零数字,那该版本号就在允许的版本号范围中。例如 `"^0.1.12"` 最左边的非零数字是 `1`,因此,只要新的版本号是 `"0.1.z"` 就可以落在范围内,而`0.2.0` 显然就没有落在范围内,因此通过 `"^0.1.12"` 引入的依赖包是无法被升级到 `0.2.0` 版本的。
@ -42,17 +44,20 @@ time = "0.1.12"
以上是更多的例子,**事实上,这个规则跟 `SemVer` 还有所不同**,因为对于 `SemVer` 而言,`0.x.y` 的版本是没有其它版本与其兼容的,而对于 Rust只要版本号 `0.x.y` 满足 `z>=y``x>0` 的条件,那它就能更新到 `0.x.z` 版本。
#### `~` 指定版本
`~` 指定了最小化版本 :
```rust
~1.2.3 := >=1.2.3, <1.3.0
~1.2 := >=1.2.0, <1.3.0
~1 := >=1.0.0, <2.0.0
```
#### `*` 通配符
#### `*` 通配符
这种方式允许将 `*` 所在的位置替换成任何数字:
```rust
* := >=0.0.0
1.* := >=1.0.0, <2.0.0
@ -62,7 +67,9 @@ time = "0.1.12"
不过 `crates.io` 并不允许我们只使用孤零零一个 `*` 来指定版本号 : `*`
#### 比较符
可以使用比较符的方式来指定一个版本号范围或一个精确的版本号:
```rust
>= 1.2.0
> 1
@ -71,6 +78,7 @@ time = "0.1.12"
```
同时还能使用比较符进行组合,并通过逗号分隔:
```rust
>= 1.2, < 1.5
```
@ -78,26 +86,31 @@ time = "0.1.12"
需要注意,以上的版本号规则仅仅针对 `crate.io` 和基于它搭建的注册服务(例如科大服务源) ,其它注册服务(例如 github )有自己相应的规则。
## 从其它注册服务引入依赖包
为了使用 `crates.io` 之外的注册服务,我们需要对 `$HOME/.cargo/config.toml` ($CARGO_HOME 下) 文件进行配置,添加新的服务提供商,有两种方式可以实现。
> 由于国内访问国外注册服务的不稳定性,我们可以使用[科大的注册服务](http://mirrors.ustc.edu.cn/help/crates.io-index.html)来提升下载速度,以下注册服务的链接都是科大的
**首先是在 `crates.io` 之外添加新的注册服务**,修改 `.cargo/config.toml` 添加以下内容:
```toml
[registries]
ustc = { index = "https://mirrors.ustc.edu.cn/crates.io-index/" }
```
对于这种方式,我们的项目的 `Cargo.toml` 中的依赖包引入方式也有所不同:
```toml
[dependencies]
time = { registry = "ustc" }
```
在重新配置后,初次构建可能要较久的时间,因为要下载更新 `ustc` 注册服务的索引文件,还挺大的...
注意,这一种使用方式最大的缺点就是在引用依赖包时要指定注册服务: `time = { registry = "ustc" }`
注意,这一种使用方式最大的缺点就是在引用依赖包时要指定注册服务: `time = { registry = "ustc" }`
**而第二种方式就不需要,因为它是直接使用新注册服务来替代默认的 `crates.io`**。
**而第二种方式就不需要,因为它是直接使用新注册服务来替代默认的 `crates.io`**。
```toml
[source.crates-io]
replace-with = 'ustc'
@ -110,16 +123,17 @@ registry = "git://mirrors.ustc.edu.cn/crates.io-index"
> 注意,如果你要发布包到 `crates.io` 上,那该包的依赖也必须在 `crates.io`
#### 引入 git 仓库作为依赖包
若要引入 git 仓库中的库作为依赖包,你至少需要提供一个仓库的地址:
```toml
[dependencies]
regex = { git = "https://github.com/rust-lang/regex" }
```
由于没有指定版本Cargo 会假定我们使用 `master``main` 分支的最新 `commit` 。你可以使用 `rev`、`tag` 或 `branch` 来指定想要拉取的版本。例如下面代码拉取了 `next` 分支上的最新 `commit`
```toml
[dependencies]
regex = { git = "https://github.com/rust-lang/regex", branch = "next" }
@ -134,13 +148,16 @@ regex = { git = "https://github.com/rust-lang/regex", branch = "next" }
如果访问的是私有仓库,你可能需要授权来访问该仓库,可以查看[这里](https://course.rs/cargo/git-auth.html)了解授权的方式。
#### 通过路径引入本地依赖包
Cargo 支持通过路径的方式来引入本地的依赖包:一般来说,本地依赖包都是同一个项目内的内部包,例如假设我们有一个 `hello_world` 项目( package ),现在在其根目录下新建一个包:
```shell
# 在 hello_world/ 目录下
$ cargo new hello_utils
```
新建的 `hello_utils` 文件夹跟 `src`、`Cargo.toml` 同级,现在修改 `Cargo.toml``hello_world` 项目引入新建的包:
```toml
[dependencies]
hello_utils = { path = "hello_utils" }
@ -159,7 +176,9 @@ hello_utils = { path = "hello_utils", version = "0.1.0" }
> 注意!使用 `path` 指定依赖的 package 将无法发布到 `crates.io`,除非 `path` 存在于 [[dev-dependencies]](#dev-dependencies) 中。当然,你还可以使用多种引用混合的方式来解决这个问题,下面将进行介绍
## 多引用方式混合
实际上,我们可以同时使用多种方式来引入同一个包,例如本地引入和 `crates.io` :
```toml
[dependencies]
# 本地使用时,通过 path 引入,
@ -176,7 +195,9 @@ smallvec = { git = "https://github.com/servo/rust-smallvec", version = "1.0" }
这种方式跟下章节将要讲述的依赖覆盖类似,但是前者只会应用到当前声明的依赖包上。
## 根据平台引入依赖
我们还可以根据特定的平台来引入依赖:
```toml
[target.'cfg(windows)'.dependencies]
winhttp = "0.4.0"
@ -191,7 +212,8 @@ native = { path = "native/i686" }
native = { path = "native/x86_64" }
```
此处的语法跟Rust 的 [`#[cfg]`](https://doc.rust-lang.org/stable/reference/conditional-compilation.html) 语法非常相像,因此我们还能使用逻辑操作符进行控制:
此处的语法跟 Rust 的 [`#[cfg]`](https://doc.rust-lang.org/stable/reference/conditional-compilation.html) 语法非常相像,因此我们还能使用逻辑操作符进行控制:
```toml
[target.'cfg(not(unix))'.dependencies]
openssl = "1.0.1"
@ -202,6 +224,7 @@ openssl = "1.0.1"
如果你想要知道 `cfg` 能够作用的目标,可以在终端中运行 `rustc --print=cfg` 进行查询。当然,你可以指定平台查询: `rustc --print=cfg --target=x86_64-pc-windows-msvc`,该命令将对 `64bit` 的 Windows 进行查询。
聪明的同学已经发现,这非常类似于条件依赖引入,那我们是不是可以根据自定义的条件来决定是否引入某个依赖呢?具体答案参见后续的 [feature](https://course.rs/cargo/reference/features.html) 章节。这里是一个简单的示例:
```toml
[dependencies]
foo = { version = "1.0", optional = true }
@ -214,6 +237,7 @@ fancy-feature = ["foo", "bar"]
但是需要注意的是,你如果妄图通过 `cfg(feature)`、`cfg(debug_assertions)`, `cfg(test)``cfg(proc_macro)` 的方式来条件引入依赖,那是不可行的。
`Cargo` 还允许通过下面的方式来引入平台特定的依赖:
```toml
[target.x86_64-pc-windows-gnu.dependencies]
winhttp = "0.4.0"
@ -223,7 +247,9 @@ openssl = "1.0.1"
```
## 自定义 target 引入
如果你在使用自定义的 `target` :例如 `--target bar.json`,那么可以通过下面方式来引入依赖:
```toml
[target.bar.dependencies]
winhttp = "0.4.0"
@ -236,7 +262,9 @@ native = { path = "native/i686" }
> 需要注意,这种使用方式在 `stable` 版本的 Rust 中无法被使用,建议大家如果没有特别的需求,还是使用之前提到的 feature 方式
## [dev-dependencies]
你还可以为项目添加只在测试时需要的依赖库,类似于 `package.json`( Nodejs )文件中的 `devDependencies`,可以在 `Cargo.toml` 中添加 `[dev-dependencies]` 来实现:
```toml
[dev-dependencies]
tempdir = "0.3"
@ -244,7 +272,8 @@ tempdir = "0.3"
这里的依赖只会在运行测试、示例和 benchmark 时才会被引入。并且,假设`A` 包引用了 `B`,而 `B` 通过 `[dev-dependencies]` 的方式引用了 `C` 包, 那 `A` 是不会引用 `C` 包的。
当然,我们还可以指定平台特定的测试依赖包:
当然,我们还可以指定平台特定的测试依赖包:
```toml
[target.'cfg(unix)'.dev-dependencies]
mio = "0.0.1"
@ -253,13 +282,16 @@ mio = "0.0.1"
> 注意,当发布包到 crates.io 时,`[dev-dependencies]` 中的依赖只有指定了 `version` 的才会被包含在发布包中。况且,再加上测试稳定性的考虑,我们建议为 `[dev-dependencies]` 中的包指定相应的版本号
## [build-dependencies]
我们还可以指定某些依赖仅用于构建脚本:
```toml
[build-dependencies]
cc = "1.0.3"
```
当然,平台特定的依然可以使用:
```toml
[target.'cfg(unix)'.build-dependencies]
cc = "1.0.3"
@ -268,7 +300,9 @@ cc = "1.0.3"
有一点需要注意:构建脚本(` build.rs` )和项目的正常代码是彼此独立,因此它们的依赖不能互通: 构建脚本无法使用 `[dependencies]``[dev-dependencies]` 中的依赖,而 `[build-dependencies]` 中的依赖也无法被构建脚本之外的代码所使用。
## 选择 features
如果你依赖的包提供了条件性的 `features`,你可以指定使用哪一个:
```toml
[dependencies.awesome]
version = "1.3.5"
@ -279,6 +313,7 @@ features = ["secure-password", "civet"]
更多的信息参见 [Features 章节](https://course.rs/cargo/reference/features.html)
## 在 Cargo.toml 中重命名依赖
如果你想要实现以下目标:
- 避免在 Rust 代码中使用 `use foo as bar`
@ -286,6 +321,7 @@ features = ["secure-password", "civet"]
- 依赖来自于不同注册服务的同名包
那可以使用 Cargo 提供的 `package key` :
```toml
[package]
name = "mypackage"
@ -298,19 +334,22 @@ baz = { version = "0.1", registry = "custom", package = "foo" }
```
此时,你的代码中可以使用三个包:
```rust
extern crate foo; // 来自 crates.io
extern crate bar; // 来自 git repository
extern crate baz; // 来自 registry `custom`
```
有趣的是,由于这三个 `package` 的名称都是 `foo`(在各自的 `Cargo.toml` 中定义),因此我们显式的通过 `package = "foo"` 的方式告诉 Cargo我们需要的就是这个 `foo package`,虽然它被重命名为 `bar``baz`
有趣的是,由于这三个 `package` 的名称都是 `foo`(在各自的 `Cargo.toml` 中定义),因此我们显式的通过 `package = "foo"` 的方式告诉 Cargo我们需要的就是这个 `foo package`,虽然它被重命名为 `bar``baz`
有一点需要注意,当使用可选依赖时,如果你将 `foo` 包重命名为 `bar` 包,那引用前者的 feature 时的路径名也要做相应的修改:
```toml
[dependencies]
bar = { version = "0.1", package = 'foo', optional = true }
[features]
log-debug = ['bar/log-debug'] # 若使用 'foo/log-debug' 会导致报错
```
```

@ -1,10 +1,13 @@
# 工作空间Workspace
# 工作空间 Workspace
一个工作空间是由多个 `package` 组成的集合,它们共享同一个 `Cargo.lock` 文件、输出目录和一些设置(例如 profiles : 编译器设置和优化)。组成工作空间的 `packages` 被称之为工作空间的成员。
## 工作空间的两种类型
工作空间有两种类型:`root package` 和虚拟清单( virtual manifest )。
#### 根package
#### 根 package
**若一个 `package``Cargo.toml` 包含了`[package]` 的同时又包含了 `[workspace]` 部分,则该 `package` 被称为工作空间的根 `package`**。
换而言之,一个工作空间的根( root )是该工作空间的 `Cargo.toml` 文件所在的目录。
@ -12,6 +15,7 @@
举个例子,我们现在有多个 `package`,它们的目录是嵌套关系,然后我们在最外层的 `package`,也就是最外层目录中的 `Cargo.toml` 中定义一个 `[workspace]`,此时这个最外层的 `package` 就是工作空间的根。
再举个例子,大名鼎鼎的 [ripgrep](https://github.com/BurntSushi/ripgrep/blob/master/Cargo.toml) 就在最外层的 `package` 中定义了 `[workspace]` :
```toml
[workspace]
members = [
@ -30,11 +34,13 @@ members = [
那么[最外层的目录](https://github.com/BurntSushi/ripgrep)就是 `ripgrep` 的工作空间的根。
#### 虚拟清单
若一个 `Cargo.toml``[workspace]` 但是没有 `[package]` 部分,则它是虚拟清单类型的工作空间。
**对于没有主 `package` 的场景或你希望将所有的 `package` 组织在单独的目录中时,这种方式就非常适合。**
例如 [rust-analyzer](https://github.com/rust-analyzer/rust-analyzer) 就是这样的项目,它的根目录中的 `Cargo.toml` 中并没有 `[package]`,说明该根目录不是一个 `package`,但是却有 `[workspacke]` :
```toml
[workspace]
members = ["xtask/", "lib/*", "crates/*"]
@ -44,6 +50,7 @@ exclude = ["crates/proc_macro_test/imp"]
结合 rust-analyzer 的目录布局可以看出,**该工作空间的所有成员 `package` 都在单独的目录中,因此这种方式很适合虚拟清单的工作空间。**
## 关键特性
工作空间的几个关键点在于:
- 所有的 `package` 共享同一个 `Cargo.lock` 文件,该文件位于工作空间的根目录中
@ -51,7 +58,9 @@ exclude = ["crates/proc_macro_test/imp"]
- 只有工作空间根目录的 `Cargo.toml` 才能包含 `[patch]`, `[replace]``[profile.*]`,而成员的 `Cargo.toml` 中的相应部分将被自动忽略
## [workspace]
`Cargo.toml` 中的 `[workspace]` 部分用于定义哪些 `packages` 属于工作空间的成员:
```toml
[workspace]
members = ["member1", "path/to/member2", "crates/*"]
@ -67,10 +76,11 @@ exclude = ["crates/foo", "path/to/other"]
`exclude` 可以将指定的目录排除在工作空间之外,例如还是上面的例子,`crates/*` 在包含了 `crates` 目录下的所有包后,又通过 `exclude``crates/foo``crates` 下的 `foo` 目录排除在外。
你也可以将一个空的 `[workspace]` 直接联合 `[package]` 使用,例如:
```toml
[package]
name = "hello"
version = "0.1.0"
name = "hello"
version = "0.1.0"
[workspace]
```
@ -81,6 +91,7 @@ version = "0.1.0"
- 所有通过 `path` 引入的本地依赖(位于工作空间目录下)
## 选择工作空间
选择工作空间有两种方式:`Cargo` 自动查找、手动指定 `package.workspace` 字段。
当位于工作空间的子目录中时,`Cargo` 会自动在该目录的父目录中寻找带有 `[workspace]` 定义的 `Cargo.toml`,然后再决定使用哪个工作空间。
@ -89,10 +100,12 @@ version = "0.1.0"
当成员不在工作空间的子目录下时,这种手动选择工作空间的方法就非常适用。毕竟 `Cargo` 的自动搜索是沿着父目录往上查找,而成员并不在工作空间的子目录下,这意味着顺着成员的父目录往上找是无法找到该工作空间的 `Cargo.toml` 的,此时就只能手动指定了。
## 选择package
## 选择 package
在工作空间中,`package` 相关的 `Cargo` 命令(例如 `cargo build` )可以使用 `-p``--package``--workspace` 命令行参数来指定想要操作的 `package`
若没有指定任何参数,则 `Cargo` 将使用当前工作目录的中的 `package` 。若工作目录是虚拟清单类型的工作空间,则该命令将作用在所有成员上(就好像是使用了 `--workspace` 命令行参数)。而 `default-members` 可以在命令行参数没有被提供时,手动指定操作的成员:
```toml
[workspace]
members = ["path/to/member1", "path/to/member2", "path/to/member3/*"]
@ -102,9 +115,11 @@ default-members = ["path/to/member2", "path/to/member3/foo"]
这样一来, `cargo build` 就不会应用到虚拟清单工作空间的所有成员,而是指定的成员上。
## workspace.metadata
与 [package.metadata](https://course.rs/cargo/reference/manifest.html#metadata) 非常类似,`workspace.metadata` 会被 `Cargo` 自动忽略,就算没有被使用也不会发出警告。
这个部分可以用于让工具在 `Cargo.toml` 中存储一些工作空间的配置元信息。例如:
```toml
[workspace]
members = ["member1", "member2"]
@ -114,4 +129,3 @@ root = "path/to/webproject"
tool = ["npm", "run", "build"]
# ...
```

Loading…
Cancel
Save