默认 Cargo feature resolver
概述
edition = "2021"
在Cargo.toml
中隐含着resolver = "2"
。
详情
自 Rust 1.51.0 起,Cargo 对 新的 feature resolver 提供选择性支持,可以在 Cargo.toml
中通过 resolver = "2"
启用它。
从 Rust 2021 起,这将成为默认设置。也就是说,在 Cargo.toml
中写入 edition = "2021"
将隐含着 resolver = "2"
。
resolver 是一个 工作区 (workspace) 的全局设置,此设置在依赖项中会被忽略。该设置仅对工作区的顶级包生效。如果您使用的是虚拟工作区 (virtual workspace),如果您想选择启用新的 resolver,仍然需要在 [workspace]
定义中显式设置 resolver
字段。
新的 feature resolver 不再合并被多种方式依赖的 crate 的所有请求 feature。详见 Rust 1.51 的公告。
迁移
没有针对更新到新 resolver 的自动化迁移工具。对于大多数项目,更新后通常只有很少或没有变化。
使用 cargo fix --edition
更新时,如果新 resolver 会以不同的 feature 构建依赖项,Cargo 会显示一份报告。报告可能看起来像这样:
注意:切换到 Edition 2021 将启用 Cargo 中的 version 2 feature resolver。这可能导致某些依赖项构建时启用的 feature 比以前少。有关 resolver 更改的更多信息可在 https://doc.rust-lang.net.cn/nightly/edition-guide/rust-2021/default-cargo-resolver.html 找到
构建以下依赖项时,指定的 feature 将不再使用bstr v0.2.16: default, lazy_static, regex-automata, unicode libz-sys v1.1.3 (as host dependency): libc
这让您知道某些依赖项将不再以指定的 feature 构建。
构建失败
在某些情况下,更改后您的项目可能无法正确构建。如果一个包中的依赖项声明假定另一个包中启用了某些 feature,而这些 feature 现在已被禁用,则可能导致编译失败。
例如,假设我们有这样一个依赖项:
# Cargo.toml
[dependencies]
bstr = { version = "0.2.16", default-features = false }
# ...
并且在我们依赖树中的某个地方,另一个包有这个依赖项:
# Another package's Cargo.toml
[build-dependencies]
bstr = "0.2.16"
在我们的包中,我们一直使用 bstr
的 words_with_breaks
方法,该方法要求启用 bstr
的 "unicode" feature。过去这之所以能够工作,是因为 Cargo 统一了这两个包中 bstr
的 feature。然而,更新到 Rust 2021 后,新的 resolver 将构建 bstr
两次,一次是带有默认 feature(作为构建依赖),一次是没有 feature(作为我们的普通依赖)。由于 bstr
现在构建时没有 "unicode" feature,words_with_breaks
方法不存在,构建将因方法缺失而失败。
这里的解决方案是确保依赖项声明了您实际使用的 feature。例如:
[dependencies]
bstr = { version = "0.2.16", default-features = false, features = ["unicode"] }
在某些情况下,这可能是由于您没有直接控制权的第三方依赖项引起的问题。您可以考虑向该项目提交一个补丁,尝试为有问题的依赖项声明正确的 feature 集。或者,您可以在自己的 Cargo.toml
文件中为任何依赖项添加 feature。例如,如果上面给出的 bstr
示例是在某个第三方依赖项中声明的,您可以将正确的依赖项声明复制到您自己的项目中。只要符合新 resolver 的合并规则,feature 就会被合并。这些规则是:
- 对于当前未构建目标的平台特定依赖项上启用的 feature 将被忽略。
- 构建依赖项 (build-dependencies) 和 proc-macros 不与普通依赖项共享 feature。
- 开发依赖项 (dev-dependencies) 不会激活 feature,除非构建需要它们的 target (例如测试或示例)。
一个真实的例子是使用 diesel
和 diesel_migrations
。这些包提供数据库支持,并通过 feature 选择数据库,像这样:
[dependencies]
diesel = { version = "1.4.7", features = ["postgres"] }
diesel_migrations = "1.4.0"
问题在于 diesel_migrations
有一个内部 proc-macro,它本身依赖于 diesel
,并且该 proc-macro 假定其自己的 diesel
副本启用了与依赖关系图其余部分相同的 feature。更新到新的 resolver 后,构建失败,因为现在存在两个 diesel
副本,而为 proc-macro 构建的那个缺少 "postgres" feature。
这里的解决方案是将 diesel
作为带有所需 feature 的构建依赖项添加,例如:
[build-dependencies]
diesel = { version = "1.4.7", features = ["postgres"] }
这会使 Cargo 将 "postgres" 作为 host 依赖项(proc-macros 和构建依赖项)的 feature 添加。现在,diesel_migrations
proc-macro 将启用 "postgres" feature,并能正确构建。
diesel
的 2.0 版本(目前正在开发中)没有这个问题,因为它已经被重构以消除这个依赖要求。
探索 feature
cargo tree
命令有了显著改进,有助于迁移到新的 resolver。cargo tree
可用于探索依赖关系图,查看哪些 feature 被启用,以及重要的是,为什么 它们被启用。
一个选项是使用 --duplicates
flag(简写为 -d
),它会告诉您何时一个包被构建了多次。以上面的 bstr
示例为例,我们可能会看到:
> cargo tree -d
bstr v0.2.16
└── foo v0.1.0 (/MyProjects/foo)
bstr v0.2.16
[build-dependencies]
└── bar v0.1.0
└── foo v0.1.0 (/MyProjects/foo)
此输出告诉我们 bstr
被构建了两次,并显示了在两种情况下导致其包含的依赖链。
您可以使用 -f
flag 打印每个包正在使用的 feature,像这样:
cargo tree -f '{p} {f}'
这会告诉 Cargo 改变输出的“格式”,它将打印包名和启用的 feature。
您还可以使用 -e
flag 指定要显示的“边”(edges)。例如,cargo tree -e features
将在每个依赖项之间显示每个依赖项添加了哪些 feature。配合 -i
flag 使用此选项会更加有用,-i
flag 用于“反转”依赖树。这使您能够查看 feature 如何流入给定的依赖项。例如,假设依赖关系图很大,并且我们不太确定谁依赖了 bstr
,以下命令将显示该信息:
> cargo tree -e features -i bstr
bstr v0.2.16
├── bstr feature "default"
│ [build-dependencies]
│ └── bar v0.1.0
│ └── bar feature "default"
│ └── foo v0.1.0 (/MyProjects/foo)
├── bstr feature "lazy_static"
│ └── bstr feature "unicode"
│ └── bstr feature "default" (*)
├── bstr feature "regex-automata"
│ └── bstr feature "unicode" (*)
├── bstr feature "std"
│ └── bstr feature "default" (*)
└── bstr feature "unicode" (*)
此输出片段显示项目 foo
依赖于 bar
,启用了 "default" feature。然后,bar
依赖于 bstr
作为构建依赖,启用了 "default" feature。我们还可以进一步看到 bstr
的 "default" feature 启用了 "unicode"(以及其他 feature)。