Cargo 的这个开发周期:1.77

2024 年 2 月 13 日 · Ed Page 代表 Cargo 团队

Cargo 的这个开发周期:1.77

这是过去 6 周左右,即 Rust 1.77 的合并窗口期间,Cargo 开发方面发生的事情的总结。

本周期的插件

Cargo 不可能满足每个人的需求,如果说没有其他原因,那也得是出于它必须维护的兼容性保证。插件在 Cargo 生态系统中发挥着重要作用,我们希望庆祝它们。

本周期我们推荐的插件是 cargo-watch,它会在源代码更改时重新运行 cargo 命令。有关将其合并到 cargo 中的讨论,请参阅 #9339

感谢 LukeMathWalker 的建议!

请提交您对下一篇文章的建议。

实现

改进 cargo new

cargo new 在 Cargo 1.71 中获得了检测工作区并自动继承其字段的能力,并在 Cargo 1.75 中更新了 workspace.members。这些是分开实现的,字段继承没有考虑到工作区成员排除,这由 hi-rustin#13261 中解决。linyihai 然后将工作区包含的逻辑限制为被发现的包是否已在 #13391 中具有 [workspace] 表。linyihai 还在 #13411 中,如果我们编辑了 workspace.members,则向用户添加了一个 note:

每当您运行 cargo new 时,您都会收到一条注释,其中给出了填写 Cargo.toml 的后续步骤。

# See more keys and their definitions at http://doc.rust-lang.net.cn/cargo/reference/manifest.html

虽然这可以帮助新的 Rust 程序员,但这会添加现有 Rust 程序员每次调用都必须删除的样板代码。在努力考虑到两组用户的情况下,我们正在尝试将其作为 note:#13371)。就我个人而言,我觉得在注释之后看到注释的上下文(创建了一个包)很奇怪,所以在 #13367 中,我们从在最后打印 Created 状态切换到在开头打印 Creating 状态。

使用之前的 Created

$ cargo new foo
      Adding `foo` as member of workspace at `/home/epage/src/personal/cargo`
note: see more `Cargo.toml` keys and their definitions at http://doc.rust-lang.net.cn/cargo/reference/manifest.html
     Created binary (application) `foo` package

使用新的 Creating

$ cargo new foo
    Creating binary (application) `foo` package
      Adding `foo` as member of workspace at `/home/epage/src/personal/cargo`
note: see more `Cargo.toml` keys and their definitions at http://doc.rust-lang.net.cn/cargo/reference/manifest.html
cargo upgrade 合并到 cargo update

随着 cargo addcargo remove 合并到 cargo 中,从 cargo-edit 合并的最后一个主要工具是 cargo upgrade。目前,我们只关注不兼容的升级(#12425),推迟考虑在其他情况下修改版本要求(#10498)。

到目前为止,重点是改进 cargo update,包括

在这个开发周期中,我们在 #13372 中添加了对滞后的依赖项的突出显示,为所有 cargo 用户提供 cargo-outdated 的一个子集(另请参阅 #4309)。

在审查期间,该 PR 因未遵循我们的 控制台输出样式指南 而被指出。这是一个“复制现有代码样式”的例子。为了减少将来发生这种情况的可能性,#13410 使我们的更多控制台输出与我们的样式指南保持一致。

剩余的任务是添加一个 --breaking 标志并扩展 --precise <breaking> 以便修改版本要求。

cargo update --precise <yanked>

从 1.76 更新

以前,cargo 团队批准选择撤销的包。weihanglo#13333 中提供了一个实现,该实现已合并。它正在经过 一轮测试,然后才能稳定。

这对于 cargo-semver-checks 很有意义。当前的解决方案没有完全解决他们的 需求。我们希望将此从 --precise 选择撤销的包扩展到 Cargo 考虑撤销的包,但优先级最低。这为撤销的包打开了很大的门,我们希望在合并 --precise 支持后进一步评估剩余的用例,以查看这是否值得。

-Zcheck-cfg

从 1.76 更新

Urgau 和我讨论了 rustc --check-cfg 参数的一些不一致的语法。为 --cfg 定义一组值的语法被重载,因此空集被视为无值。实际上,这意味着如果您有 #[cfg(feature = "foo")] 和一个空的 features 表,您将收到一个关于 features 未定义的警告,而不是关于值 foo 未定义的警告。这在 rust-lang/rust#119473rust-lang/rust#119930#13316 中得到了修复。有关更多详细信息,请参阅 Urgau 的评论

来自此 lint 的一个不幸的误报是使用 #[cfg_attr(docsrs, ...)]docs.rs 上启用 nightly 功能的 crate。此警告只能通过添加 build.rs 来定义 docsrs 或使用 #![allow] 完全禁用此功能来解决。rustc 维护了一个手写的“众所周知”的 --cfg 列表,但这只是按照惯例完成的,而不是官方支持的。因此,我们决定看看是否可以通过让 docs.rs 代表用户将 --cfg docsrs 传递给 rustdoc 来官方支持。似乎有兴趣,所以我打开了 rust-lang/docs.rs#2389,Urgau 使用 rust-lang/docs.rs#2390 关闭了它。然后,--cfg docsrs 被添加到了 Cargo “众所周知”列表中。Cargo 似乎更适合作为其宿主,因为 docs.rs 通常与 crates.io 联系在一起,而 crates.io 通常与 Cargo 联系在一起,而 rustc 可以与其他构建系统一起使用。

cargo 团队就稳定该功能进行了初步讨论。人们对性能提出了担忧,特别是当有大量功能时,例如 windows。我们要求对 windows 进行 -Zcheck-cfg 基准测试,以验证其影响。我们也倾向于将此功能限制为“本地”包。这意味着只检查工作区成员和路径依赖项,而保留 git 和注册表依赖项。Cargo 和 rustc 已经有了“cap lints”的概念,可以隐藏来自非本地依赖项的警告。

一个征集测试已经发布。

用户控制的 cargo 诊断

从 1.76 更新

正如在 1.76 版本中提到的,Cargo 团队正在努力更新 annotate-snippets,使其看起来像 rustc 的消息。最初的意图是让所有 Rust 项目诊断渲染器都使用此 crate 来实现统一的外观。这项工作在 rustc 方面停滞不前,这是在 清理 rustc 期间提出的,其中建议删除该代码。这再次引发了关于拥有统一渲染器的讨论。最终,决定让 Cargo 成为这项工作的试验台,因为它的用例更简单,因为对于更丰富的错误消息没有现有的期望。这将有助于弥合 rustc 需求的差距。

说到像 rustc,MuscraftPR 已合并,用于使用与 rustc 相同的配色方案。

向 cargo 添加类似 rustc 的消息的第一阶段在 #13172 中合并。我们收到了一份关于 panic 的报告(在 #13375 中修复),该报告突出显示了一条糟糕的 TOML 解析消息,因此也进行了修复(#13376)。

当未请求调试信息时,剥离 std 的调试信息

从 1.76 更新

以前,我们讨论过在 debug=0 时隐式设置 strip = "debuginfo"。来自 Kobzol 的正式提案被接受并在 #13257 中实施。通过此更改,默认 release 配置文件构建中将剥离 std 的调试符号。这更接近用户对 debug=0 的期望,并且也兑现了我们在 Cargo 文档中的承诺:完全没有调试信息。据观察,发布二进制文件小了约 3-4 MiB,并且在 Linux 上,编译略快。但是,macOS 上的编译可能会稍微慢一些(构建 cargo 大约慢 1%),因为它需要调用外部 strip 命令。另一个已知问题(#11641)是,在 macOS 上,它依赖于系统的 strip,如果 strip 命令被不兼容的 strip 二进制文件所覆盖,则可能会失败。我们将继续监控它是否会成为 Rust 维护人员或用户的负担。有关更多详细信息,请参阅 Kobzol 的文章

稳定 cargo metadataid 字段

从 1.76 更新

FCP 已完成,并且稳定 PR 已合并。

得益于每晚的测试,我们发现人们将 cargo metadata 的输出与 cargo build --message-format=json 的输出相关联,这是我们之前忽略的。因此,我们在 #13311 中将此稳定化扩展到了 --message-format=json,并在 #13322 中添加了测试,以确保它们的输出是可互操作的。

设计讨论

当人们对调试构建进行基准测试时,减少意外

Rust 新用户的一个常见误区是,他们对代码进行基准测试时发现速度出奇地慢,而答案很简单,只需传递 --release 即可。jackh726 发起了一个讨论,探索如何帮助用户避免这个误区(另请参阅 #9446)。

默认配置文件 dev 针对快速反馈进行了优化,通过包含调试信息和激活 debug_assertions 使调试更容易。假设调试将是内部开发循环的一部分,只偶尔发布。随着 cargo check 的引入,对速度的需求略有降低。

不期望这样做的用户必须在所有编译器输出中注意到并解读 dev [未优化 + 调试信息]

头脑风暴正在进行中,但想法包括:

  • 要求使用 --profile
  • 调整状态行的文本
  • 在状态行中添加表情符号或样式
  • 支持在配置中为每个命令设置默认配置文件,并在未设置时发出警告
  • 更改命令的默认配置文件
  • 减少其他输出(在 #8889 中有所讨论)

在解决这个问题时,我们需要仔细权衡所有用户的需求,包括我们对向后兼容性的承诺。讨论正在进行中。

Cargo 脚本

从 1.76 更新

截至 1.76 版本,语法方面存在两个问题

  • 信息字符串的含义是归 rustc 所有还是归使用它的工具所有
  • 反引号的使用使得在 Markdown 中嵌套 Cargo 脚本(如在 Issues 中)令人困惑

关于信息字符串的讨论可以追溯到其目的。Rustc 已经有 #[attributes] 可以工作,不需要这个新的语法。如果有的话,重点应该放在改进属性上。这个新语法是围绕无法轻易使用属性的外部工具的需求设计的。考虑到这一点,有人建议让外部工具来定义它。

如果我们同意这一点,那么我们要求信息字符串的临时措施就消失了,从而减少了最小语法,并且更容易摆脱 Markdown 代码围栏,避免了嵌套问题。在与 T-lang 的头脑风暴中,考虑了几种语法。目前,Cargo 支持所有这些语法,供人们试用(#13241, #13247)。

在讨论它们并评估用户报告后,包括 timClicks反应视频,提出了以下语法:

#!/usr/bin/env cargo

---
[dependencies]
clap = { version = "4.2", features = ["derive"] }
---

use clap::Parser;

#[derive(Parser, Debug)]
#[clap(version)]
struct Args {
    #[clap(short, long, help = "Path to config")]
    config: Option<std::path::PathBuf>,
}

fn main() {
    let args = Args::parse();
    println!("{:?}", args);
}

语法 RFC 已 提议合并

在 Cargo 方面,仍然存在如何处理配置文件的问题。

何时使用包或工作区?

Cargo 可以很容易地将二进制文件和库混合在同一个包中:你只需创建文件即可。问题是人们很快就会遇到 Cargo.toml 设计上的限制。例如,通过执行 cargo add pulldown-cmark,你会引入一个 CLI 解析器,它会减慢你的构建速度,你应该添加 --no-default-features

人们围绕这个问题提出的问题包括:

当使用 RFC #3374 改进其中一个方面时,我们发现它会导致 关于功能统一如何工作的更多困惑,而这已经是一个引起困惑的话题

我们是否在把一个方钉子硬塞进一个圆孔?正如一位团队成员所说:“在‘只需添加一个二进制文件’和‘添加一个新包’之间存在一个盲区”。也许我们可以考虑改进这方面的工作区。为此,有人提出了一个思想实验:如果我们每个包只支持一个构建输出会怎样?痛点会在哪里?

一个差距是新用户不了解如何采用工作区(另请参阅 #5656)。有人提出一个想法,即使用一个工具将一个包转换为一个工作区+包。这类似于将 Cargo 脚本转换为多文件包的想法。也许这种相似性可以帮助我们指导这个工具应该是什么样子。这可能最好作为第三方插件进行实验。

在所有包中管理元数据都存在开销,但最近 cargo new 工作带来的工作区继承已经帮助减少了这种开销。

默认情况下,每个包使用多个文件和目录仍然存在开销。支持 Cargo 脚本作为工作区成员可以帮助解决这个问题。

所有这些方面的一个大问题是,你一次只能发布一个包(#1169)。我们在下面将其列为我们的“重点领域”之一,并已将其提议用于 GSoC。发布不仅仅是发布,人们可能需要采用像 cargo release 这样的工具。我们通过在我们的 发布文档 中提到它们来提高人们对这些工具的认识。嵌套包 也会减少一些发布开销。

还有一个问题是,在一个二进制文件和一个库之间共享一个包名更方便。例如,比较:

$ cargo add pulldown-cmark
cargo add typos

和:

$ cargo install pulldown-cmark
cargo install typos-cli

RFC #3383 试图改进这一点。

虽然我们没有得出任何具体的结论,但我们至少对所涉及的不同挑战有了更好的理解。

RFC #3537:使 Cargo 在选择依赖项时尊重最低支持的 Rust 版本 (MSRV)

从 1.76 更新

在处理此 RFC 的反馈时,作者提出了一个重大更新。部分目标是重新定义围绕不同用例的对话,并弄清楚我们如何优先考虑这些不同的用例。在进行这种重新定义时,观察并解决了工作流程中更多粗糙的边缘。

此 RFC 要求更改解析器的行为。我们曾考虑使用一个新字段来控制这一点,但这会使行为比预期更静态。例如,我们可能希望在本地 cargo check、某些 CI 作业和 cargo install 之间有不同的行为。如果我们有这个,我们可以将其与 Edition 联系起来。因为我们已经开始走这条路,所以 package.resolver 被忽略了。RFC 已更新为允许使用 package.resolver 控制默认值,该字段的默认值将随着下一个 Edition 而更改。

在稳定 Cargo.lock v4 时,出现了一个问题,即在生成 lockfile 时是否尊重 MSRV。在 #12861 中审查该问题时,出现了另一个问题,即如果传入 --ignore-rust-version,我们是否应该这样做。今天,它的意思是“忽略 MSRV 不兼容的错误”。使用 RFC,它也意味着“不要基于 MSRV 进行解析”。Lockfile 将添加第三层含义。这是否太多了?在评估它时,大多数人可能不会将 --ignore-rust-version 传递给构建命令,因为他们预测依赖关系树会发生变化,而是会将其更多地用于像 cargo update 这样的 lockfile 命令。同样,我们预计 cargo build --ignore-rust-version 的需求会减少,因为 RFC 要求将错误转换为默认拒绝的 lint。我们可能会弃用构建命令中的该标志,从而减少这种重载。我们认为没有理由为此而搁置 RFC,我们可以在 RFC 合并后解决 lockfile 的问题。

在 Pre-RFC 中,一位用户指出,他们的 cargo publish 在从其 MSRV 工具链运行时会失败。这是因为 Cargo 仅在你有二进制文件时才重用你的 lockfile,导致选择最新的依赖项。我们由此创建了 #13306,推迟了任何决定。

RFC #3516(公共/私有依赖项)

从 1.76 更新

在跟踪问题上提出了一个担忧,即关于 公共依赖项在稳定时需要 MSRV 升级,这将减慢该功能的采用速度。到目前为止,我们的流程一直专注于要求 MSRV 升级以采用新功能,因为这是确保用户意图得到保留的安全默认设置。例如,对于 different-binary-name,忽略 filename 字段而不是出错会产生意外的结果。我所知第一次 Cargo 将不稳定的 Cargo.toml 字段视为稳定版上未使用的键是 package.rust-version,因为它仅用于诊断目的。然后,在 [lints]中重复了这一点。我们已经 澄清了我们的不稳定功能文档,使其更容易评估 MSRV 升级的替代方案。对于公共依赖项,我们决定在稳定版上发出警告,而不是出错(#13340)。
虽然我们无法改变过去,但一些编译器问题(rust-lang/rust#71043, rust-lang/rust#119428)使得尚不清楚此功能何时会稳定下来,因此我们可能有足够的时间间隔来证明这项工作的合理性。我们决定支持通过 Cargo.tomlcargo-features(适用于始终需要它的人)和 -Z(适用于希望在稳定版上构建的人)来启用该功能。

在审查 RFC #3560 时,有一个关于 希望所有 Edition 中的警告都相同的注释。在 RFC #3516 中,我们犯了根据 Edition 更改级别的错误,以减少噪声。在 在 Zulip 上讨论这个问题时,我们需要在稳定之前重新评估这个决定。

后备依赖项

可选依赖允许调用者选择更专业的实现,例如 winnow 具有一个特性,可以将手动实现的字符串搜索替换为 memchr。有时你可能想重用 crate 中现有的回退实现(另请参阅 #1839)。我们在讨论中使用的例子是 flate2 及其底层使用的压缩库。如果启用了两个后端,flate2 会优先选择其中一个,而另一个会被忽略,但这会减慢用户的构建速度。

这个问题可以通过互斥的全局特性来解决,但在那之前,是否有更小的解决方案可以尝试?

例如,我们是否可以支持 target."cfg(not(feature = "miniz_oxide"))".dependencies (另请参阅 #8170)?我们无法处理这些依赖项,因为我们正在解析特性,并且是以增量方式构建特性集,而没有一个地方可以表示“这个特性集已经完成,让我们评估 not(features)”。我们可以正常地解析特性,然后检查 not(features) 并添加它们。但是,这样做会失败,因为这些新的依赖项将不会执行特性解析。相反,我们需要循环执行特性解析,检查 not(features),并将它们添加到我们下次评估的集合中。这实现起来很复杂,算法也复杂,并且可能会与 dev-dependencies 产生循环。

我们是否可以让 build.rs 请求启用某些特性?像上面一样,这也会遇到实现和算法复杂性的问题。这还会遇到分歧解析的问题,即后面的包启用了会更改之前已构建包的解析的特性。

当回退是为了与旧版本的 Rust 兼容时,可能有效的方法是允许像 target."cfg(accessible(std::io::IsTerminal))".dependenciesrust-lang/rust#64797)或 target."cfg(version(1.70.0))".dependenciesrust-lang/rust#64796)这样的依赖项。

构建脚本指令

从 1.76 更新

构建脚本通过 打印特殊命令 与 cargo 通信。我们发现很难添加新的指令,因为我们在定义链接元数据时与用户共享命名空间。我们通过将指令前缀从 cargo: 迁移到 cargo:: 来解决这个问题,从而将我们的命名空间与用户的命名空间分开 (cargo::metadata)

在这样做时,我们忽略了 target.<triple>.<links> 也有类似的问题 (另请参阅 #12201)。由于新的语法已在 1.76 版本中稳定,而 1.76 版本正处于 beta 阶段,因此紧迫的问题是我们是否需要回退并一起完成这些工作。经过讨论,我们认为它们之间没有硬性要求必须保持同步,尽管一致性很好。我们现在正在 #13211 中跟踪此配置方面的问题。

Cargo 和 rustup

GuillaumeGomez 准备他们的关于自定义 linters 的博客文章时,他们遇到了一个问题,因为他们期望 cargo install --path <foo> 使用在 <foo> 处发现的 rust-toolchain.toml 文件,而不是从他们当前的目录 ( #11036 )。像 .cargo/config.toml 一样,rust-toolchain.toml 是一种“环境配置”,并且不遵守诸如 --manifest-path 之类的标志。但是,cargo 为 cargo install (以及即将推出的 cargo script)的 .cargo/config.toml 做了一个例外。我们是否可以为 rust-toolchain.toml 做类似的处理?

Rustup 是一个可选的工具链管理器,其本质是版本化且独立于 Cargo 分发的。我们在 Cargo 中为它做了一些特殊的处理,但它更侧重于错误消息和性能。如果让 Cargo 承担 Rustup 在识别要使用的工具链版本方面的一些角色,我们将打破一个抽象。我们还必须谨慎行事,因为存在对隔离工具链的需求,例如 Linux 发行版。更糟糕的是,当混合使用旧的 Cargo 与新的 Rustup 或新的 Rustup 与旧的 Cargo 时,我们可能会遇到行为不匹配的情况,导致 Cargo 做了错误的事情。

可能的第一步是向用户发出警告,告知工具链被忽略了。

其他

没有进展的重点领域

这些是 Cargo 团队成员感兴趣的领域,但在此开发周期内没有可报告的进展。

准备开发

需要设计和/或实验

计划

如何提供帮助

如果您有改进 cargo 的想法,我们建议您首先检查我们的积压工作,然后在Internals上探索这个想法。

如果存在您希望解决但此处未讨论的特定问题,您可以采取以下一些步骤来帮助推动它向前发展

  • 总结现有的对话(示例:更好地支持 docker 层缓存Cargo.lock 策略的变更MSRV 感知的解析器
  • 记录其他生态系统中的现有技术,以便我们可以基于其他人所做的工作进行构建,并在有意义的地方为用户提供熟悉的东西
  • 记录 Cargo 中相关的问题和解决方案,以便我们了解我们是否在正确的抽象层进行解决
  • 基于这些帖子,提出一个考虑到以上信息和 cargo 的兼容性要求的解决方案(示例

我们可以在 zulip 上帮助指导人们解决 S-accepted 问题,您可以在贡献者办公时间与我们实时交谈。如果您希望帮助这里提到的某个更大的项目并且刚刚开始,解决一些问题将帮助您熟悉流程和期望,从而使事情进展更加顺利。如果您想在没有导师的情况下解决问题,那么您需要自己完成的工作的期望会更高。