Cargo 的此开发周期:1.79

2024 年 5 月 7 日 · Ed Page 代表 Cargo 团队

Cargo 的此开发周期:1.79

这是过去 6 周左右 Cargo 开发的总结,大约是 Rust 1.79 的合并窗口。

本周期的插件

Cargo 不可能满足所有人的需求,如果没有其他原因,它必须维护兼容性保证。插件在 Cargo 生态系统中起着重要作用,我们希望对其进行表彰。

我们本周期的插件是 cargo-outdated,它提供了过时的依赖项的概述。从 Cargo 1.78 开始,我们在 cargo-update 输出中包含了一些此信息(#13372)。尝试运行 cargo update --dry-run --verbose!关于我们如何进一步改进过时依赖项的报告,请参阅 #4309

感谢 LukeMathWalker 的建议!

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

实现

弃用

来自 1.78 的更新

weihanglo 深入研究了 Cargo 代码库,以列举官方和非官方的弃用,并将它们记录在 #13629 中。

这些弃用最终被分为以下几类

弃用,在下一个版本中删除:包括 #13747#13804#13839

弃用但永不删除:这主要针对 CLI 或 .cargo/config.toml 等没有版本机制来演变它们的区域。

删除,打破兼容性:这主要针对对用户影响最小的错误。

一个简单的例子是 badges.workspace = true 允许从 package.badges 继承。这不在 RFC 中,没有文档记录,并且没有遵循继承的标准模式,使其更难被发现。我们在 #13788 中删除了对此的支持。

Cargo 还允许没有来源的依赖项(例如 dep = {})。这最初在 3 年前在 #9686 中删除,但在报告说它破坏了 bit-set crate 的旧版本后被恢复,该版本由 libusb 使用,该版本已无人维护(参见 #9885)。我们重新审视了这个问题,并决定再次删除对此的支持(参见 #13775),不久之后,libusb 的用户再次注意到了这个问题(#13824)。在更仔细地研究了这个问题之后,我们决定坚持最初的决定。我们 3 年前破坏了人们,从那时起就一直在警告它将被删除,并且有两个维护的替代包(rusbnusb)。

未来重新评估:特别是对于 #4797,我们希望等到有一个稳定的机制来替代它。

用户控制的 cargo 诊断

来自 1.78 的更新。总之,这旨在添加 用户控制的 cargo lints,它们看起来像 rustc,并通过 [lints]进行控制

Muscraft 在这个开发周期的开始时,对 lint 系统进行了粗略的草图(#13621),并对其进行了充实和完善,包括

  • 报告为什么显示 lint(#13801
  • 处理 forbid 的特殊行为(#13797
  • 支持不稳定的 lint(#13805

原始 lint 名称使用 kebab-case 编写。在 #13635 中,它们被切换为也支持 snake_case 以匹配 rustc。在我们必须处理弃用 Cargo.toml 中的 snake_case 字段之后,Muscraft 提出了我们是否应该最初只支持一种大小写形式。一些参与者在风格上更喜欢 kebab-case,尤其是为了匹配清单的其余部分。但是,rustc 认为 snake_case 是规范形式,我们认为这是一个好的起点(#13837)。如果需要,我们稍后总是可以添加第二种风格。

此功能的测试用例是弃用 Edition 2024 中的隐式功能。我们将其建模为对现有版本中隐式功能的弃用警告,而 Edition 2024 将报告可选依赖项为未使用(#13778)。我们讨论了我们希望如何建模未使用的可选依赖项。从高层次上讲,最直接的方法是我们根据版本更改内部枚举功能的方式。但是,这与注册表包不兼容。我们在 Index 中解析它们,Index 没有完整的 Cargo.toml,尤其是版本,并且早期版本的 Cargo 会读取这些 Index 条目并生成隐式功能,在升级 Cargo 时会因为疏忽而中断。也许我们应该努力支持 Index 中的版本,但我们现在不需要这样做。我们最终从已发布的 Cargo.toml 和 Index 中剥离了未使用的可选依赖项。这样做的结果还意味着它们不会像未使用的 workspace.dependencies 一样显示在 Cargo.lock 中。作为副作用,某些 lint 可能不会针对这些依赖项运行。

rendering of unused optional dependency lint

MSRV 感知的 Cargo

来自 1.78 的更新

Edition 2024 所需的子集实际上已经完成了代码!请随意试用留下反馈

我们继续迭代如何报告 lockfile 更改,包括

  • 报告依赖项在任何命令中都发生了更改,而不仅仅是 update#13561#13759

我们继续迭代 MSRV 解析器的行为,包括

  • 当您的 package.rust-version 未设置时,默认为 rustc -V#13743
  • 当依赖项的 package.rust-version 未设置时,调整了行为(#13791
  • 避免将其用于 cargo install#13790

至于控制解析器策略,我们已经实现了

  • --ignore-rust-version 禁用 MSRV 依赖项解析(#13738
  • 我们向 cargo updatecargo generate-lockfile 添加了 --ignore-rust-version#13742
  • 我们添加了一个占位符配置字段,因此可以强制启用或关闭它(#13769)。我们仍然需要为此确定最终名称,请参阅 #13540
  • 我们添加了 package.resolver = "3"#13776
  • 我们将其设置为 Edition 2024 的默认解析器(#13785
Edition 2024

来自 1.76 的更新

除了上述内容之外,对版本的开发工作更加关注 cargo fix。这包括 #13728#13792,作者是 weihanglo

我们还在 Zulip 上讨论了是否应该通过 cargo fix --edition 进行 Cargo.toml 更改,例如更新 package.editionpackage.rust-version

更新 package.edition 的挑战在于 cargo fix 仅针对一组构建目标、平台和功能组合运行,因此我们不知道何时整个项目完全转换为新版本。用户可能需要多次调用才能迁移,并且过早更新 package.edition 可能会妨碍迁移。

规范化已发布的包文件

来自 1.78 的更新

经过大量工作(#13666 #13693 #13701 #13713 #13729),已发布和供应商化的 Cargo.toml 文件现在将包含显式枚举的所有构建目标。

好处

  • 您不能通过删除文件来绕过在供应商化依赖项中添加构建目标的校验和
  • 当所有构建目标都具有显式路径时,如果在打包时排除一个构建目标,您现在会收到警告,这有助于发现错误
  • 您现在可以有目的地排除构建目标而不必设置路径来阻止其发布。
  • 可以更轻松地审核跨版本构建目标的更改。
  • 我们希望这能为解析大型依赖树时带来更多的性能改进。

副作用是,cargo vendor 的输出会因 Cargo 版本而异。 我们尽量减少此类变动,但认为在这种情况下是合理的。

cargo info

来自 1.76 的更新

最近,关于 cargo add 应该如何呈现特性(#10681)的问题有一些讨论。epage 认为 cargo info 可能是尝试他们提议的好地方 (cargo-information#140)。其中一个重要方面是对依赖项应用相同的渲染,以区分必需的、已激活的可选和已停用的可选依赖项。

epage 还使自动选择要显示的版本更加智能。当未指定版本时,cargo info 不再显示最新版本,而是尝试智能地显示与你相关的版本。以前,那是来自你的 lockfile 或与 MSRV 兼容的版本。通过 cargo-information#137,我们不仅检查 lockfile,还首先检查你所在包的直接依赖项,然后检查所有工作区成员的直接依赖项,这使得显示的版本更有可能是你将要使用的版本。

使用 SVG 渲染 cargo-info 的详细输出 (详细输出,通常依赖项是隐藏的)

在这一点上,cargo-information 感觉可以合并到 cargo 中了。请试用一下,并让我们知道您的想法

设计讨论

将补丁文件应用于依赖项

来自 1.76 的更新

之前,我们讨论过关闭此 RFC,要求进行实验性实现以帮助充实设计。 weihanglo#13779 中提出了概念验证。 高级设计讨论正在该 PR 上进行。

Cargo 脚本

1.77 版本更新

T-lang 批准了 RFC #3503 以嵌入清单的语法。这仍然保留了 RFC #3502

虽然 cargo script 主要侧重于探索,但有时人们想要进行繁重的分析并想要发布版本(请参阅 RFC 注释)。

我们可以添加 cargo --release <script>。这遇到了一个常见的挑战,即 #! 处理在处理解释器参数时受到限制,而这些参数无法以完全跨平台的方式解析。

不幸的是,你不能直接这样做

[profile.dev]
inherits = "release"

因为这是内置配置文件所不允许的。 这也会影响你在脚本上做的任何其他操作,例如 cargo test

我们可以引入一个 run 配置文件,因为它是为 cargo 使用而保留的。这个名称暗示我们也应该更新 cargo run 来使用它,这将导致目标目录从 cargo build 更改,从而导致额外的构建和更大的 target/

我们可以为 default-profile 添加配置。这将存储在单独的配置文件中,该文件需要与脚本一起提供。它还会将重点转移到用户设置它,而不是脚本作者。 很可能,脚本作者最清楚该怎么做。我们很可能想将其放入清单中。

知道将来有办法以向后兼容的方式改进这一点,我们很高兴将此推迟到 RFC 之外。

另一个挑战是如何解释 cargo <something> 的细微差别。 它可以是内置命令、别名、第三方命令或脚本。 我们如何决定先尝试哪个? 这是 此 RFC 线程的延续。 epage 在 epage/cargo-script-mvs#154 中提供了关于现有技术的更多详细信息。 在研究不同的情况时,我们发现的主要问题是,名为 cargo test 的脚本将运行 cargo-test。 要运行脚本,你需要运行 cargo ./test

解决此问题的一种明显方法是使用单独的 cargoscript 二进制文件。 RFC 深入探讨了这方面的一些权衡。 如果二进制文件名称以 cargo- 开头,尤其是一个问题,因为它会被视为第三方命令。

但是,这种情况只发生在 Linux 和 Mac(Windows 是一个单独的案例)上非标准的 PATH,或者你的 PATH 中有过于通用的脚本名称时。 这降低了我们的优先级。

我们可能会利用支持的 AT_EXECFN,以便 cargo 可以检测到它作为 #! 解释器运行,并绕过优先级规则。

谈到这一点时,出现了一个新的担忧: cargo <script> 会根据 <script> 而不是你当前的工作目录来加载配置文件。 这类似于 cargo install,与其他所有 cargo 命令不同。 提出的最大担忧是,在临时目录或下载目录之外运行脚本并加载意外的配置文件。 现在,RFC 指定 cargo <script> 仅从 CARGO_HOME 加载配置。

解决了这些问题后,已提议合并该 RFC。

SBOM

来自 1.76 的更新

尽管 RFC 尚未获得批准,但 justahero#13709 中创建了它的概念验证。

作为一个团队,我们最近讨论了此 RFC 的配置方面。 目前,RFC 将其列为 build.sbom = <bool> 配置字段。 当我们之前讨论 RFC #2801 (cargo auditable) 并将重点转移到完整的 SBOM 时,核心思想是我们可以像对调试信息建模一样对 SBOM 建模。 我们可以将其作为 “拆分” “完整” SBOM 开始,并在此基础上进行发展。 这意味着这应该是一个配置文件设置,而不是一个通用的构建设置。 特别是如果我们最终支持将其嵌入到二进制文件中,那么它似乎更依赖于构建类型(发布版与开发版),而不是无条件执行的操作。 但是,调试信息类比失败了,因为一个项目可能既需要最终的 SBOM,也需要嵌入式的 cargo auditable SBOM。

如果我们撇开未来的可能性,假设这主要将由包装工具设置,这些工具将从我们生成的片段中生成适当的 SBOM。 它们将在每次运行的基础上进行设置,而不是期望用户启用此设置,从而将重点放在环境变量上,而不是配置上。 在此框架中,我们不会将其绑定到特定的配置文件,并且要求人们这样做可能会增加一些额外的障碍。

我们现在可以探索使用构建设置,并允许将来分层配置设置。 需要解决的主要问题是是否存在合理的优先级策略。

在讨论期间,我们还提出了另一种框架。 这可以被视为构建报告,它不仅可以用于 SBOM,还可以用于外部工具来分析构建,包括实际构建的内容、作为构建的一部分进行指纹识别的内容等。 在这种情况下,它更像是一个临时的构建设置。

我们尚未达成结论,需要重新审视这一点。

嵌套包

来自 1.78 的更新

上次 Cargo 团队讨论这个问题时,我们没有得出任何明确的结论,并认为我们需要更多时间来深入研究 RFC 文本。

讨论的核心问题是“我们是否认为收益值得付出代价?”。

讨论期间出现的另一个用例是将此与工件依赖项相结合,这样你就可以拥有完整的包构建脚本,而无需发布整个工作区的开销。

当涉及工作区时,嵌套包会创建一个陷阱。 如果你有一个嵌套包是多个工作区成员的公共依赖项,你可能会开始在包之间传递类型。 这在本地可以正常工作,因为它们都将嵌套包的同一实例用作依赖项。 当你发布时,每个包都将拥有其自己的嵌套包实例,并且类型将变得不兼容。

总的来说,人们担心嵌套包可能会鼓励人们更频繁地供应商依赖项,这种方式更难跟踪,并允许代码以不受控制的方式漂移,而我们希望工作流程鼓励人们直接与上游合作。

顺便提一句,在讨论期间,出现了一个新 pub(scope) 的想法,允许访问命名空间内的私有 API。

工作区依赖项继承

LukeMathWalker 最近宣布了 cargo autoinherit,它会将你的工作区成员来源的依赖项合并到 [workspace.dependencies]

在讨论公告时,出现了一个关于 default-features 的陷阱。 如果你在工作区依赖项中设置 default-features = true,则会忽略你的包依赖项中的 default-features = false#12162)。 这可能还不太糟糕,除了 default-features = true 是默认值,包括使用 dep = "1.0" 语法时。 然后,你必须在需要时显式重新启用 default-features,例如

[workspace.dependencies]
foo = { version = "1.0", default-features = false }

[dependencies]
foo = { workspace = true, default-features = true }

在讨论public 依赖项字段#13125)的工作区继承时,epage 的一个想法是,也许依赖项继承应专门关注继承依赖项的来源,而不是其他任何内容。 当试图避免重复自己时,很容易做得过头并重复反映更多当前实现而不是需求的逻辑。 随着实现的变化,你必须在共享状态和特定状态之间来回移动。

将这个概念应用于 default-features,也许我们应该在新 Edition 中禁止在工作区依赖项中使用它,并使包依赖项的 default-features 始终获胜。 由于工作区没有 Edition,我们可以从包的角度将其定义为它继承了一个字段,从而使包的 Edition 生效。

从工作区依赖项中完全删除 default-featuresfeatures 可能与今天的情况相差太远。 例如,维护人员可能会发现共享 serdederive 功能在其整个工作区中都很方便。 一个渐进的步骤可能是将工作区依赖项中缺少 default-features 视为未设置。

我们在 Cargo 团队会议中讨论了这个问题。为了更好地沟通,我们借鉴了之前关于继承 default-features 的讨论中的一个表格。

工作区 (Workspace) 成员 (Member) 1.64 版本行为 1.69 版本行为 提议的 202X 版本行为 原因
df=false 启用 启用,并警告它将被忽略 禁用 基本上意味着在未指定时让包控制默认功能
df=true df=false 启用 启用,并警告 启用,并警告 过去应该报错,因为该字段被忽略了。未来可能会改为报错。
df=true 启用 启用 启用 关于默认功能是否启用的问题没有冲突。
df=true df=true 启用 启用 启用 ^相同
启用 启用 启用 没有歧义。
df=true 启用 启用 启用 启用
df=false df=false 禁用 禁用 禁用 没有歧义。
df=false df=true 禁用 启用 启用 这允许成员决定是否需要默认功能。 features=["default"] 的解决方法似乎没有必要。
df=false df=nothing 禁用 禁用 禁用 编写“给我工作区中写入的任何内容”的自然方式。

讨论的一些要点

  • 记录这一点,因为它偏离了预期的心智模型
  • 切换到此方案的生态系统变动
  • 如果我们不鼓励在工作区依赖项中使用 features,这可能会排除那些将其作为低成本 工作区 hack 的人。我们认为应该谨慎行事,暂不删除 features。

由于这发生在 2024 版本截止日期之后,因此很可能会被搁置等待 2027 版本。

杂项

  • 感谢 PaulDance,我们现在发布了 cargo-test-supportcargo-test-macro,这对于开发与 cargo 集成的工具非常有用 #13418。重点是满足 Cargo 的需求,因此对于其他人来说,API 和文档可能有点粗糙。
  • 感谢 linyihai,我们现在有了对 cargo update foo --precise <prerelease> 的不稳定支持 (#13626),上次在 1.76 中讨论过。

没有进展的关注领域

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

准备开发

需要设计和/或实验

计划中

如何提供帮助

如果您有改进 cargo 的想法,我们建议您首先查看 我们的积压问题列表,然后在 Internals 上探讨这个想法。

如果这里没有讨论到您想要解决的特定问题,您可以采取一些步骤来帮助推动其进展,包括

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

我们可以在 S-accepted 问题上为人们提供指导,在 zulip 上进行交流,您可以在 贡献者办公时间与我们实时交谈。如果您想帮助完成这里提到的一些更大的项目,并且是刚入门,修复一些问题将有助于您熟悉流程和期望,使事情进展更顺利。如果您想在没有导师的情况下解决问题,对您自己需要完成的事情的期望会更高。