Cargo 开发周期:1.81

2024 年 8 月 15 日 · Ed Page 代表 Cargo 团队

Cargo 开发周期:1.81

本文总结了 Rust 1.81 合并窗口期间 Cargo 开发的进展。

本周期的插件

Cargo 不可能满足所有人的需求,原因之一是它必须维护兼容性保证。插件在 Cargo 生态系统中发挥着重要作用,我们希望支持它们。

我们本周期的插件是 cargo nextest,它是 cargo test 的替代方案。T-testing-devex 已经成立,以便更好地专注于改进测试工作流程。我们希望能够从 cargo nextest 的一些经验中学习,并将其融入 cargo test 中。

感谢 LukeMathWalker 的建议!

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

实现

快照测试

来自 1.80 的更新

虽然没有值得注意的技术讨论,但我们认为有必要表扬那些参与将 Cargo 的 3000 多个测试从自定义断言移植到 snapbox 的人员的辛勤工作,如 #14039 中所跟踪的那样。

我们仍然为初始阶段以及深入研究我们推迟的极端情况留有一些位置。

作为这项工作的一部分,我们一直在重新审查我们的贡献者文档。#14272 是改进它们的第一步。关于贡献者文档,我们根据办公时间的反馈改进了问题分类说明(#14052),并根据 RFC 讨论记录了 RFC 的设计考虑因素(#14222)。

优化 git 源

来自 1.78 的更新

Cargo 的无构建开销再次出现 最近Osiewicz 指出 Zed 作为一个大型工作区,可能值得作为代表性案例进行分析。

Zed 遇到了我们之前未涵盖的跟踪分析案例,需要添加更多跟踪(#14238)。特别是,Zed 有一些 git 补丁(当时为 2 个)和 git 依赖项(当时为 12 个)。

一个简单的修复方法是,当结果已经在我们的数据库中,或者禁用 GC 时,不计算 git 仓库为我们即将到来的 缓存垃圾回收 填充了多少磁盘空间(#14252)。

git 依赖项的其他容易观察到的开销将需要更多精力来解决。

首先,我们完全加载 git 仓库内的所有清单,以查找指定依赖项名称的包。如果可以在 git 仓库中指定路径,我们就不需要这样做(在 #11858 中跟踪)。在不涉及设计工作的情况下,我们可能会优化我们当前的做法。

加载清单有三个不同的昂贵阶段

  1. 解析 TOML
  2. 将解析后的 TOML 反序列化为我们的类型
  3. 其他后处理

我们只需要名称,因此我们可以只执行 (1)。除非这并不完全正确,因为“加载所有清单”并不完全正确。最初,我们跳过隐藏目录和 git 子模块中的清单,仅在我们跟踪路径依赖项时才加载它们。可能优化此操作的唯一方法(在没有新选择加入功能的情况下)是缓存我们的 package.name 到清单路径的索引。缓存并非易事。我们需要一个地方来存储缓存。万一修复了与此相关的错误,我们需要处理缓存失效。如果我们通过按 Cargo 版本缓存来执行此操作,则需要处理删除过时的条目。这并非不可能,但需要一些注意。您可以在 zulip 上关注此进展。

另一个观察到的减速是在子模块更新中。我们使用一个哨兵文件来了解是否需要签出给定 git 提交的文件。但是,该哨兵文件仅保护根仓库,而不保护任何子模块,而子模块需要大量时间来发现并确定不需要执行任何操作。在解决此问题时,我们需要确保它在中断的签出中运行良好,并且在不同的 Cargo 版本中运行良好。需要就确切的解决方案进行更多讨论。

重复包名警告

正如我们刚刚介绍的那样,用户通过名称在 git 仓库中指定包。当两个包具有相同的名称时,Cargo 会选择找到的第一个包。在 1.63 中(#10701),当包名称不明确时,我们添加了一个警告。但是,这没有考虑到用户是否首先依赖于该包名称。例如,Cargo 在测试数据中有很多重复的包名称,没有人引用它们(#10752)。

修复此问题的挑战在于,我们将加载 git 源分为两个单独的阶段:加载所有清单(丢弃重复项),以及按名称查找清单。经过多次重构(#13993#14169#14231),更容易确定更改行为的影响范围,并进行更改本身,我们已在 #14239 中完成。

移除隐式特性

来自 1.80 的更新

在 1.80 的开发过程中,我们发现了一个在不应该失败时失败的极端情况(#14016)。这来自于很容易忘记 "dep_name/feature_name" 的所有含义。从表面上看,它看起来像 "dep:dep_name", "dep_name?/feature_name"。但是,一个区别是 "dep:dep_name" 禁用隐式特性,而 dep_name/feature_name 不禁用。上个周期,我们决定隐式注入 "dep:dep_name",并最终在本周期实现了这一点(#14221)。

但是,我们再次忽略了另一个区别:"dep:dep_name" 不能与必需的依赖项一起使用,而 "dep_name/feature_name" 可以(#14283)。

我们可以根据依赖项是否是可选的来使此条件成立,除了在标准化依赖项已经依赖于标准化特性时,标准化特性将变得依赖于标准化依赖项。我们有几种方法可以打破这个逻辑循环,但它们依赖于对今天有效的假设,我们可能会为未来的自己挖坑。此外,我们还会被这个复杂的设计所困扰,因为我们将来所做的任何事情(例如重做 dep_name/feature_name)都会简化这一点。作为一个团队,我们讨论了是否应该从 2024 版本中删除隐式特性,或者甚至尝试设计出未来 dep_name/feature_name 工作的一个子集。我们可以做出的两个“简单”更改包括允许 dep:dep_name 和/或 dep_name?/feature_name 与必需的依赖项一起使用。有人担心 dep_name?/feature_name 会令人困惑,因为 ? 意味着“可能已启用”。这需要进一步跟进,以查看是否有在没有新设计工作的情况下前进的方法。当我们为此制定长期设计时,我们还需要记住 "dep_name/feature_name" 更像 "dep_name"?, dep:dep_name"?, "dep_name?/feature_name"

为了解除对 2024 版的测试,我们在 #14295 中恢复了 #14221

垃圾回收

(来自 1.78 的更新)

ehuss 建议我们稳定最少量的垃圾回收:一个控制来打开、关闭或控制回收频率。最初,该提议是稳定频率为 6 个月,然后将下载内容切换为 3 个月,将下载生成的内容切换为 1 个月。这样做的目的是为使用旧版本 Cargo 的人提供更多的缓冲时间,这些版本不会将缓存中的项目标记为已使用。但是,我们决定保持预期设置简单,并使用当前数字。

在讨论稳定 PR(#14287)时,配置字段的名称(gc.auto.frequency出现了。一些用户之前报告说,他们发现术语“GC”在此上下文中令人困惑。从技术上讲,该术语是合适的,其他工具也使用它来指代此目的。即使我们对该名称感到满意,我们也应该确保在批准设计时已确认了此担忧。我们正在 #14292 中进一步讨论该名称。

将所有警告转化为错误

在项目中,一个常见的模式是在本地开发期间对警告比较随意,但在 CI 中会将任何警告转化为错误,以防止警告被合并。类似地,开发人员可能希望隐藏数百个警告,以便更好地查看阻止他们的错误。

目前,通过设置 RUSTFLAGS=-Dwarnings 可以使构建因警告而失败,而通过 RUSTFLAGS=-Awarnings 可以隐藏警告,但是

  • 这对 rustdoc 没有帮助,它需要 RUSTDOCFLAGS(这不是很明显)
  • 这对于我们正在努力添加的 Cargo 警告也没有帮助
  • 在运行之间更改 RUSTFLAGS 与构建缓存不太兼容

之前曾提出一个 --deny-warnings 标志(#8424)。为了处理 RUSTFLAGS=-Awarnings 的情况,我们提议将其推广为 --warnings <allow|deny>。在 #12875 中对此进行了首次尝试。

在解决一些我们最近讨论过的未决问题时,这项工作失去了动力。

这是否应该应用于所有现有的 Cargo 警告? 不应该,Cargo 随意地使用“警告”状态,我们需要审查何时将这些警告合理地转化为错误。

如果这是在 CLI 上,是否会与 rustc 的 --warn--allow--deny--forbid 标志混淆? 现在可能不会,但是如果我们将它们添加到 cargo 命令中,可能会。我们现在将从仅配置开始,绕过这个问题。

这应该放在 .cargo/config.toml 中的哪个位置? 我们一致认为不应该放在 term 表中。目前,我们正在考虑 build.warnings,尽管一些警告情况发生在构建之外(例如 cargo publish)。

我们还讨论了配置值。我们需要一个“重置为默认值”(当前为 warn)。我们需要一个“警告转错误”(当前为 deny)。我们还在讨论一个“隐藏警告”(当前为 allow)。我们以 lint 级别的行为来命名它们。build.warnings = "warn" 看起来会很奇怪,RUSTFLAGS=--warn=warnings 也会很奇怪。

对于“隐藏警告”的风险存在一些疑问。我们这样做的目的是在开发过程中出现错误时隐藏它们。当然,如果内置了像 bacon 这样的工具,它可以按级别排序,那将会有所帮助。我们还没有做到这一点。最大的风险是人们过于放任,并将此扩展到他们的 CI。他们已经可以执行 RUSTFLAGS=-Awarnings,所以没有真正的损失。

cargo upgrade 合并到 cargo update

来自 1.80 的更新

torhovland 一直在改进 cargo update --breaking (14259),并致力于添加 cargo update --precise <breaking> (#14140)。这揭示了很多在 #12425 中未明确指定的 UX 决策,这些决策将不同的工作流程相互对立,并与 cargo update 的非破坏性行为对立,包括

  • cargo update --precise <breaking> 是否只应应用于直接依赖项,还是也应应用于传递依赖项(这将导致错误)?当您同时具有依赖项的直接路径和传递路径时,这会变得很奇怪。我们应该只更新其中一个还是报错?
  • 为了允许发生破坏性更新,cargo 应该在多大程度上自由地更新其他依赖项?目前,--precise 只允许您指定的依赖项更新,除非您传递 --recursive。破坏性更改很可能会产生连锁反应。

其中一些正在这些 PR 中解决,而另一些则留给 跟踪问题

cargo publish --workspace

来自 1.80 的更新

jneem 继续进行 cargo package --workspace 的工作。第一步是将 cargo package 切换为分阶段运行,首先打包 .crate 文件,然后验证它们 (#14074)。一旦到位,他们添加了构建多个 .crate 文件 (#13947)。

构建指纹识别

Xaeroxezulip 上重新开始了关于使用校验和而不是 mtime 对源代码进行指纹识别的讨论。具体来说,他们开始进行这项工作,并希望避免 cargo 对源代码进行校验和与 rustc 构建之间的竞争条件。理想情况下,rustc 将对源代码进行校验和以避免这种情况。分享了过去在这方面的一些工作。从那里,他们创建了一个编译器 MCP 和一个针对编译器的 PR。但是,要对 build.rs 执行此操作,需要 build.rs 文件选择加入,并且它们需要协调校验和算法。我们决定暂时放弃这一点,并暂时让 build.rs 使用 mtime。

Xaeroxe 发布了 #14137,它是 Cargo 校验和指纹识别的初始版本。

cargo info

来自 1.79 的更新

在等待一段时间收集反馈后,Rustin170506 提议将 cargo info 合并到 cargo 中 (#14141)。这包括将代码从作为第三方编写的方式调整为 Cargo 期望的方式,添加了文档和补全脚本。

关于是否立即稳定存在一些讨论。作为第三方命令,我们可以获得仅限 nightly 功能的测试优势。如果我们不立即稳定它,您只能将其作为 cargo +nightly -Zsubcommand info 运行,或者运行现有的 cargo-info,而不是 cargo info,而是 cargo-info infocargo info 将会失败。与 cargo addcargo remove 类似,我们决定立即稳定它。

此命令的 FCP 已启动。

设计讨论

--lockfile-path

当您(或 rust-analyzer)运行 cargo metadata 时,Cargo 将确保 lockfile 与 Cargo.toml 文件同步,并在需要时生成它。但是,如果您在只读文件系统上执行此操作,则会失败 (#10096)。

在讨论该问题时,主要的观点是

  • cargo metadata 的输出应该是一致的,这只有在我们能够将 Cargo.lock 写入文件系统时才会发生
  • cargo metadata 应该始终让我查询信息,即使这意味着信息会因外部更改而更改(例如,发布了依赖项的新版本,因此选择了它)

后者的​​问题在于用户意图。如今,您可以运行 cargo metadata --no-deps,并且不会生成 Cargo.lock,因此没有问题。一旦您请求依赖项,您就是在请求该项目实例和临时状态的依赖项。

一个提议的折衷方案是 --lockfile-path 标志,允许调用者覆盖项目使用的 lockfile 位置,从而允许调用者使用可写位置。Ifropc 主导了对此的讨论,我们在 Cargo 团队中进一步讨论了它。

对于设计,我们决定与 --manifest-path 的行为保持一致

  • CLI 标志,不在清单或配置中
  • lockfile_path.file_name() == "Cargo.lock" 必须为真

除此之外,我们还给出了一些关于开始实现此功能的指导。

path-bases

在我们努力 分类 RFC 的基础上,Cargo 团队讨论了 RFC: 3529: 向 cargo 添加命名路径基 (v2),该 RFC 试图通过集中管理包路径的知识来更轻松地管理具有多个工作区的大型存储库。

对于 Cargo.toml(特定于项目的配置)依赖于 .cargo/config.toml(特定于环境的配置)来获取 base 的定义,这会让人感觉有点奇怪。特别是,为了更好地支持这些角色,我们一直希望找到如何在 Cargo.toml 中支持一些 .cargo/config.toml 工作流程 (#12738)。作为这种情况的真实结果,使用 .cargo/config.toml 中定义的 base 的项目不能用作 git 依赖项或补丁。

虽然作者的用例侧重于跨工作区依赖项,但在大型工作区内指定 base 也可能很有用(尽管通过工作区继承共享依赖项规范也会有所帮助)。我们希望至少在 Cargo.toml 中支持 base 的定义。经过一番讨论后,我们决定可以在 .cargo/config.toml 中定义 base,并将它们分层。这与从 .cargo/config.toml 引用 registry 的依赖项并没有太大区别。

随着允许在 Cargo.toml 中使用 [path-bases],需要解决细节,例如

  • 这是否是像 [dependencies] 这样的包部分,还是像 [profile] 这样的工作区部分
  • 如果是包部分,它是否支持工作区继承,语义是什么?

经过一番讨论后,我们决定最好逐步构建此功能,首先在 .cargo/config.toml 中使用 [path-bases],然后在未来的某个时间将其添加到 Cargo.toml 中。

杂项

没有进展的关注领域

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

准备开发

需要设计和/或实验

计划

如何提供帮助

如果您有改进 Cargo 的想法,我们建议您先查看我们的待办事项,然后在 Internals 上探讨该想法。

如果您希望解决此处未讨论的特定问题,您可以采取一些步骤来帮助推动其进展,包括:

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

我们可以在 zulip 上为 S-accepted 问题 提供指导,您可以在贡献者办公时间期间与我们实时交流。如果您希望帮助解决此处提到的一些较大项目,并且刚刚开始,那么修复一些问题将有助于您熟悉流程和期望,使事情进展得更加顺利。如果您想在没有导师的情况下解决一些问题,那么对您需要独立完成的工作的期望会更高。