add contents to cargo chapters

pull/436/head
sunface 3 years ago
parent 118474c8dd
commit d3afd4c2ce

@ -131,11 +131,17 @@
- [下载并构建Package](cargo/guide/download-package.md)
- [添加依赖](cargo/guide/dependencies.md)
- [Package目录结构](cargo/guide/package-layout.md)
- [Cargo.toml vs Cargo.lock](cargo/guide/cargo-toml-lock.md)
- [测试和CI](cargo/guide/tests-ci.md)
- [Cargo构建缓存](cargo/guide/build-cache.md)
- [进阶参考 todo](cargo/reference/intro.md)
- [指定依赖项 todo](cargo/reference/specify-deps/intro.md)
- [依赖覆盖 todo](cargo/reference/specify-deps/overriding.md)
- [Cargo.toml格式讲解 todo](cargo/reference/manifest/intro.md)
- [修改默认的文件目录 todo](cargo/reference/manifest/cargo-target.md)
- [环境变量](cargo/reference/env.md)
- [Package ID 说明](cargo/reference/package-id.md)
- [易混淆概念解析](confonding/intro.md)
- [切片和切片引用](confonding/slice.md)

@ -17,6 +17,7 @@ fn main() {
虽然三种使用形式皆可,但是 Rust 内置的宏都有自己约定俗成的使用方式,例如 `vec![...]`、`assert_eq!(...)` 等。
在 Rust 中宏分为两大类:声明式宏 `macro_rules!` 和三种过程宏( *procedural macros* ):
- `#[derive]`,在之前多次见到的派生宏,可以为目标结构体或枚举派生指定的代码,例如 `Debug` 特征
- 类属性宏(Attribute-like macro),用于为目标添加自定义的属性
- 类函数宏(Function-like macro),看上去就像是函数调用

@ -0,0 +1,11 @@
# Cargo构建缓存
Cargo 使用了缓存的方式提升构建效率当构建时Cargo 会将已下载的依赖包放在 `CARGO_HOME` 目录下,下面一起来看看。
## Cargo Home
默认情况下Cargo Home 所在的目录是 `$HOME/.cargo/`,例如在 `macos` ,对应的目录是:
```shell
$ echo $HOME/.cargo/
/Users/sunfei/.cargo/
```
我们也可以通过修改 `CARGO_HOME` [环境变量](https://course.rs/cargo/reference/env.html)的方式来重新设定该目录的位置

@ -0,0 +1,87 @@
# Cargo.toml vs Cargo.lock
`Cargo.toml``Cargo.lock``Cargo` 的两个元配置文件,但是它们拥有不同的目的:
- 前者从用户的角度出发来描述项目信息和依赖管理,因此它是由用户来编写
- 后者包含了依赖的精确描述信息,它是由 `Cargo` 自行维护,因此不要去手动修改
它们的关系跟 `package.json``package-lock.json` 非常相似,从 Javascript 过来的同学应该会比较好理解。
## 是否上传本地的 `Cargo.lock`
当本地开发时,`Cargo.lock` 自然是非常重要的,但是当你要把项目上传到 `Git` 时,例如 `Github`,那是否上传 `Cargo.lock` 就成了一个问题。
关于是否上传,有如下经验准则:
- 从实践角度出发,如果你构建的是三方库类型的服务,请把 `Cargo.lock` 加入到 `.gitignore` 中。
- 若构建的是一个面向用户终端的产品,例如可以像命令行工具、应用程一样执行,那就把 `Cargo.lock` 上传到源代码目录中。
例如 [`axum`](https://github.com/tokio-rs/axum) 是 web 开发框架,它属于三方库类型的服务,因此源码目录中不应该出现 `Cargo.lock` 的身影,它的归宿是 `.gitignore`。而 [`ripgrep`](https://github.com/BurntSushi/ripgrep) 则恰恰相反,因为它是一个面向终端的产品,可以直接运行提供服务。
**那么问题来了,为何会有这种选择?**
原因是 `Cargo.lock` 会想尽描述上一次成功构建的各种信息环境状态、依赖、版本等等Cargo 可以使用它提供确定性的构建环境和流程,无论何时何地。这种特性对于终端服务是非常重要的:能确定、稳定的在用户环境中运行起来是终端服务最重要的特性之一。
而对于三方库来说,情况就有些不同。它不仅仅被库的开发者所使用,还会间接影响依赖链下游的使用者。用户引入了三方库是不会去看它的 `Cargo.lock` 信息的,也不应该受这个库的确定性运行条件所限制。
还有个原因,在项目中,可能会有几个依赖库引用同一个三方库的同一个版本,那如果该三方库使用了 `Cargo.lock` 文件,那可能三方库的多个版本会被引入使用,这时就会造成版本冲突。换句话说,通过指定版本的方式引用一个依赖库是无法看到该依赖库的完整情况的,而只有终端的产品才会看到这些完整的情况。
## 假设没有 `Cargo.lock`
`Cargo.toml` 是一个清单文件( `manifest` )包含了我们 `package` 的描述元数据。例如,通过以下内容可以说明对另一个 `package` 的依赖 :
```rust
[package]
name = "hello_world"
version = "0.1.0"
[dependencies]
regex = { git = "https://github.com/rust-lang/regex.git" }
```
可以看到,只有一个依赖,且该依赖的来源是 `Github` 上一个特定的仓库。由于我们没有指定任何版本信息,`Cargo` 会自动拉取该依赖库的最新版本( `master` 分支上的最新 `commit` )。
这种使用方式,其实就错失了包管理工具的最大的优点:版本管理。例如你在今天构建使用了版本 `A`,然后过了一段时间后,由于依赖包的升级,新的构建却使用了大更新版本 `B`,结果因为版本不兼容,导致了构建失败。
可以看出,确保依赖版本的确定性是非常重要的:
```rust
[dependencies]
regex = { git = "https://github.com/rust-lang/regex.git", rev = "9f9f693" }
```
这次,我们使用了指定 `rev` ( `revision` ) 的方式来构建,那么不管未来何时再次构建,使用的依赖库都会是该 `rev` ,而不是最新的 `commit`
但是,这里还有一个问题:`rev` 需要手动的管理,你需要在每次更新包的时候都思考下 `SHA-1`,这显然非常麻烦。
## 当有了 `Cargo.lock`
当有了 `Cargo.lock` 后,我们无需手动追踪依赖库的 `rev``Cargo` 会自动帮我们完成,还是之前的清单:
```rust
[package]
name = "hello_world"
version = "0.1.0"
[dependencies]
regex = { git = "https://github.com/rust-lang/regex.git" }
```
第一次构建时,`Cargo` 依然会拉取最新的 `master commit`,然后将以下信息写到 `Cargo.lock` 文件中:
```rust
[[package]]
name = "hello_world"
version = "0.1.0"
dependencies = [
"regex 1.5.0 (git+https://github.com/rust-lang/regex.git#9f9f693768c584971a4d53bc3c586c33ed3a6831)",
]
[[package]]
name = "regex"
version = "1.5.0"
source = "git+https://github.com/rust-lang/regex.git#9f9f693768c584971a4d53bc3c586c33ed3a6831"
```
可以看出,其中包含了依赖库的准确 `rev` 信息。当未来再次构建时,只要项目中还有该 `Cargo.lock` 文件,那构建依然会拉取同一个版本的依赖库,并且再也无需我们手动去管理 `rev``SHA` 信息!
## 更新依赖
由于 `Cargo.lock` 会锁住依赖的版本,你需要通过手动的方式将依赖更新到新的版本:
```rust
$ cargo update # 更新所有依赖
$ cargo update -p regex # 只更新 “regex”
```
以上命令将使用新的版本信息重新生成 `Cargo.lock` ,需要注意的是 `cargo update -p regex` 传递的参数实际上是一个 `Package ID` `regex` 只是一个简写形式。

@ -0,0 +1,68 @@
# 测试和CI
Cargo 可以通过 `cargo test` 命令运行项目中的测试文件:它会在 `src/` 底下的文件寻找单元测试,也会在 `tests/` 目录下寻找集成测试。
```rust
$ cargo test
Compiling regex v1.5.0 (https://github.com/rust-lang/regex.git#9f9f693)
Compiling hello_world v0.1.0 (file:///path/to/package/hello_world)
Running target/test/hello_world-9c2b65bbb79eabce
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
从上面结果可以看出,项目中实际上还没有任何测试代码。
事实上,除了单元测试、集成测试,`cargo test` 还会编译测试 `examples/` 下的示例文件以及[文档中的示例](https://course.rs/basic/comment.html#文档测试doc-test)。
如果希望深入学习如何在 Rust 编写及运行测试,请查阅[自动化测试章节](https://course.rs/test/intro.html)。
## CI
持续集成是软件开发中异常重要的一环,大家应该都听说过 Jenkins它就是一个拥有悠久历史的持续集成工具。简单来说持续集成会定期拉取同一个项目中所有成员的相关代码对其进行自动化构建。
在没有持续集成前,首先开发者需要手动编译代码并运行单元测试、集成测试等基础测试,然后启动项目相关的所有服务,接着测试人员开始介入对整个项目进行回归测试、黑盒测试等系统化的测试,当测试通过后,最后再手动发布到指定的环境中运行,这个过程是非常冗长,且所有成员都需要同时参与的。
在有了持续集成后,只要编写好相应的编译、测试、发布配置文件,那持续集成平台会自动帮助我们完成整个相关的流程,期间无需任何人介入,高效且可靠。
## Github Actions
关于如何使用 `Github Actions` 进行持续集成,在[之前的章节](https://course.rs/test/ci.html)已经有过详细的介绍,这里就不再赘述。
#### Travis CI
以下是 `Travis CI` 需要的一个简单的示例配置文件:
```yml
language: rust
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
```
以上配置将测试所有的 [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
rust-latest:
stage: build
image: rust:latest
script:
- cargo build --verbose
- cargo test --verbose
rust-nightly:
stage: build
image: rustlang/rust:nightly
script:
- cargo build --verbose
- cargo test --verbose
allow_failure: true
```
这里将测试 `stable``nightly` 发布版本,同样的,`nightly` 下的测试失败不会导致全局测试的失败。查看 [Gitlab CI 文档](https://docs.gitlab.com/ee/ci/yaml/index.html) 获取更详细的说明。
Loading…
Cancel
Save