6 构建依赖
6.1 根据场景正确配置dependencies、dev-dependencies、build-dependencies
在Rust的Cargo.toml中,有三种依赖可供配置,分别是dependencies、dev-dependencies、build-dependencies,需要根据他们的使用场景进行正确配置:
a) dependencies:Rust项目的主要依赖项类型,描述了Rust项目本身需要使用的库,在项目运行过程中必不可少,于项目编译时安装。
b) dev-dependencies(开发依赖):Rust项目在测试、示例和基准测试(benchmark)过程中所依赖的库,在项目运行过程中非必须。开发依赖关系不会传播到依赖于此包的其他包,同时在项目发布成crate
时,只有指定了版本(version)的依赖项才会包含在crate
中,在大多数场景下,发布包中不需要包含开发依赖。
示例:
Rust
[dev-dependencies]
tempdir = "0.3" //发布为crate时会包含在crate包中
c) build-dependencies(构建脚本依赖):Rust项目在构建脚本(build.rs)中所依赖的库,构建脚本(build.rs)和项目代码彼此独立,依赖不能互通,构建脚本无法访问dependencies和dev-dependencies中的依赖。
6.2 由Cargo工具维护Cargo.lock文件,避免手动编辑内容
Cargo.lock文件记录了项目所依赖的软件包的精确版本,目的是保证每次构建使用的软件包版本一致,使项目的构建过程更可靠和可重现。手动编辑会破坏这个目的,可能会导致构建失败、运行时错误或其他不可预测的行为。 因此,宜避免手动编辑Cargo.lock文件,而是通过Cargo在管理依赖或者构建时自动更新文件。推荐使用cargo add、cargo remove、cargo generate-lockfile等命令管理依赖项及Cargo.lock文件。
6.3 对于多包(crate)工程,使用Workspace工作空间进行工程级依赖管理
对于多包(crate)工程,Cargo提供了工作空间(workspaces)特性,它可以帮助我们管理多个相关的协同开发的包。使用Workspace工作空间进行工程级依赖管理将共享同一个Cargo.lock文件、输出目录和一些Cargo.toml设置,使结构清晰、维护方便。
使用工作空间(workspaces)时,推荐使用依赖继承(Rust 1.64.0以上版本支持),即工作空间(workspaces)的所有成员包使用相同的依赖,以避免依赖冲突以及方便审计外部依赖。设置方式为在定义工作空间(workspaces)的Cargo.toml中配置[workspace.dependencies]项,并同时在成员包的Cargo.toml中配置依赖项时指定workspace=true。
示例一:
Rust
# 定义workspaces的Cargo.toml
# [PROJECT_DIR]/Cargo.toml
[workspace]
members = ["bar"] #workspace中包含成员包bar
[workspace.dependencies]
cc = "1.0.73"
rand = "0.8.5"
regex = { version = "1.6.0", default-features = false, features = ["std"] }
示例二:
Rust
# 成员包bar的Cargo.toml
# [PROJECT_DIR]/bar/Cargo.toml
[package]
name = "bar"
version = "0.2.0"
#使用workspace中指定的依赖版本
[dependencies]
regex = { workspace = true, features = ["unicode"] }
[build-dependencies]
cc.workspace = true
[dev-dependencies]
rand.workspace = true
6.4 合理指定依赖软件的语义化版本(SemVer)范围
Rust软件包的版本号遵循语义化版本(SemVer)的约定,依赖软件同样如此,语义化版本(SemVer)为不同版本之间的兼容性建立了一个通用约定。依赖软件版本要求的语法如表1所示。
表1 依赖软件版本要求语法
约束(符号) | 样例 | 等效范围 | 描述(中文) |
---|---|---|---|
Caret(补注号) | 1.2.3 or ^1.2.3 | >=1.2.3, <2.0.0 | 允许SemVer兼容更新指定版本。新的版本允许更新的条件是,不修改最左边的非零数字(无论major,minor,patch)。 |
Tilde(波浪符) | ~1.2 | >=1.2.0,<1.3.0 | 指定具有更新最小版本的一定能力。如果指定major版本,minor版本和patch程序版本,或仅指定major版本和minor版本,则仅允许patch程序级别更改。如果仅指定major版本,则允许进行minor和patch级别更改. |
Wildcard(通配符) | 1.* | >=1.0.0,<2.0.0 | 允许任何通配符所在的版本 |
Equals(等于) | =1.2.3 | =1.2.3 | 只匹配指定的版本 |
Comparison(比较) | >1.1 | >=1.2.0 | Naive numeric comparison of specified digits. |
Compound(混合约束) | >=1.2,<1.5 | >=1.2.0,<1.5.0 | 必须同时满足的多个版本范围约束 |
一般情况下使用补注号来约束依赖版本(等同于不使用任何符号),同时需注意避免以下用法,以免版本范围避免版本范围过大或过小造成不兼容升级或无法升级。
a)单独使用通配符“*”,代表任何版本都可以使用,依赖项会不考虑兼容问题一直升级。
反例:
Rust
[dependencies]
rand = "*"
正例:
Rust
[dependencies]
rand = "0.8.5"
b)避免版本范围过大。例如,>=2.0.0 可以引入任何与SemVer不兼容的版本,如version5.0.0
,这可能会导致将来构建失败。
反例:
Rust
[dependencies]
foo = ">=2.0.0"
c)避免版本范围过小。例如,同一项目下某一个包指定依赖项bar="~1.3",而另一个包指定要求bar="1.4",二者将无法归一,即使minor版本应该是相互兼容。
例外:
若项目由于特殊需求需要使用‘=’符号锁定使用版本,建议使用工作空间(workspacce)的依赖继承特性进行归一。
6.5 同一项目或工作空间(workspace)内的不同模块间存在依赖关系时,依赖项中的路径宜使用相对路径指定
同一项目或工作空间(workspace)内的不同模块间存在依赖关系时,依赖项中的路径使用相 对路径指定。避免出现使用绝对路径造成在其他环境上构建时出现找不到文件的情况。
反例:
Rust
[dependencies]
tokio-macros = { version = "~2.0.0", path = "/absolute/path/to/tokio-macros",
optional = true }
正例:
Rust
[dependencies]
tokio-macros = { version = "~2.0.0", path = "../tokio-macros", optional = true }
6.6 开启未来不兼容项(Future incompat report)检查,并将告警清零
Cargo检查所有依赖项中的未来不兼容项并输出告警,这些告警是针对将来可能无法成功编译的依赖项需要针对这些告警的提示修改依赖项,建议调整选型版本,或者与上游社区依赖项的开发人员合作以解决不兼容问题,将告警清零。
不兼容项告警示例:
warning: the following packages contain code that will be rejected by a future
version of Rust: rental v0.5.5
note: to see what the problems were, use the option `--future-incompat-report`,
or run `cargo report future-incompatibilities --id 1`
该选项在.cargo/config.toml中可设置,默认开启:
Rust
[future-incompat-report]
frequency = "always"